Codeforces Educational 183(ABCD)
前言
太粗心了这一场,不然能上大分的……
一、A. Candies for Nephews
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n;cin>>n;cout<<(3-n%3)%3<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个题观察一下还是很简单的,就是求能把n加成3的倍数的最小数,所以就是3减去n%3之后再%3即可。
二、B. Deck of Cards
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n,k;cin>>n>>k;string s;cin>>s;s=" "+s;if(n==k){for(int i=1;i<=n;i++){cout<<'-';}cout<<endl;return ;}vector<int>cnts(3);for(int i=1;i<=k;i++){cnts[s[i]-'0']++;}vector<char>ans(n+1,'+');int i=1;for(int c=1;c<=cnts[0];c++){ans[i++]='-';}for(int c=1;c<=cnts[2];c++){ans[i++]='?';}i=n;for(int c=1;c<=cnts[1];c++){ans[i--]='-';}for(int c=1;c<=cnts[2];c++){ans[i--]='?';}for(int i=1;i<=n;i++){cout<<ans[i];}cout<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
byd这道题赛时统计词频把k打成n了,WA了两次卡了好一会儿才发现……
首先考虑一定留不下的,那么很显然可以发现,有多少个0,开头就有多少张牌一定留不下。类似地,有多少个1,结尾就有多少张牌一定留不下。之后考虑有可能留不下的,因为操作2移除的位置不确定,所以若有cnt次操作2,那么除了开头和结尾一定留不下的,剩下部分的开头的cnt个和结尾的cnt个一定都是不确定的。那么在填完一定留不下的和不确定的,剩下的就都是一定能留下的。
三、C. Monocarp's String
这题有点意思。
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;int bs(int v,vector<int>&a)
{int l=0;int r=a.size()-1; int m;int ans=-1;while(l<=r){m=l+r>>1;if(a[m]<=v){ans=m;l=m+1;}else{r=m-1;}}return ans;
}void solve()
{int n;cin>>n;string s;cin>>s;s=" "+s;vector<int>cnts(2);for(int i=1;i<=n;i++){if(s[i]=='a'){cnts[0]++;}else{cnts[1]++;}}if(cnts[0]==cnts[1]){cout<<0<<endl;return ;}vector<vector<int>>pos(2*(n+1));for(int i=1,ca=0,cb=0;i<=n;i++){pos[ca-cb+n].push_back(i);if(s[i]=='a'){ca++;}else{cb++;}}int ans=1e9;for(int i=n,ca=0,cb=0;i>=1;i--){int p=bs(i,pos[cb-ca+n]);//cout<<i<<" "<<ca<<" "<<cb<<" "<<p<<endl;if(p!=-1){ans=min(ans,i-pos[cb-ca+n][p]+1);}if(s[i]=='a'){ca++;}else{cb++;}}if(ans==n){cout<<-1<<endl;}else{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;
}
因为要求是删除一个连续的区间,那么就可以从区间的左右端点入手进行考虑。若存在一个区间[l,r],那么左边界l左侧部分和右边界r右侧部分的a和b的个数必然相等。对这个条件转化一下可以得到,左侧部分a的个数减去b的个数和右侧部分a的个数减去b的个数一定互为相反数。举个例子,假如左侧部分为aab,那么a的个数减去b的个数就是1,即a比b多一个。那么假如中间存在一个合法区间,右侧部分a的个数减去b的个数就一定是-1,因为需要把左侧缺少的b补上。
所以有了这个结论,思路就可以是先从左往右遍历一遍,构建pos数组,其中pos[i]表示从左往右扫,a的个数减去b的个数为i的所有位置。又因为有可能出现负数,所以考虑对数组进行一下偏移,让0位置表示-n这个数。在构建完pos数组后,就可以考虑从后往前遍历,若b的个数减去a的个数为x,那么说明当前位置i可以作为区间右边界,让pos[x]中的所有位置作为左边界,构成合法的区间。又因为是要让区间最短,所以可以考虑去pos[x]里二分找小于等于i的最右位置,然后统计区间最小长度即可。
四、D. Inversion Value of a Permutation
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n,k;cin>>n>>k;if(k==0){for(int i=1;i<=n;i++){cout<<i<<" ";}cout<<endl;return ;}//对于一个排列,其区间数由长度最短的逆序对决定,因为大的逆序对形成的区间肯定包括小的//所以若最小的逆序对的右边位置为i,那么区间个数就为i*(n-i),在i=n/2时取到最大值//定义在i位置劈一刀为在i和i+1位置构成一个逆序对//那么就转化成了选若干个位置劈,能不能使总方案数为k -> 01背包//之后对于每个被劈出来的区间,让区间内的数单调递增//k的最大值int mx=n*(n-1)/2;//dp[i][j]:在i位置前分开构成逆序对,方案数为j的状态是由哪个状态转移过来的vector<vector<pii>>dp(n,vector<pii>(mx+1));//方案数正好为k时最后一次劈的位置int ans=-1;for(int i=1;i<n;i++){//不是从前面状态转移过来的dp[i][i*(n-i)]={-1,0};//正好为kif(i*(n-i)==k){ans=i;break;}//i前面的位置for(int j=1;j<i;j++){//能达到的状态for(int l=0;l<=mx;l++){//存在if(dp[j][l].first!=0){//新增的方案数int t=l+(i-j)*(n-i);dp[i][t]={j,l};//正好为k,直接结束if(t==k){ans=i;goto nxt;}}}}}nxt://没找到if(ans==-1){cout<<0<<endl;return ;}//沿着dp表往上跳,记录劈的位置vector<bool>vis(n);//当前状态的方案数int ck=k;while(ans!=-1){vis[ans]=true;auto [np,nck]=dp[ans][ck];ans=np;ck=nck;}vector<int>res(n);//当前区间的最后一个数int cur=n;for(int i=0;i<n;i++){int j=i;while(j+1<n&&!vis[j+1]){j++;}int len=j-i+1;int start=cur-len+1;for(int p=i;p<=j;p++){res[p]=start++;}cur-=len;i=j;}for(auto &x:res){cout<<x<<" ";}cout<<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的……
首先上来的这个结论就不好想,那就是对于一个排列,其区间数是由长度最短的逆序对决定的,因为长的逆序对形成的区间肯定包含短的逆序对形成的区间。举个例子,对于一个排列4231,那么[4,1]这个逆序对形成的区间,必然包括在[4,2]这个逆序对形成的区间内。所以若短的逆序对的右边位置为i,那么能形成区间个数就是i*(n-i),在i等于二分之n时取得最大值。之后我们定义在i位置劈一刀为在i和i+1位置构成一个逆序对。那么就将问题转化成了,在若干个位置劈,使得总区间数恰好为k,那么很显然这就是一个01背包问题了。而对于两个被劈点中间的部分,考虑让区间单调递增,所以还需要求出dp的路径。
那么因为k的最大值就是n*(n-1)/2,所以定义dp[i][j]为在i位置前构成逆序对,区间数为j的状态是由哪个状态转移过来的,同时用ans记录区间数为k的最后一次劈的位置。之后当来到每个位置时,先考虑在当前位置劈的情况,即dp[i][i*(n-i)],若i*(n-i)正好为k,那么就可以直接退出了。否则,就去枚举i前面的位置和可能的区间数,然后求出新增的区间数转移。
dp完以后,若存在区间数为k的方案,那么就需要从dp[ans][k]开始往上跳,记录每个被劈的位置。最后就是考察每一段区间,从大往小,保证每个区间内单调递增,填数即可。
太难了……
总结
还是要多见题多练dp,加油!