• [loj 6496]「雅礼集训 2018 Day1」仙人掌


    传送门

    Description

    给出一张 (n)个点 (m)条边的无向连通图,其中每条边至多属于一个简单环,保证没有自环,可能有重边。你需要为其中每条边定向,其中第 (i)个点的出度不能超过(a_i) ,求方案数(对(998244353)取模)。

    Solution

    建出原图的圆方树,其中两个点和一条边组成的不算做一个双联通分量,圆方树的的根是(1)

    定义(f[x][i])表示,第(x)个点,它上面的点给它的入度为(i),以(x)为根的子树内给边定向的方案数。

    更清楚一点:

    1. 如果(x)是一个圆点,且它的父亲也是圆点

      (f[x][0/1])即表示它的父亲节点与它之间的边是否是指向(x)

    2. 如果(x)是一个方点,且它的父亲是圆点

      (f[x][0/1/2])即表示这个环的最上面的两条边(就是于(fa)直接相连的那两条),不指向(fa)的数量((0/1/2))

    3. 如果(x)是一个圆点,且它的父亲是方点

      (f[x][0/1/2])表示的就是它所在的环上与它直接相邻的两条边,指向它自己的边的数量((0/1/2))

    (dfs)时考虑分情况转移:

    1. (x)是圆点,显然有:

      [f[x][0]=sum_{k_1+K_2+...+k_pleq a[x]}prod_{v_i} f[v_i][Sum_{v_i}-k_i] ]

      其中,如果(v_i)是方点,(Sum_{v_i}=2),如果(v_i)是圆点,(Sum_{v_i}=1)

      因为是一个背包的形式,可以直接利用(NTT)加速

    2. (x)是方点,设(x)的父亲节点是(y)

      枚举环上与(y)相连的一条边的方向,然后顺着环递推即可

    最后,在进行分治(NTT)时,每次都把两个数组全部清空复杂度是不对的

    每次做完后,把当前的数组有修改的地方清为(0)就行

    代码写得真的很清楚啦


    Code 

    #include<bits/stdc++.h>
    #define min(a,b) ((a)<(b)?(a):(b))
    namespace IO
    {
        const int lim=(1<<20)+5;
        char buf[lim+5],*S,*T;
        inline char gc(){if(S==T){T=(S=buf)+fread(buf,1,lim,stdin);if(S==T)return EOF;}return *S++;}
        inline int read()
        {
            int x;char ch;bool f;
            for(f=0;(ch=gc())<'0'||ch>'9';f=ch=='-');
            for(x=ch^'0';(ch=gc())>='0'&&ch<='9';x=(x<<1)+(x<<3)+(ch^'0'));
            return f?-x:x;
        }
    }
    #define reg register
    #define son e[i].to
    const int MN=1e5+5,P=998244353,g=3,invg=332748118,MX=524288;
    int u[MX],v[MX],N;
    void Add(int &x,int y){x+=y;if(x>=P)x-=P;}
    class MyNTT
    {
    	int pos[MX],invN,di;
    	int fpow(int x,int m){int r=1;for(;m;m>>=1,x=1ll*x*x%P)if(m&1)r=1ll*r*x%P;return r;}
    	void NTT(int *a,bool type)
    	{
    		reg int w,wn,i,j,p,k,X,Y;
    		for(i=0;i<N;++i) if(i<pos[i]) std::swap(a[pos[i]],a[i]);
    		for(i=1;i<N;i<<=1)
    		{
    			wn=fpow(type?g:invg,(P-1)/(i<<1));
    			for(p=i<<1,j=0;j<N;j+=p)
    			for(w=1,k=0;k<i;++k,w=1ll*w*wn%P)
    			{
    				X=a[j+k];Y=1ll*a[i+j+k]*w%P;
    				a[j+k]=(X+Y)%P;a[i+j+k]=(X-Y+P)%P;
    			}
    		}
    		if(!type)for(i=0;i<N;++i)a[i]=1ll*invN*a[i]%P;
    	}
    	public:
    		void Pro(int *a,int *b,int n,int m)
    		{
    			for(di=0,N=1;N<n+m;N<<=1,++di);invN=fpow(N,P-2);
    			reg int i;
    			for(i=0;i<N;++i)pos[i]=(pos[i>>1]>>1)|((i&1)<<(di-1));
    			NTT(a,1);NTT(b,1);
    			for(i=0;i<N;++i)a[i]=1ll*a[i]*b[i]%P;
    			NTT(a,0);
    		}
    }ntt;
    int pin,t;
    struct Node{int x,nex;}O[MN*40];
    class MyCDQ
    {
    	int res[MN];
    	public:
    		struct poly
    		{
    			int hr,n;
    			poly(int _=0,int __=-1):hr(_),n(__){}
    			void push_back(int x){++n;O[++pin]=(Node){x,hr};hr=pin;}
    			void To_array(int *a){for(int i=hr,j=n;i;i=O[i].nex)a[j--]=O[i].x;}
    		}p[MN];
    		poly Cdq(int l,int r)
    		{
    			if(l==r) return p[l];
    			reg int mid=(l+r)>>1;
    			poly L=Cdq(l,mid),R=Cdq(mid+1,r);
    			L.To_array(u);R.To_array(v);
    			ntt.Pro(u,v,L.n+1,R.n+1);
    			poly ans;for(reg int i=0;i<L.n+R.n+1;++i)ans.push_back(u[i]);
    			for(reg int i=0;i<N;++i) u[i]=0;
    			for(reg int i=0;i<N;++i) v[i]=0;
    			return ans;
    		}
    		void clear(){t=0;}
    		void add(){++t;p[t].hr=0;p[t].n=-1;}
    		void pb(int x){p[t].push_back(x);}
    		void get_ans(int &_0,int &_1,int &_2,int limi)
    		{
    			poly c=Cdq(1,t);
    			c.To_array(res);
    			for(reg int i=c.n+1;i<=limi;++i) res[i]=0;
    			for(reg int i=1;i<=limi;++i) Add(res[i],res[i-1]);
            	_0=res[limi];if(limi)_1=res[limi-1];if(limi>1)_2=res[limi-2];
    		}
    }cdq;
    int n,m,du[MN],hr[MN],Hr[MN<<1],en=1,A[MN],f[MN<<1][3];
    struct edge{int to,nex;}e[MN<<3];
    inline void ins(int x,int y,int *h){e[++en]=(edge){y,h[x]};h[x]=en;}
    int dfn[MN],low[MN],st[MN],tp,ind,num;
    void tj(int x,int F=0)
    {
    	dfn[x]=low[x]=++ind;st[tp++]=x;
    	reg int i;
    	for(i=hr[x];i;i=e[i].nex)if(i^F^1)
    	{
    		if(!dfn[son])
    		{
    			tj(son,i);
    			low[x]=min(low[x],low[son]);
    			if(dfn[x]==low[son])
    			{
    				++num;ins(x,num,Hr);
    				for(;st[tp]!=son;--tp) ins(num,st[tp-1],Hr);
    			}
    			else if(low[son]>dfn[x]) --tp,ins(x,son,Hr);
    		}
    		else low[x]=min(low[x],dfn[son]);
    	}
    }
    void dfs(int x)
    {
    	reg int i,j,_;
    	for(i=Hr[x];i;i=e[i].nex) dfs(son);
        if(x<=n)
        {
        	if(!Hr[x]) f[x][0]=1,f[x][1]=A[x]>0,f[x][2]=A[x]>1;
        	else
        	{
        		cdq.clear();
        		for(i=Hr[x];i;i=e[i].nex)
    			{
    				cdq.add();
    				if(son>n) cdq.pb(f[son][2]);
    				cdq.pb(f[son][1]);cdq.pb(f[son][0]);
    			}
    			cdq.get_ans(f[x][0],f[x][1],f[x][2],A[x]);
    		}
    	}
        else
    	{
            for(_=0;_<2;++_)
    		{
    			int S0[2]={0,0},S1[2];S0[_]=1;
                for(i=Hr[x];i;i=e[i].nex)
    			{
    				S1[0]=(1ll*S0[0]*f[son][1]+1ll*S0[1]*f[son][2])%P;
    				S1[1]=(1ll*S0[0]*f[son][0]+1ll*S0[1]*f[son][1])%P;
                    S0[0]=S1[0];S0[1]=S1[1];
                }
                Add(f[x][_+1],S0[0]);
                Add(f[x][_],S0[1]);
            }
        }
    }
    int main()
    {  
    	num=n=IO::read();m=IO::read();
    	reg int i,x,y;
    	for(i=1;i<=m;++i) x=IO::read(),y=IO::read(),ins(x,y,hr),ins(y,x,hr),++du[x],++du[y];
    	for(i=1;i<=n;++i) A[i]=IO::read(),A[i]=min(A[i],du[i]);
    	for(i=1;i<=n;++i) if(!dfn[i]) tj(i);dfs(1);
    	return 0*printf("%d
    ",f[1][0]);
    }
    


    Blog来自PaperCloud,未经允许,请勿转载,TKS!

  • 相关阅读:
    map内置函数、lambda表达式、快捷生成想要的列表、filter内置函数
    python生成随机验证码
    Redis数据库之概念与创建服务
    JavaScript中的类
    python之with的使用
    PHP变量名区分大小写,函数名不区分大小写
    php curl基本操作
    PHP生成随机字符串包括大小写字母
    PHP多例模式
    一个关于动态编译时 Evidence的问题
  • 原文地址:https://www.cnblogs.com/PaperCloud/p/loj_6496.html
Copyright © 2020-2023  润新知