[jzoj]5966. 【NOIP2018提高组D2T3】保卫王国(矩阵乘法+链剖维护线段树 或 倍增DP)

本文介绍了一种解决树上动态询问最小覆盖集问题的方法,通过树链剖分、线段树和矩阵转移实现。文章详细解释了如何将问题转化为求最大独立集,并使用动态DP算法解决。通过矩阵乘法的巧妙应用,实现了高效的查询和更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem

  • 弱化版动态询问一棵树的最小覆盖集.

  • 每次只选择其中某两个点必选或必不选,且询问独立.

Data constraint

  • n,m≤105n,m\le 10^5n,m105

Solution

【动态DP = 树链剖分 + 线段树 + 矩阵转移】
  • 这好像是WC2018WC_{2018}WC2018搞出来的一个新玩意儿,挺神奇的.

  • 首先,最小覆盖集 = 全集 - 最大独立集.

  • 其次,一个点必选或不必选,可以直接把他的权值赋值为无穷大,或无穷小.

  • 这样,问题完美转化为求解一棵树的最大独立集.

  • 一个小学四年级的DP是f[i][0/1]f[i][0/1]f[i][0/1]表示iii点选或不选,最大收益.

  • 先树剖一下,然后一个初三的DP是,设一个g[i][0/1]g[i][0/1]g[i][0/1]表示与fff含义相同,唯独不能用重儿子去转移.

  • 这样有什么好处?

  • 我们不妨用iii表示当前要计算fff的点,假设它在线段树上的编号也是iii,那么它的重儿子在线段树上的编号就是i+1i+1i+1.

  • 那么你可以写出这样的两条式子:f[i][0]=g[i][0]+max(f[i+1][0],f[i+1][1])f[i][0] = g[i][0] + max(f[i+1][0], f[i+1][1])f[i][0]=g[i][0]+max(f[i+1][0],f[i+1][1])f[i][1]=g[i][1]+f[i+1][0]f[i][1] = g[i][1] + f[i+1][0]f[i][1]=g[i][1]+f[i+1][0].

  • 然后神奇的一步出现,你考虑把它写成矩阵转移的形式,会变成:[gi,0gi,0gi,10]∗[fi+1,0fi+1,1]=[fi,0fi,1]\begin{bmatrix}g_{i, 0} & g_{i, 0} \\g_{i, 1} & 0\end{bmatrix} * \begin{bmatrix}f_{i + 1, 0} \\ f_{i + 1, 1}\end{bmatrix} = \begin{bmatrix}f_{i, 0} \\ f_{i, 1}\end{bmatrix}[gi,0gi,1gi,00][fi+1,0fi+1,1]=[fi,0fi,1]

  • 这里需要注意,普通的矩阵乘法形式是形如:Ci,j=∑k=1nAi,k∗Bk,jC_{i, j} = \sum_{k = 1}^{n} A_{i, k} * B_{k, j}Ci,j=k=1nAi,kBk,j

  • 而这里,我们把它改变一下,变成Ci,j=max⁡k=1n(Ai,k+Bk,j)C_{i, j} = \max_{k = 1}^{n} (A_{i, k} + B_{k, j})Ci,j=k=1maxn(Ai,k+Bk,j)

  • 并且发现后者也是满足所谓的结合律的.

  • 那么不难发现,其实一个点的答案,就是它所在重链的每一个节点的g矩阵乘起来.

  • 因为满足结合律,所以刚好可以用链剖+线段树来维护.

  • 然后考虑修改,假设修改一个点xxx的权值,那么其实父亲ggg矩阵不会改变,I.e.其所在重链的所有祖先的ggg矩阵不会改变.

  • 会改变的仅仅是xxx本身的ggg矩阵,以及所在重链每一个祖先的fff值(答案).

  • 因为会影响祖先的fff值,所以在经过完这条重链后走一条轻边到达另一条重链时,会影响到那条链上的ggg矩阵.

  • 所以我们的修改步骤就很明确了:

    • 先修改当前权值,然后处理一下ggg矩阵.

    • 然后进入循环,一直向顶端去修改.

  • 具体实现的时候,实际上我们可以记录一个b[x]b[x]b[x]表示xxx这个点在线段树上的编号.

  • 那么修改一个点的ggg矩阵直接修改即可,然后暴力的往上跳,进行合并即可.

  • 这样常数会小很多,且可以优化一下这个转移,把它不要真的写成取max,这样太慢.

  • 参考代码:

#include <cstdio>
#include <cstring>
#include <iostream>

#define ll long long
#define F(i, a, b) for (ll i = a; i <= b; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)
#define mx(a, b) ((a) = max(a, b))
#define get getchar()

#define M (st + en >> 1)
#define Ls (x << 1)
#define Rs (Ls | 1)

const ll N = 2e5 + 10, T = 4 * N, W = 1e15;

using namespace std;

ll n, m, u, v, a, x, b, y, t, cnt, Sum, sum, DFN[N], V[N]; char ch[10];
ll sz[N], fa[N], Son[N], top[N], dfn[N], f[N][2], B[N], D[N];
ll tov[T], nex[T], las[N], tot, S0, S1;

struct mat {
	ll g[2][2];
	mat() { mem(g, 0); }
	inline mat operator * (const mat &b) const { mat c;
		c.g[0][0] = max(g[0][0] + b.g[0][0], g[0][1] + b.g[1][0]);
		c.g[0][1] = max(g[0][0] + b.g[0][1], g[0][1] + b.g[1][1]);
		c.g[1][0] = max(g[1][0] + b.g[0][0], g[1][1] + b.g[1][0]);
		c.g[1][1] = max(g[1][0] + b.g[0][1], g[1][1] + b.g[1][1]);
		return c;
	}
} tr[T], TR[T], ANS, LAS, NOW, PRE, SON;

inline void Re(ll &x) {
	char c = get; x = 0; ll t = 1;
	for (; !isdigit(c); c = get) t = (c == '-' ? - 1 : t);
	for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = get); x *= t;
}
inline void Wr(ll x) {
	if (x < 0) { putchar('-'); x = - x;	}
	if (x > 9) Wr(x / 10); putchar(x % 10 + '0');
}

void ins(ll x, ll y) { tov[++ tot] = y, nex[tot] = las[x], las[x] = tot;}
void Dfs(ll k) {
	sz[k] ++;
	for (ll x= las[k], Hv = 0; x ; x = nex[x])
		if (!sz[tov[x]]) fa[tov[x]] = k, Dfs(tov[x]), Son[k] = sz[tov[x]] > Hv ? tov[x] : Son[k], mx(Hv, sz[tov[x]]), sz[k] += sz[tov[x]];
}
void Dfn(ll k) {
	dfn[k] = DFN[k] = ++ cnt;
	if (Son[k]) top[Son[k]] = top[k], Dfn(Son[k]), DFN[k] = DFN[Son[k]];
	for (ll x = las[k] ; x ; x = nex[x])
		if (tov[x] ^ Son[k] && sz[tov[x]] < sz[k])
			top[tov[x]] = tov[x], Dfn(tov[x]);
}

void Modify(ll x, ll st, ll en, ll p) {
	if (st == en) B[st] = x;
		else
	M >= dfn[p] ? Modify(Ls, st, M, p) : Modify(Rs, M + 1, en, p);
}
void Down(int x) { for (; x > 1 ; x >>= 1, tr[x] = tr[Ls] * tr[Rs]); }

void DP(ll k) {
	for (ll x = las[k] ; x ; x = nex[x])
		if (sz[k] > sz[tov[x]]) DP(tov[x]);
	for (ll x = las[k], y; x ; x = nex[x])
		if (sz[k] > sz[y = tov[x]]) {
			f[k][0] += max(f[y][0], f[y][1]);
			f[k][1] += f[y][0];
		}
	f[k][1] += V[k];

	tr[B[dfn[k]]].g[0][0] = tr[B[dfn[k]]].g[0][1] = f[k][0] - max(f[Son[k]][0], f[Son[k]][1]);
	tr[B[dfn[k]]].g[1][0] = f[k][1] - f[Son[k]][0], tr[B[dfn[k]]].g[1][1] = - W;

	Down(B[dfn[k]]);
}

mat Query(ll x, ll st, ll en, ll l, ll r) {
	if (l <= st && en <= r) return tr[x];
	if (r <= M) return Query(Ls, st, M, l, r);
	if (l > M) return Query(Rs, M + 1, en, l, r);
	return Query(Ls, st, M, l, r) * Query(Rs, M + 1, en, l, r);
}

void Update(ll x, ll y) {
	LAS = Query(1, 1, n, dfn[top[x]], DFN[top[x]]), tr[B[dfn[x]]].g[1][0] += y;
	Down(B[dfn[x]]), D[++ D[0]] = x;
	while (fa[top[x]]) {
		ANS = Query(1, 1, n, dfn[top[x]], DFN[top[x]]); t = top[x], x = fa[t];
		if (top[x]) PRE = Query(1, 1, n, dfn[top[x]], DFN[top[x]]);
		NOW = Query(1, 1, n, dfn[x], DFN[x]);
		SON = Query(1, 1, n, dfn[Son[x]], DFN[Son[x]]);
		tr[B[dfn[x]]].g[0][0] = tr[B[dfn[x]]].g[0][1] =
			NOW.g[0][0] - max(SON.g[0][0], SON.g[1][0]) - max(LAS.g[0][0], LAS.g[1][0]) + max(ANS.g[0][0], ANS.g[1][0]);
		tr[B[dfn[x]]].g[1][0] =
			NOW.g[1][0] - SON.g[0][0] - LAS.g[0][0] + ANS.g[0][0];
		Down(B[dfn[x]]), LAS = PRE, D[++ D[0]] = x;
	}
}

int main() {
	freopen("defense.in", "r", stdin);
	freopen("defense.out", "w", stdout);

	Re(n), Re(m); scanf("%s", ch + 1);
	F(i, 1, n) Re(V[i]), sum += V[i];
	F(i, 1, n - 1) Re(u), Re(v), ins(u, v), ins(v, u);

	Dfs(1), top[1] = 1, Dfn(1);
	F(i, 1, n) Modify(1, 1, n, i);
	DP(1), mec(TR, tr);

 	F(i, 1, m) {
		Re(a), Re(x), Re(b), Re(y), Sum = 0, D[0] = 0;
		if ((fa[a] == b || fa[b] == a) && x + y == 0) { puts("-1"); continue; }
		if (x == 0) Update(a, W), Sum += W; else Update(a, - W);
		if (y == 0) Update(b, W), Sum += W; else Update(b, - W);
		ANS = Query(1, 1, n, 1, DFN[1]), Wr(sum - (max(ANS.g[0][0], ANS.g[1][0]) - Sum)), putchar('\n');
		F(i, 1, D[0]) tr[B[dfn[D[i]]]] = TR[B[dfn[D[i]]]], Down(B[dfn[D[i]]]);
	}
}

【倍增DP】
  • 待学
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值