【虚树+容斥/动态DP+容斥】BZOJ5287 [HNOI2018] 毒瘤

【题目】
BZOJ
luogu
求图的最大独立集方案数。 n ≤ 1 0 5 , n − 1 ≤ m ≤ n + 10 n\leq 10^5,n-1\leq m\leq n+10 n105,n1mn+10

【解题思路】
非树边只有 11 11 11条。
树的最大独立集可以用一个朴素的最大独立集来做,我们枚举非树边中高的一点的状态,每种状态做一次 DP \text{DP} DP,可以做到 O ( 2 m − n + 1 n ) O(2^{m-n+1}n) O(2mn+1n)
不难想到一类树上 NP \text{NP} NP问题的套路——动态 DP \text{DP} DP,于是这道题可以做到 O ( 2 m − n + 1 log ⁡ n + n log ⁡ n ) O(2^{m-n+1}\log n+n\log n) O(2mn+1logn+nlogn),这里需要用 DFS \text{DFS} DFS去枚举状态。
实际上由于转移矩阵中很多都不会受到影响,我们可以考虑建出虚树,在虚树上进行 DP \text{DP} DP,就可以做到 O ( n + S ⋅ 2 m − n + 1 ) O(n+S\cdot 2^{m-n+1}) O(n+S2mn+1),这里 S S S是虚树上节点个数。

【参考代码】

#include<bits/stdc++.h>
#define fi first
#define se second
#define mkp make_pair
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10,M=100,mod=998244353;
int n,m,ans;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

namespace Graph
{
	struct Tway{int v,nex;};
	struct G
	{
		int tot,head[N];Tway e[N<<1];
		void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
	};
}
using namespace Graph;

namespace Tree
{
	G T,vt;
	int ind,top,K,cnt;
	int dep[N],pos[N],fa[19][N],fc[32],st[M],p[M];
	int f[N][2],g[N][2],k[N][2][2],c[N];
	bool invt[N],vis[N],fg[N<<1];
	pii a[M];
	void dfs(int x,int ff)
	{
		pos[x]=++ind;dep[x]=dep[ff]+1;fa[0][x]=ff;vis[x]=1;
		for(int i=1;i<18;++i) fa[i][x]=fa[i-1][fa[i-1][x]];
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(v==ff) continue;
			if(!vis[v]) dfs(v,x);
			else a[++K]=mkp(x,v),fg[i]=fg[i^1]=1;
		}
	}
	int lca(int x,int y)
	{
		if(dep[x]<dep[y]) swap(x,y);
		for(int t=dep[x]-dep[y],i=0;i<17;++i) 
			if(t&fc[i]) x=fa[i][x];
		for(int i=17;~i;--i) if(fa[i][x]^fa[i][y]) 
			x=fa[i][x],y=fa[i][y];
		return x==y?x:fa[0][x];
	}
	void init()
	{
		n=read();m=read();T.tot=1;
		fc[0]=1;for(int i=1;i<30;++i) fc[i]=fc[i-1]<<1;
		for(int i=1,u,v;i<=m;++i) u=read(),v=read(),T.add(u,v),T.add(v,u);
	}
	bool cmp(int x,int y){return pos[x]<pos[y];}
	void buildvt()
	{
		for(int i=1;i<=K;++i) 
		{
			if(!invt[a[i].fi]) invt[a[i].fi]=1,p[++cnt]=a[i].fi;
			if(!invt[a[i].se]) invt[a[i].se]=1,p[++cnt]=a[i].se;
		}
		sort(p+1,p+cnt+1,cmp);
		if(!invt[1]) invt[1]=1,st[++top]=1;
		for(int i=1;i<=cnt;++i)
		{
			int x=p[i],t=0;
			for(;top;)
			{
				t=lca(st[top],x);
				if(top>1 && dep[st[top-1]]>dep[t]) vt.add(st[top-1],st[top]),--top;
				else if(dep[st[top]]>dep[t]) {vt.add(t,st[top]),--top;break;}//
				else break;
			}
			if(st[top]^t) invt[t]=1,st[++top]=t;
			st[++top]=x;
		}
		while(top>1) vt.add(st[top-1],st[top]),--top;
	}
	void predp(int x,int ban)
	{
		f[x][0]=f[x][1]=1;invt[x]=1;
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(invt[v] || fg[i] || v==fa[0][x] || v==ban) continue;
			predp(v,ban);
			f[x][0]=(ll)f[x][0]*(f[v][0]+f[v][1])%mod;
			f[x][1]=(ll)f[x][1]*f[v][0]%mod;
		}
	}
	void getmat(int x,int y)
	{
		k[x][0][0]=k[x][1][1]=1;
		for(int i=x;fa[0][i]^y;i=fa[0][i])
		{
			predp(fa[0][i],i);
			int t0=k[x][0][0],t1=k[x][1][0],ff=fa[0][i];
			k[x][0][0]=(ll)f[ff][0]*(k[x][0][0]+k[x][0][1])%mod;
			k[x][1][0]=(ll)f[ff][0]*(k[x][1][0]+k[x][1][1])%mod;
			k[x][0][1]=(ll)f[ff][1]*t0%mod;
			k[x][1][1]=(ll)f[ff][1]*t1%mod;
		}
	}
	void prework(int x)
	{
//cerr<<x<<endl;
		for(int i=vt.head[x];i;i=vt.e[i].nex) prework(vt.e[i].v),getmat(vt.e[i].v,x);
		f[x][0]=f[x][1]=1;
		for(int i=T.head[x];i;i=T.e[i].nex)
		{
			int v=T.e[i].v;
			if(invt[v] || fg[i] || v==fa[0][x]) continue;
			predp(v,0);
			f[x][0]=(ll)f[x][0]*(f[v][0]+f[v][1])%mod;
			f[x][1]=(ll)f[x][1]*f[v][0]%mod;
		}
	}
	void dp(int x)
	{
		g[x][0]=f[x][0];g[x][1]=f[x][1];
//printf("%d %d\n",f[x][0],f[x][1]);
		for(int i=vt.head[x];i;i=vt.e[i].nex)
		{
			int v=vt.e[i].v;dp(v);
			int f0=((ll)k[v][0][0]*g[v][0]+(ll)k[v][1][0]*g[v][1])%mod;
			int f1=((ll)k[v][0][1]*g[v][0]+(ll)k[v][1][1]*g[v][1])%mod;
			g[x][0]=(ll)g[x][0]*(f0+f1)%mod;g[x][1]=(ll)g[x][1]*f0%mod;
		}
		if(c[x]==1) g[x][0]=0;
		if(c[x]==-1) g[x][1]=0;
	}
	void solve()
	{
		dfs(1,0);buildvt();
//cerr<<cnt<<" "<<K<<endl;
		prework(1);

		for(int S=0;S<fc[cnt];++S)
		{
			for(int i=1;i<=cnt;++i) c[p[i]]=(S&fc[i-1])?1:-1;
			bool flag=0;
			for(int i=1;i<=K;++i) if(c[a[i].fi]==1 && c[a[i].se]==1) {flag=1;break;}
			if(flag) continue;
			dp(1);ans=((ll)ans+g[1][0]+g[1][1])%mod;
		}
		printf("%d\n",ans);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ5287.in","r",stdin);
	freopen("BZOJ5287.out","w",stdout);
#endif
	Tree::init();Tree::solve();
	return 0;
}

这里把shadowice1984 的动态DP也贴上

#include<cstdio>
#include<algorithm>
using namespace std;const int N=1e5+10;typedef long long ll;const ll mod=998244353;
inline ll po(ll a,ll p){ll r=1;for(;p;p>>=1,a=a*a%mod)if(p&1)r=r*a%mod;return r;}
struct nod//用来做除法的结构体 
{
    ll v1;int c1;ll v2;int c2;
    inline void ins(const ll& t1,const ll& t2)
    {(v1*=(t1)?t1:1)%=mod;c1+=(t1==0);(v2*=(t2)?t2:1)%=mod;c2+=(t2==0);}
    inline void del(const ll& t1,const ll& t2)
    {
        (v1*=(t1)?po(t1,mod-2):1)%=mod;c1-=(t1==0);
        (v2*=(t2)?po(t2,mod-2):1)%=mod;c2-=(t2==0);
    }
};
struct mar//矩阵 
{
    ll mp[2][2];
    inline ll* operator [](const int& x){return mp[x];}
    friend mar operator *(mar a,mar b)
    {
        mar c;
        c[0][0]=((ll)a[0][0]*b[0][0]+(ll)a[0][1]*b[1][0])%mod;
        c[0][1]=((ll)a[0][0]*b[0][1]+(ll)a[0][1]*b[1][1])%mod;
        c[1][0]=((ll)a[1][0]*b[0][0]+(ll)a[1][1]*b[1][0])%mod;
        c[1][1]=((ll)a[1][0]*b[0][1]+(ll)a[1][1]*b[1][1])%mod;return c;
    }
    void operator =(const nod& b)
    {mp[1][0]=mp[0][0]=(b.c1)?0:b.v1;mp[0][1]=(b.c2)?0:b.v2;}
};
int v[2*N];int x[2*N];int ct;int al[N];int n;int m;ll ans;
int eu[22];int ev[22];int hd;int siz[N];int h[N];int mi[N];
inline void add(int u,int V){v[++ct]=V;x[ct]=al[u];al[u]=ct;}
struct bcj//智商不够数据结构来凑,随手敲了个并查集判非树边 
{
    int fa[N];
    inline int f(int x){return fa[x]=(x==fa[x])?x:f(fa[x]);}
    inline void u(int x,int y){fa[f(x)]=f(y);}
    inline void ih(){for(int i=1;i<=n;i++)fa[i]=i;}
    inline void ins(int U,int V)
    {if(f(U)==f(V))eu[++hd]=U,ev[hd]=V;else add(U,V),add(V,U),u(U,V);}
}ufs;
inline int dfs1(int u,int f)//轻重链剖分 
{
    for(int i=al[u];i;i=x[i])
        if(v[i]!=f)siz[u]+=dfs1(v[i],u),h[u]=(siz[h[u]]<siz[v[i]])?v[i]:h[u];return ++siz[u];
}
struct global_biased_tree//全局平衡二叉树 
{
    mar mul_bas[N][22];mar we_bas[N][22];nod ld_bas[N][22];int s[N][2];
    mar* nmul[N];mar* nwe[N];nod* nld[N];//为了写的爽每个点开了个指针模拟栈 
    int st[N];int tp;int wsiz[N];int rot;int fa[N];//define n连实现栈 
    # define mul(p) (*nmul[p])
    # define we(p) (*nwe[p])
    # define incm(p) (*(nmul[p]+1)=*nmul[p],++nmul[p])
    # define decm(p) (--nmul[p])
    # define incw(p) (*(nwe[p]+1)=*nwe[p],++nwe[p])
    # define decw(p) (--nwe[p])
    # define ld(p) (*nld[p])
    # define incl(p) (*(nld[p]+1)=*nld[p],++nld[p])
    # define decl(p) (--nld[p])
    inline void ud(int p){mul(p)=mul(s[p][0])*we(p)*mul(s[p][1]);}//更新 
    inline void ih()//初始化指针 
    {
        for(int i=0;i<=n;i++)nmul[i]=mul_bas[i];for(int i=0;i<=n;i++)nwe[i]=we_bas[i];
        for(int i=0;i<=n;i++)nld[i]=ld_bas[i];
        for(int i=0;i<=21;i++)mul_bas[0][i][0][0]=1,mul_bas[0][i][1][1]=1;
    }
    inline int sbuild(int l,int r)//每条重链建bst 
    {
        if(l>r)return 0;
        int mid=l;int sum=0;for(int i=l;i<=r;i++)sum+=wsiz[st[i]];
        for(int pre=0;(pre<<1)<sum;mid++)pre+=wsiz[st[mid]];mid--;
        s[st[mid]][0]=sbuild(l,mid-1);fa[s[st[mid]][0]]=st[mid];
        s[st[mid]][1]=sbuild(mid+1,r);fa[s[st[mid]][1]]=st[mid];ud(st[mid]);
        return st[mid];
    }
    inline int solve(int u,int f)//链分治 
    {
        for(int p=u,nf=f;p;nf=p,p=h[p])
        {
            wsiz[p]++;ld(p).v1++;ld(p).v2++;
            for(int j=al[p];j;j=x[j])
                if(v[j]!=h[p]&&v[j]!=nf)
                {
                    int ve=solve(v[j],p);fa[ve]=p;wsiz[p]+=siz[v[j]];
                    ld(p).ins((mul(ve)[0][0]+mul(ve)[0][1])%mod,mul(ve)[0][0]);
                }we(p)=ld(p);
        }tp=0;for(int p=u;p;p=h[p])st[++tp]=p;reverse(st+1,st+tp+1);return sbuild(1,tp);
    }
    inline void modify(int u)//修改 
    {
        incw(u);incl(u);ld(u).v1=0;we(u)=ld(u);
        for(int p=u;p;p=fa[p])
            if(fa[p]&&s[fa[p]][0]!=p&&s[fa[p]][1]!=p)
            {
                int f=fa[p];incw(f);incl(f);
                ld(f).del((mul(p)[0][0]+mul(p)[0][1])%mod,mul(p)[0][0]);incm(p),ud(p);
                ld(f).ins((mul(p)[0][0]+mul(p)[0][1])%mod,mul(p)[0][0]);we(f)=ld(f);
            }else incm(p),ud(p);
    }
    inline void undo(int u)//撤销 
    {
        decw(u);decl(u);
        for(int p=u;p;p=fa[p])
            if(fa[p]&&s[fa[p]][0]!=p&&s[fa[p]][1]!=p)decw(fa[p]),decl(fa[p]),decm(p);
            else decm(p);
    }
    inline ll calc(){return (mul(rot)[0][0]+mul(rot)[0][1])%mod;}
}gbt;
inline void dfs(int stp,int siz)//dfs+容斥 
{
    if(stp==hd+1)
    {
        if(siz&1)(ans+=mod-gbt.calc())%=mod;
        else (ans+=gbt.calc())%=mod;return;
    }dfs(stp+1,siz);
    if(!mi[eu[stp]]){mi[eu[stp]]=stp;gbt.modify(eu[stp]);}
    if(!mi[ev[stp]]){mi[ev[stp]]=stp;gbt.modify(ev[stp]);}
    dfs(stp+1,siz+1);
    if(mi[ev[stp]]==stp){mi[ev[stp]]=0;gbt.undo(ev[stp]);}
    if(mi[eu[stp]]==stp){mi[eu[stp]]=0;gbt.undo(eu[stp]);}
}
int main()
{
    scanf("%d%d",&n,&m);ufs.ih();
    for(int i=1,u,v;i<=m;i++)scanf("%d%d",&u,&v),ufs.ins(u,v);dfs1(1,0);
    gbt.ih();gbt.rot=gbt.solve(1,0);dfs(1,0);printf("%lld",ans);return 0;//拜拜程序~ 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值