原本打算做出来之后在洛谷上面交的,然后一看已经满了,所以就在别的地方发表吧。
打状态转移方程实在太辛苦了!!! \tiny \text{打状态转移方程实在太辛苦了!!!} 打状态转移方程实在太辛苦了!!!
题意:
给定一个无向基环树森林,点带点权,求其独立集的权值最大值。
为什么是无向基环树森林呢?因为一个骑士和他痛恨的骑士无论如何都不会一起被选上了,所以即使设前者也被后者痛恨也是没有影响的。
显然基环树之间是独立的,所以考虑在一棵基环树上面跑最大独立集。
很容易发现这道题的弱化版是 P1352 没有上司的舞会,大家可以先去做一下,这对独立思考此题至关重要。
好的,默认大家已经做完了。考虑从树上 dp 扩展到基环树上 dp。一般地,设深搜树上导致有环的那条回边为 u p → d n up \to dn up→dn,其中 u p up up 深度更低。
发现基环树只比普通的树上增加了一个额外的限制: u p up up 和 d n dn dn 不能同时取。所以当我们计算 u p up up 的 dp 值的时候,还需要考虑 d n dn dn 选了没选。
因为 d p dp dp 的状态制度是一直沿用的(也就是你一开始设了 dp[n][4]
以后也需要考虑其他点的 dp[n][4]
),所以所有包含 d n dn dn 的子树需要同时记录 子树的根结点取不取 和 d n dn dn 结点取不取。
于是这个时候就得出来了 d p dp dp 的状态:
设 d p i , 0 dp_{i,0} dpi,0 和 d p i , 1 dp_{i,1} dpi,1 表示不取 d n dn dn 的情况下, i i i 取 / 不取的最大答案。
设 d p i , 2 dp_{i,2} dpi,2 和 d p i , 3 dp_{i,3} dpi,3 表示取 d n dn dn 的情况下, i i i 取 / 不取的最大答案。
这个状态的表示已经足够全面:因为基环树的独立集只比树上的独立集多了一个东西。
显然,有时候如果 i i i 不在环上,也不在 u p up up 的祖先,即 i i i 的子树里面没有 d n dn dn,就需要进行一些初始化的约定(因为这个时候的 i i i 完全与多出来的限制无关,只需要求解两个状态就行了):
这时候就设 d p i , 1 = d p i , 3 dp_{i,1} = dp_{i,3} dpi,1=dpi,3,以及 d p i , 2 = d p i , 0 dp_{i,2} = dp_{i,0} dpi,2=dpi,0 即可。
另外,还有一个情况,也就是 i = d n i=dn i=dn 的情况:显然这个时候 d p i , 1 dp_{i,1} dpi,1 和 d p i , 2 dp_{i,2} dpi,2 是完全无意义的,要设为负无穷大。
考虑推导转移方程。
显然,在一开始对于所有的 i i i, d p i , 0 = d p i , 2 = 0 dp_{i,0} = dp_{i,2} = 0 dpi,0=dpi,2=0, d p i , 1 = d p i , 3 = v a l i dp_{i,1} = dp_{i,3} = val_i dpi,1=dpi,3=vali。其中 v a l i val_i vali 表示点权。
对于 i = d n i=dn i=dn, d p i , 1 = d p i , 2 = − ∞ dp_{i,1} = dp_{i,2} = -\infty dpi,1=dpi,2=−∞。
对于 i ≠ d n , i ≠ u p i \not = dn,i \not = up i=dn,i=up,枚举其子结点 x x x(没有被访问过):
不取环底, i i i 也不取: d p i , 0 + max ( d p x , 0 , d p x , 1 ) → d p i , 0 dp_{i,0}+\max(dp_{x,0},dp_{x,1}) \to dp_{i,0} dpi,0+max(dpx,0,dpx,1)→dpi,0。(自己都不取了,子结点取不取当然随意)
不取环底, i i i 取: d p i , 1 + d p x , 0 → d p i , 1 dp_{i,1}+dp_{x,0} \to dp_{i,1} dpi,1+dpx,0→dpi,1。(自己取了,子结点肯定不能取)
显然因为这个时候 i i i 和 d n dn dn 没有直接的联系,所以取环底和不取环底是一模一样的两种情况,即有:
d p i , 2 + max ( d p x , 2 , d p x , 3 ) → d p i , 2 dp_{i,2}+\max(dp_{x,2},dp_{x,3}) \to dp_{i,2} dpi,2+max(dpx,2,dpx,3)→dpi,2
d p i , 3 + d p x , 2 → d p i , 3 dp_{i,3}+dp_{x,2} \to dp_{i,3} dpi,3+dpx,2→dpi,3
对于 i = u p i = up i=up,同样枚举其子结点 x x x(没有被访问过):
d p i , 2 = d p i , 3 = − ∞ dp_{i,2} = dp_{i,3} = -\infty dpi,2=dpi,3=−∞。
d p i , 0 + max { d p x , 0 , d p x , 1 , d p x , 2 , d p x , 3 } → d p i , 0 dp_{i,0} + \max\{dp_{x,0},dp_{x,1},dp_{x,2},dp_{x,3}\} \to dp_{i,0} dpi,0+max{dpx,0,dpx,1,dpx,2,dpx,3}→dpi,0。
d p i , 1 + d p x , 0 → d p i , 1 dp_{i,1} + dp_{x,0} \to dp_{i,1} dpi,1+dpx,0→dpi,1。
对于答案,其就是对于每一个基环树的根结点 i i i,计算 max { d p i , 0 , d p i , 1 , d p i , 2 , d p i , 3 } \max\{dp_{i,0},dp_{i,1},dp_{i,2},dp_{i,3}\} max{dpi,0,dpi,1,dpi,2,dpi,3} 即可。
#include
#define int long long
using namespace std;
const int N = 1000010;
int n;
int val[N];
vector<int> v[N];
int dp[N][4];
bool stk[N], vis[N];
int up;
void dfs(int u, int pre) {
vis[u] = stk[u] = 1;
dp[u][1] = dp[u][3] = val[u], dp[u][0] = dp[u][2] = 0;
for (auto i : v[u])
if (!vis[i]) {
dfs(i, u);
if (u == up) {
dp[u][0] += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));
dp[u][1] += dp[i][0];
dp[u][2] = dp[u][3] = -1e16;
} else {
dp[u][0] += max(dp[i][0], dp[i][1]);
dp[u][1] += dp[i][0];
dp[u][2] += max(dp[i][2], dp[i][3]);
dp[u][3] += dp[i][2];
}
} else if (i != pre && stk[i])
up = i, dp[u][1] = dp[u][2] = -1e16;
stk[u] = 0;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> val[i] >> x;
v[i].push_back(x), v[x].push_back(i);
}
int ans = 0;
for (int i = 1; i <= n; i++)
if (!vis[i])
up = 0, dfs(i, 0), ans += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));
cout << ans << endl;
return 0;
}
一道蓝题做这么久,身败名裂!!!