[反悔贪心] [dp] P8352 [SDOI_SXOI2022] 小 N 的独立集
posted on 2024-05-15 05:18:53 | under | source
题解只有 \(\rm dp\) 套 \(\rm dp\) 做法?来补充下丑陋的 \(\rm dp\) 套反悔贪心方法。
显然,定义 \(f_{u,i}\) 表示 \(u\) 子树、最大权独立集为 \(i\) 的情况数。那么第二维怎么定义呢?
最大权独立集可以 \(\rm dp\) 解决,尝试再套个 \(\rm dp\) 解决。
但是 \(\rm dp\) 是以顶部取不取为状态,但是“取不取”是人为决定的,而我们要求的是不同树的种类数,似乎不太好处理重复情况。
总之,蒟蒻太菜了,不会 \(\rm dp\) 套 \(\rm dp\),至此走上一条歪路。
众所周知,反悔贪心也能解决最大权独立集。具体来说,令 \(g_i\) 表示由取 \(i\to\) 不取 \(i\) 的代价。然后对于 \(u\),令 \(res=\sum\limits_{v\in son_u} g_v\),若 \(a_u\ge res\) 则取 \(u\),\(g_u=a_u-res\);反之不取 \(u\),\(g_u=0\)。
显然不同树的 \(g\) 是唯一确定的。同时 \(g_u\) 只有 \(\le a_u\) 才有意义,且 \(g_u\) 不可能为 \(0\),这是可以归纳验证的。总之 \(0\le g\le a\)。
注意到 \(a_u\le k\),\(k\) 非常小,所以以 \(g\) 为第二维。定义 \(f_{u,i,j}\) 表示 \(u\) 子树、最大权独立集为 \(i\)、\(g_u=j\) 时的情况数。
转移是容易的。可以先不考虑 \(a_u\) 填什么,计算出 \(f_{u,i,j}\)。然后枚举 \(a_u\),按照反悔贪心过程转移就好啦。
复杂度 \(O(n^2k^4)\)。
卡常注意事项:
-
注意卡好循环上界;特判到 \(0\) 直接跳过;
-
可以稍微更改下 \(f\) 定义,一开始就把 \(a_u\) 考虑进来。
魔改完代码后发现和 \(\rm dp\) 套 \(\rm dp\) 做法极其相似!
看了看,发现另一种做法的状态优化思路和反悔贪心貌似有着异曲同工之妙。
代码
#include<bits/stdc++.h>
using namespace std;
#define ADD(a, b) a = (a + (b)) % mod
const int N = 1e3 + 5, K = 5, mod = 1e9 + 7;
int n, k, u, v;
int siz[N], f[N][N * K][K + 1], G[N][N * K][K + 1];
vector<int> to[N];
inline void dfs(int u, int fa){
siz[u] = 1;
for(int uk = 1; uk <= k; ++uk) f[u][uk][uk] = 1;
for(auto v : to[u])
if(v ^ fa){
dfs(v, u);
for(int j = 0; j <= siz[u] * k; ++j)
for(int p = 0; p <= k; ++p) G[u][j][p] = f[u][j][p], f[u][j][p] = 0;
for(int j = 0; j <= siz[v] * k; ++j)
for(int p = 0; p <= k; ++p)
if(f[v][j][p])
for(int j2 = 0; j2 <= siz[u] * k; ++j2)
for(int p2 = 0; p2 <= k; ++p2)
ADD(f[u][j + j2 - min(p, p2)][max(0, p2 - p)], 1ll * f[v][j][p] * G[u][j2][p2] % mod);
siz[u] += siz[v];
}
}
signed main(){
cin >> n >> k;
for(int i = 1; i < n; ++i) scanf("%d%d", &u, &v), to[u].push_back(v), to[v].push_back(u);
dfs(1, 0);
for(int i = 1; i <= n * k; ++i){
int res = 0;
for(int p = 0; p <= min(i, k); ++p) ADD(res, f[1][i][p]);
printf("%d\n", res);
}
return 0;
}

浙公网安备 33010602011771号