树形dp 题解
SAO
题意 让你求有向图的拓扑序个数,保证这个有向图忽略掉边的方向,可以构成一颗树。
题解
你直接在有向图上面怎么怎么dp一下显然是不行的,然后,你发现,它是在树形dp作业里面,所以考虑树形dp。
我们考虑设 dp[i][j] 表示在最终的拓扑序中,\(i\) 前面有 \(j\) 个。
然后你考虑如何转移。
我们设 \(v\) 是 \(u\) 的儿子,当前要从 \(u\) 转移到 \(v\)。
我们对着这两个点之间的关系进行分类讨论。
首先考虑 \(u\) 要在 \(v\) 前面的情况。
假设 \(u\) 前面有 \(i\) 个点,\(v\) 前面有 \(j\) 个点。
那么,首先 \(u\) 和 \(v\) 的前面的点一定是没有互相影响的对吧,这个你直接对着这些点分类讨论一下,一共只有几种情况,然后你就能证出来了。
所以你可以直接暴力枚举 \(v\) 前面的点里面有 \(k\) 个点是在 \(u\) 前面的,那么选出来这 \(k\) 个点往里面塞的方案即是 \(C_{i+k}^{k}\),然后 \(u\) 后面的方案数即是 \(C_{siz_u+siz_v-i-j-1}^{siz_v-k}\)。
那么转移的方程式就很显然了。
然后另一种情况同理,于是你得到了一个暴力 \(O(n^3)\) 的代码。
于是前缀和优化之,然后做完了呀。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
const int N=1010,mod=1e9+7;
int a[N],siz[N],dp[N][N],lx[N],inv[N],g[N],sum[N];
vector<pair<int,int> >e[N];
int read() {int x;cin>>x;return x;}
int ksm(int x,int y) {int t=1;while(y){if(y&1)t=t*x%mod;x=x*x%mod;y>>=1;}return t;}
int C(int x,int y) {if(x<y) return 0;return lx[x]*inv[y]%mod*inv[x-y]%mod;}
void dfs(int x,int fa) {
siz[x]=1;
// memset(dp[x],1,sizeof(dp[x]));
// cerr<<x<<" "<<fa<<endl;
for(auto y:e[x]) {
if(y.first==fa) continue;
dfs(y.first,x);
memcpy(g,dp[x],sizeof(g));
memset(dp[x],0,sizeof(dp[x]));
for(int i=1;i<=siz[y.first];i++) {
sum[i]=sum[i-1]+dp[y.first][i];
sum[i]%=mod;
}
if(y.second==1) {
for(int i=1;i<=siz[x];i++) {
for(int j=0;j<siz[y.first];j++) {
if(!g[i]) continue;
dp[x][i+j]+=(g[i]*(sum[siz[y.first]]-sum[j]+mod)%mod*C(i+j-1,j)%mod*C(siz[x]+siz[y.first]-i-j,siz[x]-i)%mod)%mod;
dp[x][i+j]%=mod;
}
}
}
else {
for(int i=1;i<=siz[y.first];i++) {
for(int k=1;k<=siz[x];k++) {
dp[x][i+k]+=(g[k]*(sum[i])%mod*C(i+k-1,k-1)%mod*C(siz[x]+siz[y.first]-i-k,siz[y.first]-i))%mod;
dp[x][i+k]%=mod;
}
// }
}
}
siz[x]+=siz[y.first];
}
}
void work() {
n=read();
lx[0]=1;
for(int i=1;i<n;i++) {
int u,v,f=0;
char c;
u=read();cin>>c;v=read();
u++;v++;
if(c=='<') f=1;
e[u].push_back(make_pair(v,f));
e[v].push_back(make_pair(u,f^1));
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
dp[i][j]=1;
}
}
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++) {
ans=(ans+dp[1][i])%mod;
e[i].clear();siz[i]=0;
}
cout<<ans<<endl;
}
signed main() {
lx[1]=1;
for(int i=2;i<N;i++) {
lx[i]=lx[i-1]*i%mod;
}
inv[N-1]=ksm(lx[N-1],mod-2);
for(int i=N-2;i>=0;i--) {
inv[i]=(inv[i+1]*(i+1))%mod;
}
int t=read();
while(t--) work();
return 0;
}
ARC179D
题意
给你一棵树,走过每条边都要花费 \(1\) 的代价,然后你还有一个传送门,你可以把传送门放在你当前遍历到的节点上,然后继续走,每次从当前节点回到传送门所在节点是不需要代价的,求遍历整棵树的最小代价。
解法
首先啊,根据一贯的套路,是要先从根节点dp一遍,然后跑换根dp的。
所以考虑从根节点出发怎么做。
鉴于题目里面说了,不需要你再回到出发点,所以这启示我们状态里面要有一维表示是否要回到根节点。
然后你发现,放传送门和不放传送门区别很大,所以你给状态里面又加了一维,表示传送门放没放在根节点上。
所以最后,你设 \(dp_{i,0/1,0/1}\) 表示把 \(i\) 的子树都遍历一遍,是否要回到根节点,根节点上放不放传送门的最小代价。
转移是显然的。
然后,你只需要换根就好了。
换根的步骤很经典了对吧,我们只需要每次清楚当前子节点对父节点的贡献,然后给当前子节点加上父节点的贡献即可。
于是,恭喜你,你做完了。
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=2e5+100;
// int dp[N][2][2];
int dp[N][2][2],siz[N];
vector<int>e[N];
int ans=1e9;
struct node {
int fir,sec;
int get(int x) {
if(x!=fir) return fir;
return sec;
}
}maxx[N],len[N];
int read() {int x;cin>>x;return x;}
void dfs(int x,int fa) {
siz[x]=1;
for(auto y:e[x]) {
if(y==fa) continue;
dfs(y,x);
siz[x]+=siz[y];
dp[x][1][1]+=min(dp[y][1][1]+2,dp[y][0][0]+1);
int xx=min(dp[y][1][1]+2,dp[y][0][0]+1);
xx-=dp[y][0][1]+1;
if(xx>maxx[x].fir) {
maxx[x].sec=maxx[x].fir;
maxx[x].fir=xx;
}
else {
maxx[x].sec=max(maxx[x].sec,xx);
}
xx=len[y].fir+1;
if(xx>len[x].fir) {
len[x].sec=len[x].fir;
len[x].fir=xx;
}
else {
len[x].sec=max(len[x].sec,xx);
}
}
dp[x][0][1]=dp[x][1][1]-maxx[x].fir;
dp[x][1][0]=2*siz[x]-2;
dp[x][0][0]=dp[x][1][0]-len[x].fir;
}
void DP(int x,int fa) {
ans=min(ans,min(min(dp[x][0][1],dp[x][1][0]),min(dp[x][0][0],dp[x][1][1])));
for(auto y:e[x]) {
if(y==fa) continue;
int len1=len[x].get(len[y].fir+1);
int maxx1=maxx[x].get(min(dp[y][1][1]+2,dp[y][0][0]+1)-dp[y][0][1]-1);
int tmp[2][2];
tmp[0][0]=dp[x][0][0];
tmp[0][1]=dp[x][0][1];
tmp[1][0]=dp[x][1][0];
tmp[1][1]=dp[x][1][1];
dp[x][1][0]=2*(n-siz[y])-2;
dp[x][0][0]=dp[x][1][0]-len1;
dp[x][1][1]-=min(dp[y][1][1]+2,dp[y][0][0]+1);
dp[x][0][1]=dp[x][1][1]-maxx1;
dp[y][1][0]=2*n-2;
if(len1+1>len[y].fir) {
len[y].sec=len[y].fir;
len[y].fir=len1+1;
}
else {
len[y].sec=max(len[y].sec,len1+1);
}
dp[y][0][0]=dp[y][1][0]-len[y].fir;
dp[y][1][1]+=min(dp[x][1][1]+2,dp[x][0][0]+1);
if(min(dp[x][1][1]+2,dp[x][0][0]+1)-dp[x][0][1]-1>maxx[y].fir) {
maxx[y].sec=maxx[y].fir;
maxx[y].fir=min(dp[x][1][1]+2,dp[x][0][0]+1)-dp[x][0][1]-1;
}
else maxx[y].sec=max(maxx[y].sec,min(dp[x][1][1]+2,dp[x][0][0]+1)-dp[x][1][0]-1);
// maxx[y].upd(min(f[x][1][1]+2,f[x][0][0]+1)-f[x][1][0]-1);
dp[y][0][1]=dp[y][1][1]-maxx[y].fir;
DP(y,x);
dp[x][0][0]=tmp[0][0];
dp[x][0][1]=tmp[0][1];
dp[x][1][0]=tmp[1][0];
dp[x][1][1]=tmp[1][1];
}
}
signed main() {
n=read();
int u,v;
for(int i=1;i<n;i++) {
u=read();v=read();
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);
dp[1][0][0]=dp[1][0][0];
dp[1][1][1]=dp[1][1][1];
dp[1][1][0]=dp[1][1][0];
dp[1][0][1]=dp[1][0][1];
DP(1,0);
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号