AtCoder Beginner Contest 415(ABCDE)
前言
总算是赛时能独立写出d了呜呜。
一、A - Unsupported Type
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;void solve()
{int n;cin>>n;set<int>s;for(int i=0,x;i<n;i++){cin>>x;s.insert(x);}int x;cin>>x;cout<<(s.find(x)!=s.end()?"Yes":"No");
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题没啥好说的,就是把序列里的所有数都存到一个set里,然后看看x在不在即可。
二、B - Pick Two
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;void solve()
{string s;cin>>s;int n=s.length();int cnt=0;for(int i=0;i<n;i++){if(s[i]=='#'&&cnt==0){cout<<i+1<<",";cnt++;}else if(s[i]=='#'&&cnt==1){cout<<i+1<<endl;cnt=0;}}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题就是设置一个cnt记录拿了几个。如果当前位置是“#”且cnt等于0,即现在这个是一组里的第一个,那么就输出下标和一个逗号,然后让cnt加一。如果是“#”且cnt等于1,即现在这个是第二个,那就输出下标并换行,然后让cnt回到0即可。
这代码太丑陋了(捂脸)
三、C - Mixture
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;bool dfs(int status,int n,set<int>&d,vector<int>&dp)
{if(d.find(status)!=d.end()){return false;}if(status==(1<<n)-1){return true;}if(dp[status]!=-1){return dp[status];}bool ans=false;for(int i=0;i<n;i++){if(((status>>i)&1)==0){ans=ans||dfs(status|(1<<i),n,d,dp);}}dp[status]=ans;return ans;
}void solve()
{int n;cin>>n;string s;cin>>s;set<int>d;for(int i=0;i<s.length();i++){if(s[i]=='1'){d.insert(i+1);}}vector<int>dp(1<<n,-1);cout<<(dfs(0,n,d,dp)?"Yes":"No")<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个题一开始没读明白在说什么,后来多读几遍读懂了其实没那么难,就是一个状压dp。
首先,给出的字符串中,是“1”的话就说明当前状态危险。注意,这里的状态是指当前下标的二进制,是1的话就说明药剂里有这一位所代表的试剂。又因为观察数据量,可以发现试剂的种类最多就是18种,所以这就是一个标准的状压dp题。
思路就是,先遍历一遍字符串,把危险的状态存到set里,接着就去跑状压dp的记忆化搜索。方法就是暴力枚举所有可能的状态,如果当前状态里有一位是0,那就改成1去后续递归。如果当前的状态在set里,说明该状态危险,那就返回false。若状态满了,那就说明找到了一种把全部试剂混合起来的方法,那就返回true。
多说一句,赛时其实还多思考了一下这个递归可不可以改成记忆化。举个例子,假如13,即二进制1101的状态是危险的,那么说明药剂里不能同时有1,3,4这三种试剂。而观察可以发现,不管这三种试剂谁先加入谁后加入,都会导致当前状态的药剂危险。所以这个递归存在重复调用的情况,可以改成记忆化搜索。
四、D - Get Many Stickers
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;void solve()
{ll n,m;cin>>n>>m;vector<pll>a(m+1);for(int i=1;i<=m;i++){cin>>a[i].first>>a[i].second;}//按减少的从小到大排序sort(a.begin()+1,a.end(),[&](const pll&x,const pll&y){return x.first-x.second<y.first-y.second;});ll ans=0;for(int i=1;i<=m;i++){ll diff=a[i].first-a[i].second;ll cnt=(n-a[i].first+diff)/diff;//向上取整if(cnt>=0){ans+=cnt;n-=diff*cnt;}}cout<<ans<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题总感觉在哪做过()
这个题看这数据量肯定就是贪心了,思路就是把所有兑换方式按兑换后减少的饮料数排序。这样可以保证每次都用减少的数量最少的兑换方式,只要还能兑换就一直用这个兑换方式,这样就可以保证兑换的次数最多。
排完序之后,就是遍历所有的兑换方式,每次先算出兑换后损失的饮料数。那么观察可以发现,能用当前兑换方式的次数就是当前的饮料数减去这个兑换方式的空瓶数加一,再除以损失数向上取整。(太抽象了这个公式)
之后,如果这个兑换次数大于等于0,(等于0无所谓)那么就让ans加上兑换次数,然后n减去消耗的数量。
五、E - Hungry Takahashi
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;vector<int>dx={0,1};
vector<int>dy={1,0};ll dfs(int x,int y,int d,ll money,int h,int w,vector<vector<ll>>&a,vector<ll>&p)
{if(d==h+w-1){return max(0ll,-(money+a[x][y]-p[d]));}ll ans=1e18;for(int i=0;i<2;i++){int nx=x+dx[i];int ny=y+dy[i];if(nx<=h&&ny<=w){ans=min(ans,max(-(money+a[x][y]-p[d]),dfs(nx,ny,d+1,money+a[x][y]-p[d],h,w,a,p)));}}return ans;
}//TLE暴力解
void solve1()
{int h,w;cin>>h>>w;vector<vector<ll>>a(h+1,vector<ll>(w+1));for(int i=1;i<=h;i++){for(int j=1;j<=w;j++){cin>>a[i][j];}}vector<ll>p(h+w);for(int i=1;i<=h+w-1;i++){cin>>p[i];}cout<<dfs(1,1,1,0,h,w,a,p);
}//检查能否用money走到最后
//可以要求每一步获得的钱最多 -> dp
bool check(ll money,int h,int w,vector<vector<ll>>&a,vector<ll>&p)
{vector<vector<ll>>dp(h+1,vector<ll>(w+1,-1e18));//初始化dp[1][1]=money+a[1][1]-p[1];for(int i=2;i<=h;i++){//走得通if(dp[i-1][1]>=0){dp[i][1]=dp[i-1][1]+a[i][1]-p[i];}}for(int j=2;j<=w;j++){//走得通if(dp[1][j-1]>=0){dp[1][j]=dp[1][j-1]+a[1][j]-p[j];}}for(int i=2;i<=h;i++){for(int j=2;j<=w;j++){//上方走得通if(dp[i-1][j]>=0){dp[i][j]=max(dp[i][j],dp[i-1][j]);}//下方走得通if(dp[i][j-1]>=0){dp[i][j]=max(dp[i][j],dp[i][j-1]);}dp[i][j]+=a[i][j]-p[i+j-1];}}return dp[h][w]>=0;
}//正解
void solve2()
{int h,w;cin>>h>>w;vector<vector<ll>>a(h+1,vector<ll>(w+1));for(int i=1;i<=h;i++){for(int j=1;j<=w;j++){cin>>a[i][j];}}vector<ll>p(h+w);for(int i=1;i<=h+w-1;i++){cin>>p[i];}//二分答案法ll l=0;ll r=1e18;ll m,ans=0;while(l<=r){m=(l+r)/2;if(check(m,h,w,a,p)){ans=m;r=m-1;}else{l=m+1;}}cout<<ans<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve2(); }return 0;
}
这题怎么就赛时没想出二分答案呢!
由于观察发现,初始硬币数的这个答案具有单调性,数量越多越有可能走到终点,那么这就是个标准的二分答案。
那么check方法就是检查用当前的初始硬币数能否走到终点。这里可以贪心一下,让每步拥有的硬币数量最大,从而找到更有可能走到终点的方案,那么这就是一个dp问题了。
首先就是创建一个二维的dp表,定义为走到当前格子时的最大剩余硬币数,初始每个格子都是负无穷表示走不到。根据题目里规定的走法,每个格子都依赖自己上方和左侧的格子。所以先初始化最左侧一列和最上方一行的格子,只有当格子大于等于0,即有可能走到,才根据这个依赖关系填格子。之后就是从上往下从左往右按照依赖关系填格子,能走通才依赖,最后判断终点dp值是否大于等于0即可。
总结
再接再厉吧!