《AtCoder Beginner Contest 207 F - Tree Patrolling》
这题非常好。
dp[i][j][3] - 以i为根的子树保护了j个点的方案数,0 - 未放置自己,且没被子节点保护,1 - 未放置自己,被子节点保护,2 - 放置了自己.
这里的转移从所有子节点的状态转移过来来想比较好。
这里主要有一点就是会存在增加新节点的情况。
1:自己没有放置,但是被子节点覆盖了。
2:子节点没有放置,但是被父节点覆盖了。
这里的转移很显然是树形背包,理论上复杂度应该是n ^ 3.
这里有个非常好的优化,动态维护size,这样可以保证每对点都在LCA处合并了一次,复杂度即为nlogn。
还有就是为了去除重复计方案的情况,我们每次都开个空的数组f来存答案。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 2005; const int M = 2e3 + 5; const LL Mod = 1e9 + 7; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; int n,sz[N]; vector<int> G[N]; LL dp[N][N][3],f[N][3];//dp[i][j] - 以i为根的子树保护了j个点的方案数,0 - 未放置自己,且没被子节点保护,1 - 未放置自己,被子节点保护,2 - 放置了自己 LL ADD(LL a,LL b) { return (a + b) % Mod; } void dfs(int u,int fa) { sz[u] = 1; dp[u][0][0] = 1; dp[u][1][2] = 1; for(auto v : G[u]) { if(v == fa) continue; dfs(v,u); memset(f,0,sizeof(f)); for(int i = sz[u];i >= 0;--i) { for(int j = sz[v];j >= 0;--j) { f[i + j][0] = ADD(f[i + j][0],dp[u][i][0] * ADD(dp[v][j][0],dp[v][j][1]) % Mod); f[i + j + 1][1] = ADD(f[i + j + 1][1],dp[u][i][0] * dp[v][j][2] % Mod); f[i + j][1] = ADD(f[i + j][1],dp[u][i][1] * ADD(dp[v][j][2],ADD(dp[v][j][0],dp[v][j][1])) % Mod); f[i + j][2] = ADD(f[i + j][2],dp[u][i][2] * ADD(dp[v][j][1],dp[v][j][2]) % Mod); f[i + j + 1][2] = ADD(f[i + j + 1][2],dp[u][i][2] * dp[v][j][0] % Mod); } } for(int i = sz[u] + sz[v];i >= 0;--i) { dp[u][i][0] = f[i][0]; dp[u][i][1] = f[i][1]; dp[u][i][2] = f[i][2]; } sz[u] += sz[v]; } } int main() { scanf("%d",&n); for(int i = 1;i < n;++i) { int x,y;scanf("%d %d",&x,&y); G[x].push_back(y); G[y].push_back(x); } dfs(1,0); for(int i = 0;i <= n;++i) printf("%lld\n",(dp[1][i][0] + dp[1][i][1] + dp[1][i][2]) % Mod); system("pause"); return 0; }