Codeforces 1042 Div3(ABCDEFG)
前言
沟槽的D卡了我一个多小时,快结束看了眼E发现E这么简单,结果就是比赛结束了才过了E,之后发现群友的D的思路是真的妙……还是太菜了T^T
一、A. Lever
#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;vector<int>a(n+1);for(int i=1;i<=n;i++){cin>>a[i];}vector<int>b(n+1);for(int i=1;i<=n;i++){cin>>b[i];}int ans=0;while(true){bool flag=true;for(int i=1;i<=n;i++){if(a[i]>b[i]){a[i]--;flag=false;break;}}for(int i=1;i<=n;i++){if(a[i]<b[i]){a[i]++;break;}}ans++;if(flag){break;}}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;
}
这个题可以看到数据量很小,那就直接模拟做就可以了。那就是每次迭代都先过一遍a数组,如果有一个比b数组大那就减小,然后直接break。之后再过一遍a数组,如果比b数组小就增加,然后break。最后看如果第一次操作没发生过那就直接退出输出答案即可。
二、B. Alternating Series
#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;if(n%2==0){for(int i=1;i<n;i++){if(i%2==1){cout<<-1<<" ";}else{cout<<3<<" ";}}cout<<2<<endl;}else{for(int i=1;i<=n;i++){if(i%2==1){cout<<-1<<" ";}else{cout<<3<<" ";}}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;
}
这就是一个简单的构造题。
既然要求绝对值的字典序最小,那么贪心一下肯定是由-1开始,正负正负这样交替。又因为必须满足子数组的累加和是正数,所以对于中间的每个正数,累加和最小的情况肯定是选左右的负数组成的长度为三的子数组。又因为为了保证字典序最小,所以负数肯定都要选-1,那么只要中间的正数是3就一定能保证累加和是正数。而如果最后以正数结尾,那么因为右侧没有-1了,所以填2就能保证累加和是正数。
三、C. Make it Equal
#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,k;cin>>n>>k;vector<ll>s(n+1);for(int i=1,x;i<=n;i++){cin>>x;s[i]=x%k;}multiset<ll>t;for(int i=1,x;i<=n;i++){cin>>x;t.insert(x%k);}for(int i=1;i<=n;i++){if(t.find(s[i])!=t.end()){t.erase(t.find(s[i]));}else if(t.find(k-s[i])!=t.end()){t.erase(t.find(k-s[i]));}else{cout<<"NO"<<endl;return ;}}cout<<"YES"<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这题真的坑,上来看了半天没想明白,后来纯靠猜还真给猜过了。
观察发现,因为可以无限次加减k,所以其实这些数的原始数根本不重要,是需要考虑模k后的余数即可,就是把所有数都一直减k减到再减就小于0为止。这样操作后,对于可以通过反复加k相等的数,这两个数模k的余数肯定是一样的。之后,只需要过一遍s数组,每次考察这个余数和进行一次减k操作后的数在t中是否存在即可,如果两个都不存在就肯定没法凑成。
四、D. Arboris Contractio
还得加训树形结构……
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;const int MAXN=2e5+5;void solve()
{int n;cin>>n;vector<vector<int>>g(n+1);for(int i=0,u,v;i<n-1;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}//叶节点的总个数int sum=0;//连接到同一个节点的叶节点数的最大值int mx=0;for(int i=1;i<=n;i++){int tmp=0;//自己就是叶节点if(g[i].size()==1){sum++;tmp++;}for(int v:g[i]){//孩子是叶节点if(g[v].size()==1){tmp++;}}mx=max(mx,tmp);}//选择有最多叶节点的节点为中心点//操作数就是全部叶节点的个数sum减去最多叶节点的个数mxcout<<sum-mx<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
群友的这个思路太妙了……
首先贪心一下,上来选择叶节点最多的节点肯定不会亏。因为整个过程就是每次把最长链上的节点都连到中心节点,那么每次剩下没连的就是叶节点。所以就是先考察每个节点,统计叶节点的总数和叶节点最多的节点的叶节点个数,并选择这个节点为中心节点。那么之后一次重连肯定只能解决一个叶节点,所以操作数就是叶节点的总数减去最多叶节点个数。
注意力惊人了属于是,赛时根本没往叶节点这方面考虑……
五、E. Adjacent XOR
赛时最后五分钟搓的,一塌糊涂但能过()
#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;vector<ll>a(n+1);for(int i=1;i<=n;i++){cin>>a[i];}vector<ll>b(n+1);for(int i=1;i<=n;i++){cin>>b[i];}if(a[n]!=b[n]){cout<<"NO"<<endl;return ;}vector<ll>dp(n+1);dp[n]=a[n];for(int i=n-1;i>=1;i--){if(a[i]!=b[i]){if((a[i]^dp[i+1])==b[i]){dp[i]=a[i]^dp[i+1];}else if((a[i]^a[i+1])==b[i]){dp[i]=a[i]^a[i+1];}else{cout<<"NO"<<endl;return ;}}else{dp[i]=a[i];}}cout<<"YES"<<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一下,用dp数组存最终i位置的数。那么如果两数本来就相等,那根本就不用改,直接设置eor数组为原始数。如果不同的话,就需要修改了。修改的策略有两种,要么异或右侧原始的数,要么异或右侧修改后的数。那么就是如果跟右侧最终的数,即dp值异或后相等,那就设置当前位置的dp值为异或右侧dp值的结果。如果不行,那么如果跟右侧原始数异或能相等,就设置dp值为异或右侧原始值的结果。如果都不相等,那就根本不可能完成。
六、F. Unjust Binary Life
这b题是真的难……
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;//大于等于x的最左位置
int bs(ll x,int n,vector<array<ll,3>>&cnts)
{int l=0;int r=n-1;int m;int ans=n;while(l<=r){m=(l+r)/2;if(cnts[m][0]>=x){ans=m;r=m-1;}else{l=m+1;}}return ans;
}void solve()
{int n;cin>>n;string a,b;cin>>a>>b;//如果(i,j)->(i+1,j),那么有Ai^Bj=0,A(i+1)^Bj=0//所以可以合并得到Ai^A(i+1)=0,即Ai==A(i+1)//而如果(i,j)->(i,j+1),那么有Ai^Bj=0,Ai^B(j+1)=0//所以可以得到Bj^B(j+1)=0,即Bj==B(j+1)//又因为Ai^Bj=0,即Ai==Bj//所以如果能从(1,1)->(i,j),必须有A1~Ai和B1~Bj的所有数全相等//所以最小操作次数就是min(ones,zeros)//所以可以考虑先预处理a串中从头到i的0的个数zeros和1的个数ones//再根据zeros-ones从小到大排序,生成预处理数组help//之后遍历b串,每次统计b串的zeros和ones//去help里二分找大于等于b串ones-zeros的最左位置l//此时对答案的贡献就是a串0~l上每个位置0的个数的累加和加上b串目前0的个数乘l//再加上a串后续每个位置1的个数的累加和加上b串目前1的个数乘后续的长度//举个例子,假如help={-1,-1,0,0}//当来到b串的2位置,ones=2,zeros=1,ones-zeros=1//说明b串到这个位置1的个数比0的个数多1//所以说明a串中有四个位置补不上0比1少的个数,那么操作数就是0的个数//那么对答案的贡献就是a串中0的个数加上b串目前0的个数乘以4//再加上右侧操作数为1的个数的答案,即a串中后续1的个数加上b串目前1的个数乘以4-4=0vector<array<ll,3>>cnts(n);ll ones=0;ll zeros=0;for(int i=0;i<n;i++){if(a[i]=='0'){zeros++;}else{ones++;}cnts[i]={zeros-ones,zeros,ones};}sort(cnts.begin(),cnts.end(),[&](const array<ll,3>&x,const array<ll,3>&y){return x[0]<y[0];});vector<ll>pre0(n);vector<ll>pre1(n);pre0[0]=cnts[0][1];pre1[0]=cnts[0][2];for(int i=1;i<n;i++){pre0[i]=pre0[i-1]+cnts[i][1];pre1[i]=pre1[i-1]+cnts[i][2];}zeros=0;ones=0;ll ans=0;for(int i=0;i<n;i++){if(b[i]=='0'){zeros++;}else{ones++;}int pos=bs(ones-zeros,n,cnts);if(pos==0){ans+=pre1[n-1]+ones*n;}else{ans+=pre0[pos-1]+zeros*pos+pre1[n-1]-pre1[pos-1]+ones*(n-pos);}}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;
}
这个题上来的这个思路就想不到。根据定义,假如能从(i,j)走到(i,j+1),那么因为Ai^Bj等于0,又因为Ai^B(j+1)等于0,所以把这两个式子再异或起来可以得到Bj^B(j+1)等于0。同理可以得到Ai^A(i+1)等于0,所以如果能从(1,1)走到(i,j),那么必须有从A1到Ai,从B1到Bj,所有的数都完全一样。那么要保证能走到的最小操作数就是这些数字里,0的个数和1的个数的最小值。所以,所有操作的代价就是对于所有的二元对(i,j)的最小操作数。
之后就需要考虑如何将复杂度降下来了。考虑先遍历一遍a串,每次统计从头到当前i位置,0的个数zeros和1的个数ones,以及zeros减ones的个数。之后,根据zeros减ones从小到大排序,然后分别求整个数组里zeros和ones的前缀和。之后,遍历b串,同样统计从头到当前位置的zeros和ones。
之后是重点,举个例子,假如b串从头到当前位置的zeros为2,ones为4,那么ones减zeros就是2,表明1的个数比0的个数多2个。之后,去之前a串统计出的数组里,二分查找大于等于2的最左位置pos。那么对于这个位置pos,左侧部分都是zeros减ones小于等于2的,即0的个数和1的个数弥补不了b串当前的差距,所以此时合起来0的个数和1的个数的最小值就是0的个数。同理,这个位置pos的右侧位置都是zeros减ones大于2的,即合起来后1最小值是1的个数。所以,0的个数和1的个数可以直接从前缀和数组里查,再加上b串当前的zeros和ones乘以对应个数即可。
真的离谱这个思路……
七、G. Wafu!
这个G感觉也不过如此嘛。
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;const int MOD=1e9+7;//找小于等于的最右位置
int bs(ll x,int n,vector<ll>&cnts)
{int l=1;int r=n;int m;int ans=0;while(l<=r){m=(l+r)/2;if(cnts[m]<=x){ans=m;l=m+1;}else{r=m-1;}}return ans;
}void solve()
{ll n,k;cin>>n>>k;vector<ll>s(n+1);for(int i=1;i<=n;i++){cin>>s[i];}sort(s.begin()+1,s.end());//cnts[i]:1~i乘完的次数vector<ll>cnts(32);//mult[i]:1~i乘完vector<ll>mult(32);cnts[0]=0;mult[0]=1;for(int i=1;i<32;i++){cnts[i]=cnts[i-1]*2+1;mult[i]=((mult[i-1]*mult[i-1])%MOD*i)%MOD;}ll ans=1;int i=1;while(k>0){if(i<=n&&s[i]-1<32&&k>=1+cnts[s[i]-1])//能乘数组里的并把新增的乘完{ans=(ans*s[i])%MOD;ans=(ans*mult[s[i]-1])%MOD;k-=1+cnts[s[i]-1];i++;}else{//还能乘数组里的if(i<=n){ans=(ans*s[i])%MOD;k--;i++;}//只能乘新增的int pos=31;while(k>0){//二分找最多乘到的位置pos=bs(k,pos,cnts);ans=(ans*mult[pos])%MOD;k-=cnts[pos];//还能再乘一次if(k>0){ans=(ans*(pos+1))%MOD;k--;}}}}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;
}
首先,因为每次从集合里删除一个数后,都要把从1到这个数中间的所有数都加入集合。又因为每次都选集合中最小的数,所以每次肯定会优先处理新加入的数。那么就可以考虑用dp的思想,先预处理一个cnts数组,cnts[i]表示集合的前i个数正好为1~i,把这i个数删完需要的次数。再预处理一个mult数组,其中mult[i]还是表示集合的前i个数正好为1~i,把这i个数删完答案一共要乘的数。再观察可以发现,在删除的过程中,在删第i个数i之前,肯定要把1~i-1删完,之后才能删i。接着因为又加入了1~i-1,所以还得再删一遍1~i-1,那么就可以得到如上面写的转移公式。通过观察cnts[i]的数可以发现,cnts[i]其实就等于2^i-1。又因为k小于等于10^9,没超过int的范围,所以cnts和mult都只需要开32长度即可。又因为每次在删完集合中的数x后,都要处理1~x-1的部分,那么只有当这个x-1小于32时,才有可能把新增的全删完。
之后,在对数组从小到大排序后,只要k大于0,那么如果数组里还有数,即原本的集合没被删完,且集合中的数-1小于32,即能把新增的全删完,且k的次数比删完一轮的次数还大,那就先删集合内的数,再把1~x-1全删完。否则,说明k不够全删完的了,那如果此时集合里还有数,那就先删集合里的。之后,因为新增的数删不完,只要k还大于0,那么每次能完整删完的1~i就可以在cnts里二分查找小于等于k的最右位置,然后把1~i这一部分全删了。如果还能再删一次,那么就再把数字i+1给删了,去后续看能否接着删了。
总结
一步一步来,天道酬勤,加油!!