JZOJ 5700. 【gdoi2018 day1】小学生图论题(graph)

本文介绍了一种解决竞赛图分割问题的算法,利用强连通分量与拓扑图特性,通过构造多项式和使用分治NTT来计算分割期望数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Description

Input

Input

Output

Output

Sample Input

Sample Input1

10 2
2 1 3
3 7 8 9

Sample Input2

3 0

Sample Output

Sample Output1

462789157

Sample Output2

499122179

Data Constraint

Data Constraint

Solution

  • 省选前做过一道极其类似的原题,是MYY出的,但是我当时没搞懂,好亏啊!!!

  • 我们将强连通分量缩成点后,就会形成类似一条链、前往后有连边的拓扑图。如下图:
    Solution

  • 链上的一条边就是一个分割,分成前后两个点集 S,TS,T ,边都是从 SS 连向 T 的。

  • 那么把点集对应到原竞赛图中,点可以分成两个点集 S,TS,T ,且边都是从 SS 连向 T 的,就说明缩完点后对应一个分割。

  • 我们只要求出期望的分割数即可,强连通分量个数就是分割数+1。

  • 答案可以写成:

    1+S,TxS,yTPx,y1+∑S,T∏x∈S,y∈TPx,y
  • 由题知,边的概率 Px,y{0,12,1}Px,y∈{0,12,1}

  • 于是当 m=0m=0 时,我们很容易得到答案:

    Ans=1+i=1n112i(ni)CinAns=1+∑i=1n−112i∗(n−i)∗Cni
  • 其中 ii 枚举的是 S 的大小,此时 SST 的边方向确定。

  • 如果 m0m≠0 呢?考虑题目给出的每一条链,显然点的编号是与答案无关的。

  • 链可以分成前后两段,前一段属于 SS ,后一段属于 T

  • 对于一个长度为 kk 的链,我们尝试构造一个 k 阶多项式 G(x)G(x)xixi

  • 如果有的点不在题目给出的链中,那么就各自当成独立的一个长度为 11 的链。

  • 如果链上的点同在 S 集或 TT 集中,那么使该项系数为 1 ,即 00 次项和 k 次项系数为 11

  • 否则的话,肯定有一条边的方向已经确定了,于是概率应少乘一个 12

  • 那么第 11 次项到第 k1 次项的系数就是 22

  • 于是对于每条链,我们都构造出了一个多项式:

    G(x)=1+2x+2x2+···+2xk1+xk

  • 用分治NTT将所有多项式都乘起来,时间复杂度 O(N log2N)O(N log2N)

  • 令所有 G(x)G(x) 乘起来的多项式为 F(x)F(x) ,则最后答案即为:

    Ans=1+i=1n112i(ni)F(x)[xi]Ans=1+∑i=1n−112i(n−i)∗F(x)[xi]

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
typedef long long LL;
const int N=1e5+5,G=3,mo=998244353;
int a[N<<1],beg[N],end[N];
int w[N<<2],rev[N<<2],b[N<<2],c[N<<2];
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
inline int ksm(int x,LL y)
{
    int s=1;
    while(y)
    {
        if(y&1) s=(LL)s*x%mo;
        x=(LL)x*x%mo;
        y>>=1;
    }
    return s;
}
inline void NTT(int *y,int n,int ff)
{
    for(int i=0;i<n;i++)
        if(i<rev[i]) swap(y[i],y[rev[i]]);
    for(int m=2;m<=n;m<<=1)
    {
        int h=m>>1;
        for(int i=0;i<h;i++)
        {
            int wn=ff==-1?w[n-i*(n/m)]:w[i*(n/m)];
            for(int j=i;j<n;j+=m)
            {
                int u=y[j],v=(LL)wn*y[j+h]%mo;
                y[j]=(u+v)%mo;
                y[j+h]=(u-v+mo)%mo;
            }
        }
    }
    if(ff==-1)
    {
        int inv=ksm(n,mo-2);
        for(int i=0;i<n;i++) y[i]=(LL)y[i]*inv%mo;
    }
}
void solve(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    solve(l,mid);
    solve(mid+1,r);
    int ln=end[l]-beg[l]+1,lm=end[mid+1]-beg[mid+1]+1;
    int len=1,ll=0;
    while(len<=ln+lm+1) len<<=1,ll++;
    for(int i=0;i<len;i++) rev[i]=rev[i>>1]>>1|(i&1)<<ll-1;
    for(int i=0;i<ln;i++) b[i]=a[beg[l]+i];
    for(int i=ln;i<=len;i++) b[i]=0;
    for(int i=0;i<lm;i++) c[i]=a[beg[mid+1]+i];
    for(int i=lm;i<=len;i++) c[i]=0;
    int num=ksm(G,(mo-1)/len);
    for(int i=w[0]=1;i<=len;i++) w[i]=(LL)w[i-1]*num%mo;
    NTT(b,len,1),NTT(c,len,1);
    for(int i=0;i<len;i++) b[i]=(LL)b[i]*c[i]%mo;
    NTT(b,len,-1);
    while(len && !b[len-1]) len--;
    end[l]=beg[l]+len-1;
    for(int i=beg[l];i<=end[l];i++) a[i]=b[i-beg[l]];
}
int main()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    int n=read(),m=read(),tot=0;
    for(int i=1;i<=m;i++)
    {
        int k=read();
        tot+=k;
        beg[i]=end[i-1]+1;
        end[i]=beg[i]+k;
        a[beg[i]]=a[end[i]]=1;
        for(int j=beg[i]+1;j<end[i];j++) a[j]=2;
        while(k--) read();
    }
    for(int i=tot+1;i<=n;i++)
    {
        m++;
        beg[m]=end[m-1]+1;
        end[m]=beg[m]+1;
        a[beg[m]]=a[end[m]]=1;
    }
    solve(1,m);
    int ans=1;
    for(int i=1;i<n;i++) ans=(ans+(LL)a[i+beg[1]]*ksm((mo+1)>>1,(LL)i*(n-i)))%mo;
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值