近期codeforces做题的总结?(不定期更新)
高考考完了,考上带学了,好耶(
开始尝试做一些题复建,这里是持续更新的近期做题记录
Educational Codeforces Round 106 (Rated for Div. 2)
D
题意:给定c,d,x
求满足c∗lcm(a,b)−d∗gcd(a,b)=x的对数考虑令a=A∗gcd(a,b),b=B∗gcd(a,b)(重点,把枚举的a,b从(a,b)=t转化成(A,B)=1的A和B)
稍微整理一下就会发现
原式的形式是c∗A∗B∗gcd(a,b)−d∗gcd(a,b)=x
(c∗A∗B−d)∗gcd(a,b)=x
考虑枚举x的因数(即枚举gcd(a,b)),令其为y
则y+d需要是c的倍数
接下来考虑在已知A∗B的情况下如何求解A和B对应的组数
发现因为(A,B)=1
考虑A∗B的质因数,如果A选了一个质因数,B就不能选这个质因数。
也就是说答案是∑1<=i<=mCim 也就是2m
现在问题是如何快速统计每个数字的因数个数
这里可以用线性筛先对于每个数字求解一个minp,表示该数字的最小质因数。
则dx=dxminp+[xminp==minp]
然后就做完了。
O(n)
Code:
#include <bits/stdc++.h> using namespace std; int T,c,d,x,Ans; const int N=30000005; bool IsPrime[30000007]; int Prime[30000007],Index,sp[30000007],val[30000007]; void Pre(){ for (int i=2;i<=N;i++){ if (!IsPrime[i]) Prime[++Index]=i,sp[i]=i; for (int j=1;j<=Index&&1ll*i*Prime[j]<=N;j++){ IsPrime[i*Prime[j]]=true;sp[i*Prime[j]]=Prime[j]; if (i%Prime[j]==0) break; } } } int main(){ sp[1]=1; Pre(); for (int i=2;i<=N;i++){ if (sp[i/sp[i]]!=sp[i]) val[i]=val[i/sp[i]]+1; else val[i]=val[i/sp[i]]; } scanf("%d",&T); while (T--){ scanf("%d%d%d",&c,&d,&x); Ans=0; for (int i=1;i*i<=x;i++) if (x%i==0){ int res=x/i; int Now=i; res+=d;Now+=d; if (Now%c==0) {Ans+=(1<<val[Now/c]);} if (res%c==0 && 1ll*i*i!=x) {Ans+=(1<<val[res/c]);} } printf("%d\n",Ans); } return 0; }
E
题目链接:http://codeforces.com/contest/1499/problem/E
由于每个位置是否能填入某个字符只和它的上一位有关
于是我们就考虑设计一个dp
dpi,j,k表示a串考虑了前i位,b串考虑了前j位,k=0/1表示当前最后一位是a串或者b串的。
显然转移有六种,上一位是b串,放一个a串的;上一位是b串,放一个b串的;上一位是a串,放一个a串的;上一位是a串,放一个b串的。
还有在这个位置开新a和新b
这六种转移都在代码里有体现。
但是这题还没做完
这个地方我们唯一没考虑的不合法情况是一个有东西的串配上一个空串的情况
于是我们采用一个双指针进行扫描,寻找a和b中有多少不同的子串,把不合法的答案减掉即可。
总效率O(|a||b|)
Code:
#include <bits/stdc++.h> using namespace std; const int fish=998244353; int dp[1005][1005][2]; int ans; string s1,s2; int main(){ cin>>s1; cin>>s2; int Len1=s1.length(); int Len2=s2.length(); for (int i=0;i<=Len1;i++) for (int j=0;j<=Len2;j++){ if (i<Len1) dp[i+1][j][0]++; if (j<Len2) dp[i][j+1][1]++; if (0<i&&i<Len1&&s1[i-1]!=s1[i]) (dp[i+1][j][0]+=dp[i][j][0])%=fish; if (0<j&&i<Len1&&s1[i]!=s2[j-1]) (dp[i+1][j][0]+=dp[i][j][1])%=fish; if (0<j&&j<Len2&&s2[j-1]!=s2[j]) (dp[i][j+1][1]+=dp[i][j][1])%=fish; if (0<i&&j<Len2&&s2[j]!=s1[i-1]) (dp[i][j+1][1]+=dp[i][j][0])%=fish; (ans+=dp[i][j][0])%=fish; (ans+=dp[i][j][1])%=fish; } for (int l=0;l<Len1;l++){ int r=l; while (r+1<Len1 && s1[r]!=s1[r+1]) r++; int Len=r-l+1; int val=(1ll*Len*1ll*(Len+1)/2)%fish*1ll*(Len2+1)%fish; ans=(1ll*(ans-val)+fish)%fish; //cout<<val<<" "<<ans<<endl; l=r; } for (int l=0;l<Len2;l++){ int r=l; while (r+1<Len2 && s2[r]!=s2[r+1]) r++; int Len=r-l+1; int val=(1ll*Len*1ll*(Len+1)/2)%fish*1ll*(Len1+1)%fish; ans=(1ll*(ans-val)+fish)%fish; l=r; } cout<<ans<<endl; return 0; }
Codeforces Round #700 (Div. 2)
D
链接:http://codeforces.com/contest/1480/problem/D2
题目讲的好绕
其实题目的意思是把a数列分成两个子数列,这两个子数列中,相邻的相同项可以进行合并,问最终最大的合并次数。
考虑贪心。
当前a序列和b序列,如果有元素和这个要放入的元素相同的话,则直接放入即可。
如果都不同的话,则放入下一个相同数字距离当前位置较远的那个序列里去。
实现的话,类似于序列自动机,引入一个nxt数组记录下一个与这个位置相同的位置在哪,O(n)逆向扫描更新即可。
#include <bits/stdc++.h> using namespace std; int nxt1,nxt2,ans; int dp[100005],b[100005],a[100005],nxt[100005]; int N; int main(){ scanf("%d",&N); for (int i=1;i<=N;i++){ b[i]=N+1; nxt[i]=N+1; scanf("%d",&a[i]); } b[0]=N+1; nxt[0]=N+1; for (int i=N;i>=1;i--){ nxt[i]=b[a[i]]; b[a[i]]=i; } int x,y; x=0;y=0; for (int i=1;i<=N;i++){ if (a[x]==a[i]) {x=i;} else if (a[y]==a[i]) {y=i;} else if (nxt[x]>nxt[y]) {ans++;x=i;} else {ans++;y=i;} //cout<<x<<" "<<y<<endl; } cout<<ans; return 0; }
Codeforces Round #706 (Div. 2)
C
链接:http://codeforces.com/contest/1496/problem/C
直观理解一下,应该是y轴大的点配x轴大的点,x轴小的点配y轴小的点
xjb证明一下(
考虑四个点,其中两个点在y轴上,两个点在x轴上。
设它们的数值分别为a,b,c,d
其中a<b,c<d
如果a配c,b配d产生的贡献是
dis1=√a2+c2+√b2+d2
如果a配d,b配c产生的贡献是
dis2=√a2+d2+√b2+c2
考虑dis22−dis21的结果
发现化简完是(a−b)(c−d),显然这个式子是大于0的
于是发现dis2是比dis1要大的
于是选择dis1更优。
O(nlogn)
Code
#include <bits/stdc++.h> using namespace std; int x[100005],y[100005],N,T; double dis(int a,int b){ return (sqrt(1ll*a*1ll*a+1ll*b*1ll*b)); } int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); int cnt1=0,cnt2=0; for (int i=1;i<=2*N;i++){ int u,v; scanf("%d%d",&u,&v); if (u<0) u=abs(u); if (v<0) v=abs(v); if (u==0) y[++cnt1]=v; else x[++cnt2]=u; } sort(y+1,y+N+1); sort(x+1,x+N+1); double ans=0; for (int i=1;i<=N;i++) ans=ans+dis(x[i],y[i]); printf("%.10lf\n",ans); } return 0; }
Codeforces Round #722 (Div. 2)
D
链接:http://codeforces.com/contest/1529
定义dpi表示2i组点的符合答案的方案数。
考虑一个x点,它和1点配对。
如果x>n,剩下x−n−1对没配的,答案就是x−n−1
如果x<n,则线段必定是n的因数。
发现其实最后答案等价于
dpi=∑dpj+D(i)
D(i)预处理即可。
#include <bits/stdc++.h> using namespace std; const int fish=998244353; int N; int dp[1000005]; int main(){ scanf("%d",&N); for (int i=1;i<=N;i++) for (int j=i+i;j<=N;j+=i) dp[j]++; int Sum=1; dp[0]=1; for (int i=1;i<=N;i++){ dp[i]=(dp[i]+Sum)%fish; (Sum+=dp[i])%=fish; } cout<<dp[N]; return 0; }
Codeforces Round #726
D
链接:http://codeforces.com/contest/1537/problem/D
当前是一个数字N,考虑一个因数D
选完这个数字后,剩下的数字就是N−D
此时若D是一个质因数的话,则之后就会发现因为N−D≡0(modM)
则有M|N,M|D
于是发现因为D是一个质因数,则之后只能继续取D
如果N是一个奇数的话
发现能取ND,这必然是一个奇数。
于是Bob获胜
再考虑一个偶数的情况
同理可以发现这时候Alice必胜
但是我们需要考虑一个特殊情况(我没想到,呜呜)
如果N是一个2k的形式的话
如果我取一个数字,让它变成了不是2k的形式,我们就会发现它一定是个偶数,且有一个质因子。
而我们上面论证过,该情况是先手获胜
于是就可以发现两人都会尽量的避免这种情况。
那么要如何避免呢?
我要再次构造一个2k的形式。
于是两个人就会不断地轮流构造这样的2m的形式,谁先构造不出来谁就输了。
于是就算一下这个k然后考虑奇偶性即可。
O(TlogN)
#include <bits/stdc++.h> using namespace std; int T,N; const int a[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912}; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); if (N%2){ printf("Bob\n"); continue; } bool pd=false; for (int i=1;i<30;i++) if (N==a[i]){ if (i%2==0) printf("Alice\n"); else printf("Bob\n"); pd=true; break; } if (pd) continue; printf("Alice\n"); } }
Codeforces Round #735 (Div. 2)
D
构造题,我构造好菜,呜呜
奇数+奇数=偶数,奇数+偶数=奇数
于是我们就可以构造这种东西
aaaa....aaa([n2]个a)baa....aaaa([n2]+1个a)
这是对于给定的N是偶数来说的
如果是奇数,往中间多插一个c就行。
#include <bits/stdc++.h> using namespace std; int N; int T; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); int K; N--; if (N%2==1) K=(N+1)/2; else K=N/2; for (int i=1;i<=K;i++) printf("a"); printf("b"); if (N==0){ printf("\n"); continue; } if (N%2==0) {printf("c");N--;} for (int i=1;i<=N-K;i++) printf("a"); printf("\n"); } return 0; }
Codeforces Round #694 (Div. 2)
D
先考虑没有操作的时候这个答案怎么算
lcm(x,y)gcd(x,y)=xygcd2(x,y)为完全平方数
其实也就是xy是一个完全平方数
再换句话说,就是每一个相同的质因数的次方数的奇偶性是一样的。
稍微哈希一下,找个最大值就行了。
接下来考虑引入操作带来的影响。
可以发现的是,这个操作对于一个个数是奇数的组是没有影响的
对于一个个数是偶数的组,可以发现在做完一次操作之后,所有个数为偶数的组自身会形成完全平方数,也就是说可以被扔进一个新产生的组里。
显然,这个结论对于1也成立
于是我们设置一个Ans计算一下这个新产生的组,并且和原本的ans进行一下大小比较就可以了。
O(nlogn)
#include <bits/stdc++.h> using namespace std; int T,N,a[1000005],q,w,Index,c[1000005],Ans,ans1,Prime[1000005],sp[1000005]; bool IsPrime[1000005]; map<int,int> Count; void Pre(){ for (int i=2;i<=1000000;i++){ if (!IsPrime[i]) Prime[++Index]=i,sp[i]=i; for (int j=1;j<=Index&&1ll*i*Prime[j]<=1000000;j++){ IsPrime[i*Prime[j]]=true;sp[i*Prime[j]]=Prime[j]; if (i%Prime[j]==0) break; } } } void Solve(){ for (int i=1;i<=N;i++){ int Hash=1; int x=a[i],cnt=0; while (x>1){ cnt=0; int nw=sp[x]; while (x%nw==0){ cnt++; x/=nw; } if (cnt%2==1) Hash=Hash*nw; } Count[Hash]++; ans1=max(ans1,Count[Hash]); a[i]=Hash; } for (int i=1;i<=N;i++) if (a[i]==1||!(Count[a[i]]&1)) Ans+=Count[a[i]],Count[a[i]]=0; for (int i=1;i<=N;i++) Count[a[i]]=0; } int main(){ Pre(); scanf("%d",&T); while (T--){ ans1=Ans=0; scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%d",&a[i]); scanf("%d",&q); Solve(); for (int i=1;i<=q;i++){ long long w; scanf("%lld",&w); if (w==0) printf("%d\n",ans1); else printf("%d\n",max(ans1,Ans)); } } }
Codeforces Round #727 (Div. 2)
D
链接:http://codeforces.com/contest/1539/problem/D
贪心。
先把所有物品按照bi从小到大排序。
发现其实吧,bi在越前面的是越容易达到打折的条件的。
然后bi在后面的比较难达到打折条件的。
于是我们可以得到这些结论:
1.如果当前已经买到了bi,直接买就行了
2.如果当前不够bi,我们就考虑从bi最大的那些买,这不会使答案更劣。
于是就可以用一个双指针来实现这个贪心。
O(n)
#include <bits/stdc++.h> using namespace std; struct Node{ long long a,b; }Items[100005]; int pd(Node a,Node b){ return a.b<b.b; } int N; int main(){ scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%lld%lld",&Items[i].a,&Items[i].b); sort(Items+1,Items+N+1,pd); long long l=1,r=N,Now=0,ans=0; while (l<=r){ if (Items[l].b<=Now){ ans+=Items[l].a; Now+=Items[l].a; l++; } else{ if (Items[r].a>=Items[l].b-Now){ ans+=(Items[l].b-Now)*2; Items[r].a-=(Items[l].b-Now); Now+=(Items[l].b-Now); } else{ ans+=Items[r].a*2; Now+=Items[r].a; r--; } } } cout<<ans; return 0; }
Codeforces Round #732 (Div. 2)
D
链接:http://codeforces.com/contests
是跳棋,好耶!
显然有下面两个事实成立。
1.一组11可以不断想左/向右跳动。
2.两组11之间的相对顺序不会调换
于是其实这个问题就变成了
有k组11和一些0,问能形成的序列方案。
隔个板就可以了。
#include <bits/stdc++.h> using namespace std; const int fish=998244353; int T,N,mo,mz; bool vis[100010]; int fac[100010],inv[100010]; string ss; int Pow(int x,int y){ int ans=1; for (int i=y;i;i>>=1){ if (i&1) ans=(1ll*ans*x)%fish; x=1ll*x*x%fish; } return ans; } int C(int n,int r){ if (n<r) return 1; return 1ll*fac[n]*inv[r]%fish*inv[n-r]%fish; } int main(){ fac[0]=1; //cout<<Pow(2,5)<<endl; for (int i=1;i<=100004;i++) fac[i]=(1ll*fac[i-1]*i)%fish; inv[100004]=Pow(fac[100004],fish-2); for (int i=100003;i>=1;i--) inv[i]=1ll*inv[i+1]*(i+1)%fish; inv[0]=1; scanf("%d",&T); while (T--){ scanf("%d",&N); cin>>ss; memset(vis,false,sizeof(vis)); mo=0,mz=0; for (int i=0;i<N;i++){ if (ss[i]=='1'&&ss[i+1]=='1'&&!vis[i]) { vis[i]=true;vis[i+1]=true; mo++; } if (ss[i]=='0') mz++; } //cout<<mo<<" "<<mz<<endl; printf("%d\n",C(mz+mo,mo)); } return 0; }
Codeforces Round #736 (Div. 2)
D
链接:http://codeforces.com/contest/1549
把连续的同余转化成相邻项差在模意义下为0
模意义下为0意味着整除
构造一个新数列bn,它存储相邻项差。
于是这个问题就变成:最长的连续gcd不为1
我们可以用一个线段树来统计区间的gcd
然后用一个双指针来实现这个统计最值的操作。
#include <bits/stdc++.h> using namespace std; int N;long long a[200005],T,b[200005],Tree[1000005]; long long Build(int Now,int l,int r){ if (l==r) {Tree[Now]=a[l];return Tree[Now];} int mid=(l+r)>>1; Build(Now<<1,l,mid);Build(Now<<1|1,mid+1,r); return (Tree[Now]=__gcd(Tree[Now<<1],Tree[Now<<1|1])); } long long query(int Now,int l,int r,int L,int R){ //cout<<Now<<endl; if (L<=l&&r<=R) return Tree[Now]; int mid=(l+r)>>1; if (L<=mid && mid<R) return __gcd(query(Now<<1,l,mid,L,R),query(Now<<1|1,mid+1,r,L,R)); else if (L<=mid) return query(Now<<1,l,mid,L,R); else if (mid<R) return query(Now<<1|1,mid+1,r,L,R); } int main(){ scanf("%d",&T); int l,r,ans; while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%lld",&b[i]); for (int i=1;i<=N-1;i++) a[i]=abs(b[i+1]-b[i]); N--; if (N==0){ printf("1\n"); continue; } Build(1,1,N); l=1,r=1,ans=0; for (;r<=N;r++){ while (query(1,1,N,l,r)==1&&l<r) l++; if (l==r&&a[l]!=1) ans=max(ans,2); else if (l==r) ans=max(ans,1); else ans=max(ans,r-l+2); } if (ans==0) printf("1\n");else printf("%d\n",ans); } return 0; }
Codeforces Round #741 (Div. 2)
D
链接:http://codeforces.com/contest/1562
题目为:删除l,r中的多少节点,可以使sum[r]=sum[l]
观察发现 答案只有0,1,2
考虑一个节点的删去会带来哪些影响
也就是:该节点后续的 +1 变成 -1,-1 变成 +1
一个+1 变成 -1 对前缀和的影响是使得前缀和-2
一个-1 变成 +1 对前缀和的影响是使得前缀和+2
也就是说 其实这个位置的修改对答案的影响是:
abs(1的数量- -1的数量)*系数*2
其中系数取决于1比0多还是0比1多。
而这个 1的数量 - -1的数量
其实就是sum[r]-sum[x],x是我们选定的一个位置。
因为我们删去了一个节点,所以前缀和会相应的+1/-1
当sum[r] == sum[l]的时候,显然我们并不需要删去任何节点。
当sum[r] != sum[l]时,我们进行分类讨论:
找到一个位置,使得sum[r]-2*sum[x]+1=sum[l]
找到一个点:
sum[r]-sum[x]+1=sum[x]-sum[l]
也就是找到一个点,使得两段和恰好差1
如果这两段的差是一个奇数,那么显然这个点是可以被找到的,因为它是连续的
如果两段的差是一个偶数的,那么我们可以先随便删除r这个点,使得两个点的差变为奇数,然后再找到一个这样的点。
所以答案只有0,1,2。
答案的位置二分去找就行了。
#include <bits/stdc++.h> using namespace std; int N,T,Q,sum[300005],nw,xs; char c; int main(){ scanf("%d",&T); while (T--){ scanf("%d%d",&N,&Q); for (int i=0;i<N;i++){ cin>>c; if (c=='+') nw=1;else nw=-1; if ((i+1)%2==1) xs=1;else xs=-1; sum[i+1]=sum[i]+xs*nw; } for (int i=1;i<=Q;i++){ int l,r; scanf("%d%d",&l,&r); l--,r--; if (sum[l]==sum[r+1]){ printf("0\n"); continue; } if (abs(sum[r+1]-sum[l])%2==1) printf("1\n"); else{ printf("2\n"); printf("%d ",r+1); r--; } int L=l,R=r+1; int nd=min(sum[l],sum[r+1])+abs(sum[r+1]-sum[l])/2; while (R>L+1){ int mid=(L+R)/2; if ((sum[mid]<=nd)==(sum[L]<=nd)) L=mid; else R=mid; } printf("%d\n",L+1); } } return 0; }
Codeforces Round #701 (Div. 2)
D
链接:http://codeforces.com/contest/1485/problem/D
构造题,没想出来,我tcl(
发现ai,j的大小限制是16
发现lcm(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)=720720
取720720可以对任意的a都满足条件2。
对于条件3,我们交替的填入a4i,j即可。
(虽然但是我连720720都没想到,呜呜)
#include <bits/stdc++.h> using namespace std; int N,M; int main(){ scanf("%d%d",&N,&M); for (int i=0;i<N;i++){ for (int j=0;j<M;j++){ int x; scanf("%d",&x); if (!((i+j)&1)) printf("720720 "); else printf("%d ",720720+x*x*x*x); } printf("\n"); } return 0; }
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 开发者新选择:用DeepSeek实现Cursor级智能编程的免费方案
· Tinyfox 发生重大改版
· 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
· 小米CR6606,CR6608,CR6609 启用SSH和刷入OpenWRT 23.05.5
· 近期最值得关注的AI技术报告与Agent综述!