【做题记录】csp2025-树形dp

A. 「MXOI Round 1」城市
首先推个小式子,把让求的答案中和 \(n+1\) 有关的分出来:

\[\begin{align*} &\sum_{i=1}^{n+1}\sum_{j=1}^{n+1}cost(i,j)\\ =&\sum_{i=1}^{n+1}\sum_{j=1}^{n}cost(i,j)+\sum_{i=1}^{n+1}cost(i,n+1)\\ =&\sum_{i=1}^{n}\sum_{j=1}^{n}cost(i,j)+\sum_{i=1}^{n}cost(i,n+1)+\sum_{i=1}^{n+1}cost(i,n+1)\\ =&\sum_{i=1}^{n}\sum_{j=1}^{n}cost(i,j)+2\sum_{i=1}^{n}cost(i,n+1) \end{align*} \]

发现前面一部分是可以预先算出来的,考虑后一部分。
发现这一部分相当于是所有点到 \(k\) 的距离,再加上 \(n\times w\)
于是考虑换根DP,首先计算每个点的子树的答案,即

\[f[u]=\sum f[v]+sz[v]\times w \]

然后用父亲更新儿子为根时的答案:

\[\begin{align*} g[v]&=f[v]+(g[u]-f[v]-sz[v]\times w+(n-sz[v])\times w)\\ &=g[u]+(n-2sz[v])\times w \end{align*} \]

答案即为

\[\sum_{i=1}^ng[i]+2(g[k]+n\times w) \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,mod=998244353;
int n,m,sz[maxn],f[maxn];
vector<pii> e[maxn];
il void dfs1(int u,int fa){
	sz[u]=1;
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		(f[u]+=f[v])%=mod;
		(f[u]+=sz[v]*1ll*w%mod)%=mod;
	}
}
il void dfs2(int u,int fa){
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		f[v]=(f[u]+(n-sz[v]*2+mod)*1ll*w%mod)%mod;
		dfs2(v,u);
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n)read(m);
	for(int i=1,u,v,w;i<n;i++){
		read(u)read(v)read(w);
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	dfs1(1,0),dfs2(1,0);
//	for(int i=1;i<=n;i++){
//		cout<<f[i]<<" ";
//	}
//	puts("");
	int sum=0;
	for(int i=1;i<=n;i++){
		(sum+=f[i])%=mod;
	}
	while(m--){
		int u,w;
		read(u)read(w);
		printf("%d\n",(sum+((f[u]+n*1ll*w%mod)%mod<<1)%mod)%mod);
	}
	return 0;
}
}
int main(){return asbt::main();}

B. Dr
看到异或首先建trie树,然后考虑trie树上从0,1儿子向父亲的转移。
首先如果没有其中一个,那么直接让这一位等于有的那一个,即没有任何多余的贡献。
否则,要选一个加上2的若干次方,然后取最小值。这一部分的转移:

\[dp[u]=\min(dp[tr[u][0]],dp[tr[u][1]])+2^{dep} \]

其中 \(dep\) 是当前的二进制位。不用dfs,倒序遍历即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,a[maxn],tot,tr[maxn<<5][2];
int dp[maxn<<5],dep[maxn<<5];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i]);
		int p=0;
		for(int j=30;~j;j--){
			int d=a[i]>>j&1;
			if(!tr[p][d]){
				tr[p][d]=++tot;
				dep[tot]=j;
			}
			p=tr[p][d];
		}
	}
	for(int u=tot;u;u--){
		if(!tr[u][0]&&!tr[u][1]){
			continue;
		}
		if(!tr[u][0]){
			dp[u]=dp[tr[u][1]];
		}
		else if(!tr[u][1]){
			dp[u]=dp[tr[u][0]];
		}
		else{
			dp[u]=min(dp[tr[u][0]],dp[tr[u][1]])+(1<<(dep[u]-1));
		}
	}
	printf("%d",min(tr[0][0]?dp[tr[0][0]]:INT_MAX,tr[0][1]?dp[tr[0][1]]:INT_MAX));
	return 0;
}
}
int main(){return asbt::main();}

C. “访问”美术馆
树上背包。考虑 \(f[u][i]\) 表示在 \(u\) 的子树中,拿了 \(i\) 幅画所需的最少时间。答案是好统计的。
则有:

\[f[u][i+j]=f[l][i]+f[r][j]+2(a[l]+a[r]) \]

但是这个时候我们发现一个问题,如果在这个子树中不拿画的话,根本就不用进入这个子树,也就是转移式中的 \(2a\) 是不用加上的。解决方法是令 \(f[u][0]=-2a[u]\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2005;
int n,tot,f[maxn][maxn];
int a[maxn],b[maxn];
il int dfs(){
	int u=++tot;
	read(a[u])read(b[u]);
	if(b[u]){
		for(int i=0;i<=b[u];i++){
			f[u][i]=i*5;
		}
	}
	else{
		int l=dfs();
		int r=dfs();
		b[u]=b[l]+b[r];
		for(int i=0;i<=b[l];i++){
			for(int j=0;j<=b[r];j++){
				f[u][i+j]=min(f[u][i+j],f[l][i]+f[r][j]+((a[l]+a[r])<<1));
			}
		}
	}
	f[u][0]=-a[u]<<1;
	return u;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
//	freopen("P1270_5.in","r",stdin);
	read(n);
	memset(f,0x3f,sizeof f);
	int rt=dfs();
	for(int i=b[rt];~i;i--){
		if(f[rt][i]+(a[rt]<<1)<n){
			printf("%d",i);
			return 0;
		}
	}
}
}
int main(){return asbt::main();}

D. 「HAOI2015」树上染色
考虑一条边被经过的次数,等于边两边黑点个数之积加上白点个数之积。于是设 \(f[u][i]\) 表示在 \(u\) 的子树中选了 \(i\) 个点,并且考虑了 \(u\) 的子树中的边的答案。这是一个经典的树上背包。于是有
\( f[u][j+k]=\max(f[u][j]+f[v][k]+w\times (k\times (m-k)+(sz[v]-k)\times (n-sz[v]-m+k))) \)
答案即为 \(f[1][m]\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define int ll
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e3+5;
int n,m,f[maxn][maxn],sz[maxn],nf[maxn];
vector<pii> e[maxn];
il void dfs(int u,int fa){
	sz[u]=1;
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		dfs(v,u);
		for(int j=0;j<=sz[u]+sz[v];j++){
			nf[j]=0;
		}
		for(int j=0;j<=sz[u];j++){
			for(int k=0;k<=sz[v];k++){
				nf[j+k]=max(nf[j+k],f[u][j]+f[v][k]+w*(k*(m-k)+(sz[v]-k)*(n-sz[v]-m+k)));
			}
		}
		sz[u]+=sz[v];
		for(int j=0;j<=sz[u];j++){
			f[u][j]=nf[j];
		}
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	read(n)read(m);
	for(int i=1,u,v,w;i<n;i++){
		read(u)read(v)read(w);
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	dfs(1,0);
	printf("%lld",f[1][m]);
	return 0;
}
}
signed main(){return asbt::main();}

E. Tree Array
考虑首先枚举根,然后计算每个逆序对的贡献。
发现对于一对点 \(u,v\),从根到 \(lca(u,v)\) 的过程是相同的,因此只需要计算 \(lca(u,v)\to u\)\(lca(u,v)\to v\) 的过程,最终使得先到达编号较大的就行了。
发现只用考虑对答案有用的部分,其他地方不用关心。然后对于每一步,向 \(u\) 走一步和向 \(v\) 走一步的概率是相同的。因此只用考虑这两条路径的长度。设 \(f[i][j]\) 表示 \(u\) 路径长为 \(i\)\(v\) 路径长为 \(j\),先走到 \(u\) 的概率。有方程:

\[f[i][j]=\frac{f[i-1][j]+f[i][j-1]}2 \]

对于每对点 \(u,v\)\(u>v\),贡献即为 \(f[dep[u]-dep[lca(u,v)]][dep[v]-dep[lca(u,v)]]\)
因为是枚举根,所以最后要除以 \(n\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=205,mod=1e9+7;
int n,dep[maxn],top[maxn],hes[maxn];
int sz[maxn],fa[maxn],f[maxn][maxn];
vector<int> e[maxn];
il void dfs1(int u){
	dep[u]=dep[fa[u]]+1;
	sz[u]=1;
	int mxs=0;
	for(int v:e[u]){
		if(v==fa[u]){
			continue;
		}
		fa[v]=u;
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	if(!top[u]){
		top[u]=u;
	}
	if(hes[u]){
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(int v:e[u]){
		if(v==fa[u]||v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		u=fa[top[u]];
	}
	return dep[u]>dep[v]?v:u;
}
il int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		y>>=1,x=x*1ll*x%mod;
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=1,u,v;i<n;i++){
		read(u)read(v);
		e[u].pb(v),e[v].pb(u);
	}
	int inv2=qpow(2,mod-2);
	for(int i=0;i<=n;i++){
		f[0][i]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=(f[i-1][j]+f[i][j-1])*1ll*inv2%mod;
		}
	}
	int ans=0;
	for(int rt=1;rt<=n;rt++){
		for(int i=1;i<=n;i++){
			dep[i]=top[i]=hes[i]=sz[i]=fa[i]=0;
		}
		dfs1(rt),dfs2(rt);
		for(int i=1;i<=n;i++){
			for(int j=1;j<i;j++){
				int tmp=lca(i,j);
				(ans+=f[dep[i]-dep[tmp]][dep[j]-dep[tmp]])%=mod;
			}
		}
	}
	printf("%lld",ans*1ll*qpow(n,mod-2)%mod);
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2024-12-15 13:56  zhangxy__hp  阅读(45)  评论(0)    收藏  举报