P6773 [NOI2020] 命运 题解

P6773 [NOI2020] 命运 题解


知识点

树形计数背包 DP,线段树合并,DP 优化。

分析

\(32\%\)

部分分直接暴力深搜容斥加树剖线段树,可以做到 \(O(2^m\log_2^2{n})\)

\(40\%\)

在此基础上加虚树或子集卷积(子集卷积没思路,看别人题解看到的)可以有 \(O(m2^m)\)

\(72\%\)

那么对于这种决策类题目,一看就是 DP,不用考虑,直接先想如何设计状态。

先尝试一下最简单的一维是布尔的二维状态:\(f_{i,0/1}\),表示 \(i\) 的父边是否染色时,总方案数。

但是这个状态看着非常贫瘠,没有包含任何一点其他的信息,所以无法实现转移,那么我们尝试加一点东西进去,那加什么比较好?对 DP 起限制的只有点对 \((u,v)\),那么现在需要考虑的点对只有跨域当前节点的那些点对,我们需要找出一种合适的方法让它们能够在有限的状态下将它们加进去。

\(f_{i,j}\),我们一开始可以想到把 \(j\) 设为压缩后的所有父节点,但是发现如果要满足题目条件的话,只需要记深度最大的那个来决策即可,那么我们就得到了想要的状态了:\(f_{i,j}\) 表示到 \(i\),点对中还需决策的父节点最大深度为 \(j\),此时的方案数。

\(d_u\) 表示 \(u\) 作为点对中子孙节点时,祖先节点深度最大值,则初始状态为 \(f_{u,i}=[i=d_u]\)

转移也不难想:设当前从 \(v\) 转移到父节点 \(u\)(设 \(dep_u\)\(u\) 在树上的深度)

  1. 不将这条边染色:那么两个状态中,深度一维最大值需为 \(i\)

    \[f_{u,i} \gets \sum_{j=0}^i f_{u,i}f_{v,j} + \sum_{j=0}^{i-1} f_{u,j}f_{v,i} \\ \]

  2. 将这条边染色:

    \[f_{u,i} \gets \sum_{j=0}^{dep_u} f_{u,i}f_{v,j} \\ \]

合起来变为:

\[f_{u,i} \gets \sum_{j=0}^i f_{u,i}f_{v,j} + \sum_{j=0}^{i-1} f_{u,j}f_{v,i} + \sum_{j=0}^{dep_u} f_{u,i}f_{v,j} \\ \]

那么得到一个 \(O(n^3)\) 的做法,然后简单前缀和优化即可达到 \(O(n^2)\)

我们直接做,可以拿到前 14 个点,也就是 \(56\%\);如果可以把 DP 数组用 vector<int> 来记,时空复杂度降到 \(O(\sum_u dep_u)\),那么可以解决特殊性质,拿到 \(64\%\);然后如果再用虚树并对方程稍加改动,那么前 18 个点就都可以拿到,即 \(72\%\)

将点都扔到虚树上去,设 \(Dep_u\)\(u\) 在虚树上的深度, \(D_u\) 表示 \(u\) 作为点对中子孙节点时,祖先节点虚树深度最大值,初始状态就变成:\(f_{u,i}=[i=D_u]\)

转移:设当前从 \(v\) 转移到父节点 \(u\)

  1. 不将这条边染色:那么两个状态中,深度一维最大值需为 \(i\)

    \[f_{u,i} \gets \sum_{j=0}^i f_{u,i}f_{v,j} + \sum_{j=0}^{i-1} f_{u,j}f_{v,i} \\ \]

  2. 将这段的至少一条边染色:

    \[f_{u,i} \gets (2^{dep_v-dep_u}-1)\sum_{j=0}^{dep_u} f_{u,i}f_{v,j} \\ \]

加起来:

\[f_{u,i} \gets \sum_{j=0}^i f_{u,i}f_{v,j} + \sum_{j=0}^{i-1} f_{u,j}f_{v,i} + (2^{dep_v-dep_u}-1)\sum_{j=0}^{dep_u} f_{u,i}f_{v,j} \\ \]

那么前缀和优化就好了。

注:判断完全二叉树其实直接把所有深度加起来与 \(10^8\) 比较一下即可。

\(100\%\)

那么正解是要用一个在当年还算新颖的套路——线段树合并优化 DP。

我们把每个点的 DP 状态都放到一起,发现其实在同一时间,\(f\) 数组中真正有用的状态数最多就是 \(n\) 个,比如说:当一个点向它的父亲转移后,那么这个点上的状态就都没用了,而父节点上有用的状态则可能会多一些,这似乎很像权值线段树合并的操作。所以我们完全可以模仿线段树合并来进行转移。

\[f_{u,i} \gets \sum_{j=0}^i f_{u,i}f_{v,j} + \sum_{j=0}^{i-1} f_{u,j}f_{v,i} + \sum_{j=0}^{dep_u} f_{u,i}f_{v,j} \\ f_{u,i} \gets f_{u,i} (\sum_{j=0}^i f_{v,j} + \sum_{j=0}^{dep_u} f_{v,j}) + f_{v,i}\sum_{j=0}^{i-1} f_{u,j} \\ \]

在设定初始状态时,我们用单点加,转移的时候,\(\sum_{j=0}^{dep_u} f_{v,j}\) 我们可以一次性区间查询出来,剩下的在合并过程中类似分治地进行前缀和即可。

注意线段树合并时边界处理不能直接合并。

代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(5e5+10),lN(19),lV(lN+1);

namespace IOEcat {
#define isD(c) ('0'<=(c)&&(c)<='9')
#define DE(...) E(#__VA_ARGS__,__VA_ARGS__)
	struct Icat {

		char getc() {
			return getchar();
		}

		template<class T>void operator ()(T &x) {
			static bool sign(0);
			static char ch(0);
			sign=0,x=0;
			while(ch=getc(),!isD(ch))if(ch=='-')sign=1;
			do x=(x<<1)+(x<<3)+(ch^48);
			while(ch=getc(),isD(ch));
			if(sign)x=-x;
		}

		template<class T,class...Types>void operator ()(T &x,Types&...args) {
			return (*this)(x),(*this)(args...);
		}

	} I;
	struct Ocat {

		void putc(char c) {
			putchar(c);
		}

		template<class T>void operator ()(T x,const char lst='\n') {
			static int top(0);
			static char st[100];
			if(x<0)x=-x,putc('-');
			do st[++top]=(x%10)^48,x/=10;
			while(x);
			while(top)putc(st[top--]);
			putc(lst);
		}

		template<class T,class...Types>void operator ()(const T x,const char lst='\n',const Types...args) {
			return (*this)(x,lst),(*this)(args...);
		}

	} O;
	struct Ecat {

		template<class T>void operator ()(const char *fmt,const T x) {
			cerr<<fmt<<':'<<x<<'.'<<endl;
		}

		template<class T,class...Types>void operator ()(const char *fmt,const T x,const Types...args) {
			while(*fmt^',')cerr<<*fmt++;
			return cerr<<':'<<x<<" ,",(*this)(++fmt,args...);
		}

	} E;

} using namespace IOEcat;

namespace Modular {
#define Mod 998244353
	int pw2[N];

	template<class T1,class T2>constexpr auto add(const T1 a,const T2 b) {
		return a+b>=Mod?a+b-Mod:a+b;
	}

	template<class T1,class T2>constexpr auto mul(const T1 a,const T2 b) {
		return (ll)a*b%Mod;
	}

	template<class T,class...Types>constexpr auto add(const T a,const Types...args) {
		return add(a,add(args...));
	}

	template<class T,class...Types>constexpr auto mul(const T a,const Types...args) {
		return mul(a,mul(args...));
	}

	template<class T1,class T2>T1 &toadd(T1 &a,const T2 b) {
		return a=add(a,b);
	}

	template<class T1,class T2>T1 &tomul(T1 &a,const T2 b) {
		return a=mul(a,b);
	}

	template<class T0,class T,class...Types>T0 &toadd(T0 &a,const T b,const Types...args) {
		return toadd(a,b),toadd(a,args...);
	}

	template<class T0,class T,class...Types>T0 &tomul(T0 &a,const T b,const Types...args) {
		return tomul(a,b),tomul(a,args...);
	}
	
	void Init(int n=N-5) {
		FOR(i,pw2[0]=1,n)pw2[i]=mul(pw2[i-1],2);
	}

} using namespace Modular;

bool c[N];
int n,m,ans,idx,tot;
int u[N],v[N],dl[N],Fa[N],dep[N],Dep[N];
int fa[N][lV];
vector<int> g[N],G[N],lin[N];

void dfs0(int u) {
	dep[u]=dep[fa[u][0]]+1,dl[u]=++idx;
	FOR(i,1,lN)fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v:g[u])if(v^fa[u][0])fa[v][0]=u,dfs0(v),c[u]|=c[v];
}

int lca(int u,int v) {
	if(dep[u]>dep[v])swap(u,v);
	DOR(i,lN,0)if((dep[v]-dep[u])&1<<i)v=fa[v][i];
	if(u==v)return u;
	DOR(i,lN,0)if(fa[u][i]^fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}

void Rebuild(vector<int> &que) {
	int top(0);
	static int st[N];
	sort(que.begin(),que.end(),[](int u,int v) { return dl[u]<dl[v]; });
	que.erase(unique(que.begin(),que.end()),que.end()),st[top=1]=1;
	for(int u:que) {
		int pa(lca(u,st[top]));
		if(pa^st[top]) {
			while(top>1&&dep[st[top-1]]>dep[pa])G[Fa[st[top]]=st[top-1]].push_back(st[top]),--top;
			G[Fa[st[top]]=pa].push_back(st[top]),--top;
		}
		if(pa^st[top])st[++top]=pa;
		if(u^st[top])st[++top]=u;
	}
	while(top>1)G[Fa[st[top]]=st[top-1]].push_back(st[top]),--top;
}

namespace Subtask1 { /*虚树*/ /*40%*/
	int cnt[N];
	
	bool Check() {
		return m<=22;
	}
	
	void DFS(int now,bool flag) {
		if(now>m)return toadd(ans,flag?Mod-pw2[tot]:pw2[tot]),void();
		for(int x(v[now]); dep[x]>dep[u[now]]; x=Fa[x])tot-=(++cnt[x]==1?dep[x]-dep[Fa[x]]:0);
		DFS(now+1,flag^1);
		for(int x(v[now]); dep[x]>dep[u[now]]; x=Fa[x])tot+=(!--cnt[x]?dep[x]-dep[Fa[x]]:0);
		DFS(now+1,flag);
	}
	
	int Cmain() {
		vector<int> que;
		FOR(i,1,m)que.push_back(u[i]),que.push_back(v[i]);
		dfs0(1),Rebuild(que),tot=n-1,DFS(1,0),O(ans,'\n');
		return 0;
	}
	
}

namespace Subtask2 { /*虚树加动态规划*/ /*72%*/
	vector<int> f[N],h[N];
	
	ll Sum_Dep(int u,int fa) {
		ll sum(dep[u]);
		for(int v:G[u])if(v^fa)sum+=Sum_Dep(v,u);
		return sum;
	}
	
	bool Check() {
		vector<int> que;
		FOR(i,1,m)que.push_back(u[i]),que.push_back(v[i]);
		dfs0(1),Rebuild(que);
		return n<=2000||m<=2000||Sum_Dep(1,0)<=100000000;
	}
	
	void dp(int u) {
		Dep[u]=Dep[Fa[u]]+1,f[u]=vector<int>(Dep[u]+1,0),h[u]=vector<int>(Dep[u]+1,0);
		int mxd(0);
		for(int v:lin[u])tomax(mxd,Dep[v]);
		f[u][mxd]=1;
		FOR(i,0,Dep[u])h[u][i]=add(i?h[u][i-1]:0,f[u][i]);
		for(int v:G[u])if(v^Fa[u]) {
			dp(v);
			FOR(i,0,Dep[u])f[u][i]=add(
				mul(f[u][i],h[v][i]),i?mul(h[u][i-1],f[v][i]):0,//0
				mul(f[u][i],h[v][Dep[u]],add(pw2[dep[v]-dep[u]],Mod-1))//1
			);
			FOR(i,0,Dep[u])h[u][i]=add(i?h[u][i-1]:0,f[u][i]);
		}
	}
	
	int Cmain() {
		FOR(i,2,n)if(!c[i])++tot;
		dp(1),O(mul(f[1][0],pw2[tot]),'\n');
		return 0;
	}
	
}

namespace Subtask { /*线段树合并优化动态规划*/ /*100%*/
	struct SEG {
		int tot;
		int rt[N];
		struct node {
			int ls,rs,f,tag;
			node(int ls=0,int rs=0,int f=0,int tag=1):ls(ls),rs(rs),f(f),tag(tag) {}
			
			void down(int _tag) {
				tomul(f,_tag),tomul(tag,_tag);
			}
			
		} tr[N<<5];
		
#define ls(p) (tr[p].ls)
#define rs(p) (tr[p].rs)
#define mid ((l+r)>>1)
		
		int &operator [](int i) {
			return rt[i];
		}
		
		void Up(int p) {
			tr[p].f=add(tr[ls(p)].f,tr[rs(p)].f);
		}
		
		void Down(int p) {
			if(tr[p].tag^1)tr[ls(p)].down(tr[p].tag),tr[rs(p)].down(tr[p].tag),tr[p].tag=1;
		}
	
		void Change(int x,int d,int &p,int l=0,int r=n) {
			tr[p=++tot]=node(0,0,d,1);
			if(l==r)return;
			return x<=mid?Change(x,d,ls(p),l,mid):Change(x,d,rs(p),mid+1,r);
		}
		
		int Sum(int L,int R,int p,int l=0,int r=n) {
			if(L<=l&&r<=R)return tr[p].f;
			Down(p);
			if(R<=mid)return Sum(L,R,ls(p),l,mid);
			if(mid<L)return Sum(L,R,rs(p),mid+1,r);
			return add(Sum(L,R,ls(p),l,mid),Sum(L,R,rs(p),mid+1,r));
		}
		
		void Merge(int Sp,int Sq,int &p,int q,int l=0,int r=n) {
			if(!p&&!q)return;
			if(!p)return tr[p=q].down(Sp),void();
			if(!q)return tr[p].down(Sq);
			if(l==r)return tr[p].f=add(mul(tr[p].f,add(Sq,tr[q].f)),mul(tr[q].f,Sp)),void();
			Down(p),Down(q);
			Merge(add(Sp,tr[ls(p)].f),add(Sq,tr[ls(q)].f),rs(p),rs(q),mid+1,r);
			Merge(Sp,Sq,ls(p),ls(q),l,mid);
			Up(p);
		}
		
		void Print(int p,int l=0,int r=n) {
			if(!p)return;
			if(l==r)return DE(l,tr[p].f),void();
			Down(p),Print(ls(p),l,mid),Print(rs(p),mid+1,r);
		}
		
#undef ls
#undef rs
#undef mid
	} seg;
	
	void dp(int u,int fa) {
		dep[u]=dep[fa]+1;
		int mxd(0);
		for(int v:lin[u])tomax(mxd,dep[v]);
		seg.Change(mxd,1,seg[u]);
		for(int v:g[u])if(v^fa)dp(v,u),seg.Merge(0,seg.Sum(0,dep[u],seg[v]),seg[u],seg[v]);
	}
	
	int Cmain() {
		dp(1,0),O(seg.Sum(0,0,seg[1]),'\n');
		return 0;
	}
	
}

int main() {
	I(n),Init();
	FOR(i,2,n) {
		int u,v;
		I(u,v),g[u].push_back(v),g[v].push_back(u);
	}
	I(m);
	FOR(i,1,m)I(u[i],v[i]),c[v[i]]=1;
	if(Subtask1::Check())return Subtask1::Cmain();
	FOR(i,1,m)lin[v[i]].push_back(u[i]);
	if(Subtask2::Check())return Subtask2::Cmain();
	return Subtask::Cmain();
}
posted @ 2025-01-05 14:47  Add_Catalyst  阅读(20)  评论(0)    收藏  举报