[BZOJ5287][虚树]HNOI2018:毒瘤

BZOJ5287

题面说了那么多,其实就一句话:一棵有不超过十条非树边的树,选一个节点就不能选与之相连的节点,求选的方案数

如果没有非树边,就是个sb树形dp
有非树边呢?可以暴力枚举选不选非树边的端点,然后每一种情况树形dp一次
复杂度爆炸

因为非树边很少,所以这不是明摆着用虚树吗
预处理出虚树上每个节点对其父亲的贡献,然后就可以爆搜了

upd:
刚刚把另外一篇HNOI的题解贴过来(因为不想找传送门),然后忘了改传送门,链到了寻宝游戏去,交题的时候re了无数次。。。

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=2e5+5,mod=998244353;
inline int ad(const int &a){return a>=mod?a-mod:a;}
int f[N][2],ff[N][2],l[N],r[N],top,ref[N];
int dfn[N],low[N],tt,s[N];
int head[N],vis[N<<1],nxt[N<<1],tot=0;
int val[N];
int head2[N],vis2[N],nxt2[N];
struct node{
	int x,y;
	node(){x=0,y=0;}
	node(int _x,int _y):x(_x),y(_y){}
	inline node operator+(const node  &a){return node(x+a.x,y+a.y);}
	inline node operator*(const int &a){return node(1ll*x*a%mod,1ll*y*a%mod);}
}w[50],v[50],k[N][2];
inline void add(int u,int v){
	vis[++tot]=v,nxt[tot]=head[u],head[u]=tot;
	vis[++tot]=u,nxt[tot]=head[v],head[v]=tot;
}
void dfs(int u,int fa=0){
	dfn[u]=++tt;
	for(int i=head[u];i;i=nxt[i])
		if(vis[i]!=fa){
			if(!dfn[vis[i]])dfs(vis[i],u),s[u]+=s[vis[i]];
			else if(dfn[u]<dfn[vis[i]])l[++top]=u,r[top]=vis[i],ref[u]=1;
			else ref[u]=1;
		}
	ref[u]|=s[u]>=2;s[u]=ref[u]||s[u];
}
inline void Add(int x,int y,node a,node b){vis2[++tot]=y,nxt2[tot]=head2[x],w[tot]=a,v[tot]=b,head2[x]=tot;}
int g[N][2];
int dfs2(int u,int fa=0){
	g[u][0]=g[u][1]=1;val[u]=1;int pos=0,w;
	for(int i=head[u];i;i=nxt[i])
		if(!val[vis[i]]){
			int v=vis[i];w=dfs2(v);
			if(!w) g[u][1]=1ll*g[u][1]*g[v][0]%mod,g[u][0]=1ll*g[u][0]*(g[v][1]+g[v][0])%mod;
			else if(ref[u])Add(u,w,k[v][0]+k[v][1],k[v][0]);
			else k[u][1]=k[v][0],k[u][0]=k[v][1]+k[v][0],pos=w;
		}
	if(ref[u])k[u][0]=node(1,0),k[u][1]=node(0,1),pos=u;
	else k[u][0]=k[u][0]*g[u][0],k[u][1]=k[u][1]*g[u][1];
	return pos;
}
void dp(int u){
	f[u][0]=ff[u][1]?0:g[u][0];
	f[u][1]=ff[u][0]?0:g[u][1];
	for(int i=head2[u];i;i=nxt2[i]){
		int y=vis2[i];dp(y);int p=f[y][0],q=f[y][1];
		f[u][1]=1ll*f[u][1]*(1ll*v[i].x*p%mod+1ll*v[i].y*q%mod)%mod;
		f[u][0]=1ll*f[u][0]*(1ll*w[i].x*p%mod+1ll*w[i].y*q%mod)%mod;
	}
}
int main(){
	int n=read(),m=read();
	for(int i=1;i<=m;i++) add(read(),read());tot=0;
	dfs(1);ref[1]=1;dfs2(1);
	int lim=1<<top,ans=0;
	for(int i=0;i<lim;i++){
		for(int j=0;j<top;j++){
			if(i>>j&1)ff[l[j+1]][1]=ff[r[j+1]][0]=1;
			else ff[l[j+1]][0]=1;
		}
		dp(1);ans=ad(ans+ad(f[1][1]+f[1][0]));
		for(int j=0;j<top;j++){
			if(i>>j&1)ff[l[j+1]][1]=ff[r[j+1]][0]=0;
			else ff[l[j+1]][0]=0;
		}
	}
	cout<<ans<<"\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值