当前位置: 首页 > news >正文

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给删了,去后续看能否接着删了。

总结

一步一步来,天道酬勤,加油!!

END

http://www.dtcms.com/a/331578.html

相关文章:

  • 【科研日常】使用tensorflow实现自注意力机制和交叉注意力机制
  • Java中Record的应用
  • Flink Stream API 源码走读 - socketTextStream
  • Spark Shuffle机制原理
  • STM32HAL 快速入门(七):GPIO 输入之光敏传感器控制蜂鸣器
  • 深入理解管道(下):括号命令 `()`、`-ExpandProperty` 与 AD/CSV 实战
  • Java 大视界 -- Java 大数据在智能家居能耗监测与节能优化中的应用探索(396)
  • 【漏洞复现】WinRAR 目录穿越漏洞(CVE-2025-8088)
  • JavaScript 解构赋值语法详解
  • iOS Sqlite3
  • Playwright初学指南 (3):深入解析交互操作
  • 【完整源码+数据集+部署教程】肾脏病变实例分割系统源码和数据集:改进yolo11-CARAFE
  • 基于机器学习的文本情感极性分析系统设计与实现
  • 华为宣布云晰柔光屏技术迎来重大升级
  • 生产环境sudo配置详细指南
  • 机器学习学习总结
  • 如何选择适合工业场景的物联网网关?
  • 相较于传统AR作战环境虚拟仿真系统,其优势体现在哪些方面?
  • Python小程序1.0版本
  • C++类与对象核心知识点全解析(中)【六大默认成员函数详解】
  • Perforce P4 Git 连接器
  • 随身 Linux 开发环境:使用 cpolar 内网穿透服务实现 VSCode 远程访问
  • Activity + fragment的页面结构,fragment始终无法显示问题
  • AI 赋能的软件工程全生命周期应用
  • 第16届蓝桥杯C++中高级选拔赛(STEMA)2024年10月20日真题
  • 【C#】PNG 和 JPG、JPEG的应用以及三种格式的区别?
  • Oracle commit之后做了什么
  • 【20-模型诊断调优】
  • BSCI认证对企业的影响,BSCI认证的重要性,BSCI审核的核心内容
  • 信息vs知识:人类学习与AI规则提取