Codeforces Round #814 (Div. 2)(补题中)
战绩:
有铁头娃
A. Chip Game
猜了个结论,第一次猜的是n==m,第二次猜的是n+m的奇偶性。
严格证明也比较简单。
由于只能向右向上,我们每次移动相当于缩减问题规模。
那么n和m两个都为奇数或者偶数的情况下,能够移动到一奇一偶的情况,而一奇一偶的情况也能移动到两个都为奇数或者偶数的状态。
最后移动到(1,1)的为赢家因此先手一奇一偶就赢了。
B. Mathematical Circus
取模之后答案大于4的k,对于答案只有k%4的影响。
按照这个思路我们发现,只有四种情况。
并且,样例全列出来了。。。
输出出来即可
C. Fighting Tournament
如果一个人前面有比它大的数字,这个人一定没法赢。
如果一个人的数字是最大的,那么只要轮到它它就能一直赢。
对于其他的情况,他们能赢的局数是轮到他的那一局直到它和后面第一个比它大的接触为止,这个局数是它的上限。而如果局数在这期间用完了,那么当前统计的答案就是结果。
如此模拟就可以。

#include<iostream> #include<cstdio> #include<string> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<ctime> #include<stack> #define N 150000 #define ll long long using namespace std; ll n,T,m; ll a[N]; bool winn[N]; ll b[N],tp; stack<int> s; //��� inline void read(ll &p) { p=0;ll f=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9') p=p*10+(ch-'0'),ch=getchar(); p*=f; } int main() { read(T); while(T--) { ll maxx=-1; read(n);read(m); while(!s.empty()) s.pop(); for(int i=1;i<=n;i++) //ǰ���Ƿ��б������ { read(a[i]); b[i]=0; winn[i]=1; if(a[i]>maxx) maxx=a[i],tp=i; else winn[i]=0; } while(!s.empty()) s.pop(); //�����һ��������� for(int i=n;i>=1;i--) { while(!s.empty()) { if(a[i]>a[s.top()]) s.pop(); else break; } if(!s.empty()) b[i]=s.top(); s.push(i); } while(m--) { ll l,k; read(l);read(k); if(!winn[l]) cout<<0<<endl; else { if(l==tp) printf("%lld\n",max(1ll*0,k-max(1ll*0,l-2))); else { if(k<(l-1)) cout<<0<<endl; else { ll ans=(b[l]-1-l); //��������Ӯ if(l==1) printf("%lld\n",min(ans,k)); else { ans++; k-=(l-1); if(k<0) cout<<0<<endl; //û�ֵ� else if(k==0) cout<<1<<endl; //�ոյ� else { if(k>=(b[l]-1-l)) cout<<ans<<endl; else cout<<k+1<<endl; } } } } } } } return 0; }
但是实际上这样子细节比较多,导致我的答案一直在WA
我们可以率先把第一大的位置移动到最前面,记录需要的局数,和这期间每个人赢得次数,然后这之后的答案都只有最大的会变。判断预处理局数和已用局数的大小关系即可。
D1. Burenka and Traditions (easy version)
我们发现一些事实:
1.选取一次三个以上区间和我们选取两个+一个组成的等长区间所消耗的花费是一样的。
也就是说我们最多变换的时候选取长度为2的区间。
2.有一种方式,每一位都异或自己变为0,那么答案上限就是n。
3.如果我们每次都连续选择两位进行异或那么能够优化的方式就是异或两个数字中的第一个数,将其变为0,然后第二个数字就会变为a1^a2,当a1^a2不为0的时候,我们实际上和一个一个异或自己相比没有答案上的优化,因此我们做完连续两位的异或操作时,一定要恰好保证答案都变为0,此时相比一位一位做,答案会少1.
也就是说,如果有一段区间[l,r],使得al^al+1^al+2^...^ar==0,我们就能对这段区间进行优化。
然后利用贪心的思想,能优化的时候就进行优化,一定不会比等另一个区间终点位置在后面的方式劣。
这个贪心思想是杰哥给的启发。
我们n^2枚举寻找区间,统计答案即可。

void ac() { int n; cin >> n; vector<int> inp(n + 1); finc(i, 1, n + 1) cin >> inp[i]; int index = 1, ans = 0; finc(i, 1, n + 1) { int key = 0; fdec(j, index, i + 1) { key ^= inp[j]; if (key == 0) { ans += i - index, index = i + 1; break; } } } ans += n - index + 1; cout << ans << endl; }
D2. Burenka and Traditions (hard version)(位运算性质+贪心)
数据范围增大了,我们怎么处理?
显然此时枚举区间是不太划算的。
我们可以发现一点,一个区间包含一个两两相消的小区间的时候只会对答案产生1的影响,实际上我们只用知道以这个位置结尾的时候,前面出现过这样的小区间就行了,具体在哪里出现的,什么时候出现的,我们并不在意,只用在答案上优化一个-1即可。
因此我们可以用map来优化,每次记录当前前缀和,只要记录这个数字在前面一段出现过,然后答案累计一个-1,然后继续向后。

int main() { read(T); while(T--) { mp.clear(); read(n); ll sum=0,ans=n; mp[0]=1; for(int i=1;i<=n;i++) { read(a[i]); if(mp[sum^a[i]]!=0) { sum=0; mp.clear(); mp[0]=1; ans--; continue; } else mp[sum^a[i]]=1; sum^=a[i]; } printf("%lld\n",ans); } return 0; }
当然你也可以使用字典树来进行优化,复杂度大概是O(30n),实际上跑起来飞快
杰哥的代码:

#define finc(i,a,b) for(int i=(int)(a);i<(int)(b);i++) #define fdec(i,a,b) for(int i=(int)(b);i-->(int)(a);) #define endl '\n' #define endb ' ' #define read(a,str) scanf("%"#str,&a) class TireTree { static const int bit = 30; private: struct Node { int next[2]; Node() { next[0] = next[1] = 0; } int& operator[](int i) { return next[i]; } }; vector<Node> tree; int note; public: TireTree() { tree.assign(2, {}); note = 0; } void clear() { tree.assign(2, {}); note = 0; } void insert(int val) { int now = 1; fdec(i, 0, bit) { int key = ((val ^ note) >> i) & 1; if (tree[now][key] == 0) tree[now][key] = tree.size(), tree.push_back({}); now = tree[now][key]; } } bool find(int val) { if (val == 0) return 1; int now = 1; fdec(i, 0, bit) { int key = ((val ^ note) >> i) & 1; if (tree[now][key] == 0) return 0; now = tree[now][key]; } return 1; } void change(int val) { note ^= val; } }; void ac() { int n; cin >> n; vector<int> inp(n + 1); finc(i, 1, n + 1) cin >> inp[i]; TireTree kaze; int index = 1, ans = 0; finc(i, 1, n + 1) { if (kaze.find(inp[i])) { ans += i - index, index = i + 1; kaze.clear(); continue; } kaze.change(inp[i]); kaze.insert(inp[i]); } ans += n - index + 1; cout << ans << endl; }
或者也可以写成DP的形式,每次从发现的最晚的位置转移过来,答案是最佳的。

int main() { read(T); while(T--) { read(n); for(int i=1;i<=n;i++) { dp[i]=999999999; sum[i]=0; read(a[i]); } mp.clear(); for(int i=1;i<=n;i++) { sum[i]=sum[i-1]^a[i]; if(a[i]==0) {dp[i]=dp[i-1];mp[sum[i]]=i;continue;} dp[i]=dp[i-1]+1; if(sum[i]==0) { dp[i]=min(dp[i],dp[mp[sum[i]]]+(i-mp[sum[i]]-1)); mp[0]=i; } else { if(mp[sum[i]]!=0) dp[i]=min(dp[i],dp[mp[sum[i]]]+(i-mp[sum[i]]-1)); mp[sum[i]]=i; } } cout<<dp[n]<<endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下