Codeforces Round #738 (Div. 2) 部分题题解
D2.Mocha and Diana (Hard Version)
题目链接
Mocha and Diana (Hard Version)
简要题解
我们从\(D1\)解法中已经知道了,能连边就连边,是一种合法方案。
这里无法直接枚举所有的边,所以我们考虑换一种方法。
首先还是要维护两个并查集,然后我们指定一个根,不妨设为\(1\)。
然后我们枚举第\(i\)个点,若\(i\)在两棵树中都不和根连通,那么我们将1和\(i\)连边。
若\(i\)在两棵树中都和根连通,那么它一定没有用了。
若\(i\)在某一棵树中和根连通,那么我们对两棵树分别维护一个单调栈,将\(i\)放入不连通的树对应的那个栈中。
从两个栈中任意各取一个元素连边,都是合法的,于是直接连边即可。
注意维护栈内元素的合法性,在取出来的时候检查一下即可。
该题的巧妙之处在于:利用根对点集进行了分类。
时间复杂度\(O(\alpha(n))\)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int n,As,Ans[MAXN][2];
stack<int>Sl,Sr;
int Min(int A,int B){ return A<B?A:B; }
struct SET
{ int m,Fa[MAXN];
int Find(int S){ return Fa[S]==S?S:Fa[S]=Find(Fa[S]); }
void Merge(int A,int B){ A=Find(A),B=Find(B),Fa[A]=Fa[B]=Min(A,B); }
}F1,F2;
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int main()
{ n=Read(),F1.m=Read(),F2.m=Read();
for(int i=1;i<=n;i++) F1.Fa[i]=F2.Fa[i]=i;
for(int i=1,u,v;i<=F1.m;i++) u=Read(),v=Read(),F1.Merge(u,v);
for(int i=1,u,v;i<=F2.m;i++) u=Read(),v=Read(),F2.Merge(u,v);
for(int i=2,Nl,Nr;i<=n;i++)
{ Nl=F1.Find(i),Nr=F2.Find(i);
if(Nl==1&&Nr==1) continue ;
if(Nl!=1&&Nr!=1) As++,Ans[As][0]=1,Ans[As][1]=i,F1.Merge(1,Nl),F2.Merge(1,Nr);
else Nl!=1?Sl.push(i):Sr.push(i);
}
for(int Tl,Tr;!Sl.empty()&&!Sr.empty();)
{ while(!Sl.empty()&&F1.Find(Sl.top())==1) Sl.pop();
while(!Sr.empty()&&F2.Find(Sr.top())==1) Sr.pop();
if(Sl.empty()||Sr.empty()) break ;
Tl=Sl.top(),Sl.pop(),Tr=Sr.top(),Sr.pop();
As++,Ans[As][0]=Tl,Ans[As][1]=Tr,F1.Merge(1,Tl),F2.Merge(1,Tr);
}
printf("%d\n",As);
for(int i=1;i<=As;i++) printf("%d %d\n",Ans[i][0],Ans[i][1]);
}
E.Mocha and Stars
题目链接
简要题解
前面两个限制比较好做,可以用背包解决。
关于\(gcd\)的限制,有一个直观的想法就是去枚举它。
具体地说,我们设\(F[k]\)表示\(gcd\)是\(k\)的倍数,并且满足前两个约束的方案数。
只要求出了\(F[k]\),我们就可以容斥算出\(gcd\)恰好为\(k\)的方案数。
容斥的话,从大到小处理\(F[k]\),减去\(k\)的倍数的方案即可。
考虑如何得到\(F[k]\),我们做背包,设\(G[i][j]\)表示,前\(i\)个位置,和为\(j*k\)的方案数。
直接转移即可,单次复杂度为\(O(\frac{n*m}{k})\)的,总复杂度约为\(O(n*m*logm)\)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int Mod=998244353;
const int MAXN=100010;
int n,m,Le[100],Ri[100];
int Ans[100010],F[51][100010];
int Nl[100],Nr[100];
int Min(int A,int B){ return A<B?A:B; }
void Add(int &A,int B){ A+=B,A>=Mod?A-=Mod:0; }
int Calc(int Top)
{ for(int i=0;i<=n;i++)
for(int j=0;j<=Top;j++) F[i][j]=0;
F[0][0]=1;
for(int i=1;i<=n;i++)
{ for(int j=0;j+Nl[i]<=Top;j++)
Add(F[i][j+Nl[i]],F[i-1][j]),Add(F[i][Min(Top+1,j+Nr[i]+1)],Mod-F[i-1][j]);
for(int j=0;j<=Top;j++) Add(F[i][j],F[i][j-1]);
}
int Ret=0;
for(int i=0;i<=Top;i++) Add(Ret,F[n][i]);
return Ret;
}
int main()
{ scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&Le[i],&Ri[i]);
for(int i=1;i<=m;i++)
{ for(int j=1;j<=n;j++) Nl[j]=(Le[j]-1)/i+1,Nr[j]=Ri[j]/i;
Ans[i]=Calc(m/i);
}
for(int i=m;i>=1;i--)
for(int j=2*i;j<=m;j+=i) Add(Ans[i],Mod-Ans[j]);
printf("%d\n",Ans[1]);
}