题目大意:求基环树上的最大点权独立集。
题解:先考虑普通树的最大点权独立集:dp[i][0] 表示不选 i 点,以 i 为根的子树的最大点权独立集,dp[i][1] 表示选 i 点,以 i 为根的子树的最大点权独立集,然后简单树形DP,对于根结点取选和不选中的较大值。基环树,多了对环的处理。
思路一:对环上每一个点进行简单树形DP,然后将环上的点提出来做一遍环形DP,处理方法是将环断开,断开的边的两个点必然有一个不能选,枚举哪个点不选,然后做线性DP,结果取最大值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
int nxt[2 * maxn],to[maxn * 2],head[maxn],cnt;
int a[maxn],n;
int f[maxn],dfn[maxn],sz,vis[maxn];
int v[maxn],top;
ll dp[maxn][2];
ll dpp[maxn][2];
void init() {
sz = top = cnt = 0;
fill(head,head + n + 1,-1);
}
void add(int u,int v) {
to[cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt++;
}
void getloop(int u,int fa) {
dfn[u] = ++sz;
bool t = false;
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == fa && !t) {
t = true;continue;
}
if(!dfn[to[i]]) {
f[to[i]] = u;
getloop(to[i],u);
}
else if(dfn[to[i]] < dfn[u]) continue;
else if(dfn[to[i]] > dfn[u]) {
v[++top] = to[i];
vis[to[i]] = 1;
for(int p = to[i]; p != u; p = f[p]) {
v[++top] = f[p];
vis[f[p]] = 1;
}
}
}
}
void dfs(int u,int fa) {
dp[u][0] = 0;
dp[u][1] = a[u];
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == fa || vis[to[i]]) continue;
dfs(to[i],u);
dp[u][0] += max(dp[to[i]][0],dp[to[i]][1]);
dp[u][1] += dp[to[i]][0];
}
}
int main() {
scanf("%d",&n);
int x,y;
init();
for(int i = 1; i <= n; i++) {
scanf("%d%d",&x,&y);
a[i] = x;
add(i,y);add(y,i);
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
if(!dfn[i]) {
sz = top = 0;
getloop(i,0);
ll res = 0;
for(int i = 1; i <= top; i++)
dfs(v[i],0);
dpp[1][0] = dp[v[1]][0];dpp[1][1] = dp[v[1]][1];
for(int i = 2; i <= top; i++) {
dpp[i][0] = dp[v[i]][0];
dpp[i][1] = dp[v[i]][1];
dpp[i][1] += dpp[i - 1][0];
dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
}
res = max(res,dpp[top][0]);
dpp[top][0] = dp[v[top]][0];dpp[top][1] = dp[v[top]][1];
for(int i = top - 1; i >= 0; i--) {
dpp[i][0] = dp[v[i]][0];
dpp[i][1] = dp[v[i]][1];
dpp[i][1] += dpp[i + 1][0];
dpp[i][0] += max(dpp[i + 1][0],dpp[i + 1][1]);
}
res = max(res,dpp[1][0]);
ans += res;
}
}
printf("%lld\n",ans);
return 0;
}
思路二:先将环断开,对于断开的那条边(u,v),保证根结点不选,分别以 u 为根和 以 v为根做树形DP,结果取最大值。
(一和二其实是一样的)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const ll inf = 1e15;
int nxt[2 * maxn],to[maxn * 2],head[maxn],cnt;
int a[maxn],n;
int vis[maxn];
int v[maxn],top,fa[maxn];
ll dp[maxn][2];
void init() {
top = cnt = 0;
fill(head,head + n + 1,-1);
}
void add(int u,int v) {
to[cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt++;
}
void dfs(int rt,int u,int s) {
dp[u][0] = 0;
dp[u][1] = a[u];
vis[u] = 1;
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == s || to[i] == rt) continue;
else {
dfs(rt,to[i],u);
dp[u][0] += max(dp[to[i]][0],dp[to[i]][1]);
dp[u][1] += dp[to[i]][0];
}
}
}
ll solve(int x) {
int rt = x;
vis[rt] = 1;
ll res = 0;
while(!vis[fa[rt]]) {
vis[fa[rt]] = 1;
rt = fa[rt];
}
dfs(rt,rt,0);res = max(res,dp[rt][0]);
dfs(fa[rt],fa[rt],0);res = max(res,dp[fa[rt]][0]);
return res;
}
int main() {
scanf("%d",&n);
int x,y;
init();
for(int i = 1; i <= n; i++) {
scanf("%d%d",&a[i],&fa[i]);
add(fa[i],i);
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
if(!vis[i])
ans += solve(i);
}
printf("%lld\n",ans);
return 0;
}
反思:写第一种方法wa了一晚上,结果是环形DP处理方法错了。对于这题在环型上DP,可以枚举固定一个点的状态,然后变成线性DP,和上面枚举哪个点不选的方法是等价的,第一种方法可以改成这样。
dpp[1][0] = dp[v[1]][0];dpp[1][1] = dp[v[1]][1];
for(int i = 2; i <= top; i++) {
dpp[i][0] = dp[v[i]][0];
dpp[i][1] = dp[v[i]][1];
dpp[i][1] += dpp[i - 1][0];
dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
}
res = max(res,dpp[top][0]);
dpp[1][0] = dp[v[1]][0];dpp[1][1] = -1e15;
for(int i = 2; i <= top; i++) {
dpp[i][0] = dp[v[i]][0];
dpp[i][1] = dp[v[i]][1];
dpp[i][1] += dpp[i - 1][0];
dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
}
res = max(res,dpp[top][1]);
第二种方法是直接断链做树形DP,不能去枚举根结点选和不选做树形DP,因为树形DP是自底向上转移,根节点的状态不影响子节点的状态转移。