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

题目链接

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]);
}
posted @ 2021-08-25 17:01  Alkaid~  阅读(53)  评论(0编辑  收藏  举报