P4426 [HNOI2018] 毒瘤 【虚树+dp / DDP】

22 篇文章 1 订阅
20 篇文章 0 订阅

P4426


SOL

虚树代码都敲完了,突然发现这是一道ddp板题。。。看来最近和板子挺有缘的。。

  • transfer the probelem : \text{transfer\ the\ probelem}: transfer the probelem:在有10条额外边的树上求点独立集个数。

  • 基于原树思考。先求出 n − 1 n-1 n1条边的 f f f值。剩下的边最多与20个点有关。 2 20 2^{20} 220枚举选点情况,在一个较低的复杂度内算出整棵树的答案即可。

  • 如何实现计算? 我们发现 一个点 对于其父亲的转移方式是: f u , k = ∏ f s o n , k f_{u,k}=\prod f{son,k} fu,k=fson,k,这样对于 u u u的祖先 A n c Anc Anc f A n c , 0 = K 0 ∗ f u , 0 + K 1 ∗ f u , 1 f_{Anc,0}=K_{0}*f_{u,0}+K_{1}*f_{u,1} fAnc,0=K0fu,0+K1fu,1,可以建出虚树,预处理出系数存在边上。由于建树的过程中一个点只会被访问到一次,暴力跳父亲就可以。(也可以用dfs实现建虚树)

  • 于是我们用 O ( n + k ∗ 2 k ) O(n+k*2^k) O(n+k2k)复杂度愉快地 A C AC AC此题。

  • 仔细一想,其实不过是用了虚树模拟了 D D P DDP DDP的过程。。所以用 D D P DDP DDP可以直接做,而且代码好些得多。。。


CODE

#include<bits/stdc++.h>
using namespace std;
#define sf scanf
#define ri register int
#define in red()
#define gc getchar()
#define cs const
#define ll long long
inline int red(){
	int num=0,f=1;char c=gc;
	for(;!isdigit(c);c=gc)if(c=='-')f=-1;
	for(;isdigit(c);c=gc)num=(num<<1)+(num<<3)+(c^48);
	return num*f;
}
cs int N=1e5+20,mod=998244353;
inline int add(cs int &a,cs int &b){return a+b>=mod ? a-mod+b : a+b;}
inline int dec(cs int &a,cs int &b){return a-b<0 ? a+mod-b : a-b;}
inline int mul(cs int &a,cs int &b){return 1ll*a*b%mod;}
inline void Add(int &a,cs int &b){a= a+b>=mod ? a-mod+b:a+b;}
inline void Mul(int &a,cs int &b){a= 1ll*a*b%mod;}

//PART1 
int col[N],n,m;
inline int find(int u){
	int tu,uu=u;
	while(col[u])u=col[u];
	while(uu^u){
		tu=col[uu];
		col[uu]=u;
		uu=tu;
	}
	return u;
}
inline bool merge(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy)return 1;
	col[fx]=fy;
	return 0;
}
typedef pair<int,int> pi;
#define fi first
#define se second
pi e[N];
set<int>s;
vector<pi> V;
vector<int>P,g[N];
bool vis[N];
inline void build(){
	for(ri i=1;i<=m;++i){
		if(merge(e[i].fi,e[i].se))s.insert(e[i].fi),s.insert(e[i].se),V.push_back(e[i]);
		else g[e[i].fi].push_back(e[i].se),g[e[i].se].push_back(e[i].fi);
	}
}
int f[N][2],up,fa[N];
inline void dfs1(int u){
	f[u][0]=1;f[u][1]=1;
	for(ri i=g[u].size()-1;i>=0;--i){
		int v=g[u][i];
		if(v==fa[u])continue;
		fa[v]=u;
		dfs1(v);
		Mul(f[u][0],add(f[v][0],f[v][1]));
		Mul(f[u][1],f[v][0]);
	}
}
//PART2
cs int M=100;
int del[N][2],k[M][2][2];
int to[M],nxt[M],cnt=0,head[N];
//dfs版建树
//fi: point->id  se :edge-> id 
vector<pi>h[N];
pi dfs3(int u) {
	pi now;
	del[u][0]=del[u][1]=1;
	for(ri i=g[u].size()-1;i>=0;--i){
		int v=g[u][i];
		if(v==fa[u])continue;
		now=dfs3(v);
		if(now.fi)h[u].push_back(now);
		else Mul(del[u][0],add(f[v][0],f[v][1])),Mul(del[u][1],f[v][0]);
	}	
	if(!vis[u] && u^1 && h[u].size()<2){
		if(h[u].size()){
			int id=h[u][0].se;
			int k00=mul(del[u][0],add(k[id][0][0],k[id][1][0])),k01=mul(del[u][0],add(k[id][0][1],k[id][1][1]));
			int k10=mul(del[u][1],k[id][0][0]),k11=mul(del[u][1],k[id][0][1]);
			k[id][0][0]=k00;k[id][0][1]=k01;
			k[id][1][0]=k10;k[id][1][1]=k11;
			return h[u][0];
		}
		else return pi(0,0);
	}
	else{
		for(ri i=h[u].size()-1;i>=0;--i){
			int id=h[u][i].se,v=h[u][i].fi;
			nxt[id]=head[u];head[u]=id;to[id]=v;
		}
		++cnt;k[cnt][0][0]=1;k[cnt][1][1]=1;
		return pi(u,cnt);
	}
}
#define It set<int> :: iterator 
int sta[N],r[N][2],ans=0;
int tot=0;
inline bool check(){
	for(ri i=V.size()-1;i>=0;--i){
		if(sta[V[i].fi]&sta[V[i].se])return 0;
	}
	return 1;
}
inline void dfs4(int u){
	memset(r[u],0,sizeof(r[u]));
	if(vis[u]){
		if(!sta[u])r[u][0]=del[u][0];
		else r[u][1]=del[u][1];
	}
	else memcpy(r[u],del[u],sizeof(del[u]));
	int v0,v1;
	for(ri i=head[u];i;i=nxt[i]){
		int v=to[i];
		dfs4(v);
		v0=add(mul(k[i][0][0],r[v][0]),mul(k[i][0][1],r[v][1]));
		v1=add(mul(k[i][1][0],r[v][0]),mul(k[i][1][1],r[v][1]));
		Mul(r[u][0],add(v0,v1));
		Mul(r[u][1],v0);
	}
}
inline void dfs(int pos){
	if(pos==up){
		if(check())dfs4(1),Add(ans,add(r[1][0],r[1][1]));	
		return;
	}
	sta[P[pos]]=0;
	dfs(pos+1);
	sta[P[pos]]=1;
	dfs(pos+1);
}
inline void solve(){
	build();
	dfs1(1);
	if(m==n-1){
		cout<<add(f[1][0],f[1][1]);
		exit(0);
	}
	for(It t=s.begin();t!=s.end();++t)vis[*t]=1,P.push_back(*t);
	dfs3(1);
	up=P.size();
	dfs(0);
	cout<<ans<<'\n';
}

signed main(){
	n=in;m=in;
	for(ri i=1;i<=m;++i)e[i].fi=in,e[i].se=in;
	solve();
	
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值