题解 P8877 【[传智杯 #5 初赛] I-不散的宴会】
题解
压轴题。
容易发现这是一棵树。具体可以用归纳法。设前
容易发现这树很特殊。它的点数达到了
定义:我们选出树上一些点作为关键点。这些点包括第
图中标上红星的即为关键点。
断言:关键点的个数是
证明:考虑前
除了关键点,其他所有点的度数均为
把虚树建好后,可以用非常经典的树上最大点权独立集的动态规划做法在虚树上跑。我们关心的是两个关键点之间应该怎么转移。换言之,我们需要知道连接两个关键点的链它的贡献怎么计算。
记
假设有两个关键点
注意一个重要性质:
- 对于每一条连接了两个关键点的链,这条链上的点(不包含顶头的两个关键点),肯定在同一列上。
这同样容易证明。由于该树特殊的构造方法,对于
那么一条链可以被映射到
问题转化为了,查询对
对于
那么怎么对
我们预处理两个东西:
容易预处理上面的两个数组。现在要计算
解释:使用做差的方法计算出
最后讲讲怎么建虚树。维护
于是这题就做完了。
参考代码
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const i64 INF = 1e18;
int n;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
const int MAXN = 1e6 + 3;
const int MAXM = 2e6 + 3;
int R[MAXN], C[MAXN], A[MAXN], F[MAXN], G[MAXM], W[MAXM], o = 0;
int X[MAXM]; i64 U[MAXM][4];
int Y[MAXM]; i64 V[MAXM][4];
int P0[MAXN], Q0[MAXN], P1[MAXN], Q1[MAXN];
int value(int w){return w % 2 == 1 ? w / 2 + 1 : w / 2;}
void calc(int l, int r, i64 &w, bool t){ // [l, r] 区间
if(r - l + 1 == 0){w = 0 ; return;}
if(r - l + 1 == -1){w = 0 ; return;}
if(r - l + 1 == -2){w = -INF; return;}
if(t == false){
int u = min(Q0[l], r); w = P0[r] - P0[u] + ( R[l] == 1 ? value(u - l + 1) : 0);
} else {
int u = min(Q1[l], r); w = P1[r] - P1[u] + (!R[l] == 1 ? value(u - l + 1) : 0);
}
}
void calc(int l, int r, i64 O[4], bool t){ // [l + (0~1), r - (0~1)] 区间
calc(l , r , O[0b11], t);
calc(l , r - 1, O[0b10], t);
calc(l + 1, r , O[0b01], t);
calc(l + 1, r - 1, O[0b00], t);
}
i64 I[MAXM], J[MAXM]; // I 是必须选上,J 是必须不选
void dfs(int u){
if(X[u] == 0) I[u] = W[u], J[u] = 0; else {
int l = X[u], r = Y[u]; dfs(l), dfs(r);
I[u] = W[u]
+ max(U[u][0b00] + I[l], U[u][0b01] + J[l])
+ max(V[u][0b00] + I[r], V[u][0b01] + J[r]);
J[u] =
+ max(U[u][0b10] + I[l], U[u][0b11] + J[l])
+ max(V[u][0b10] + I[r], V[u][0b11] + J[r]);
}
}
int main(){
n = qread();
up(1, n, i) R[i] = qread();
up(1, n, i) C[i] = qread();
up(2, n, i) A[i] = qread();
P0[1] = R[1], P1[1] = !R[1];
up(2, n, i){
P0[i] = max(P0[i - 1], P0[i - 2] + R[i]);
P1[i] = max(P1[i - 1], P1[i - 2] + !R[i]);
}
Q0[n] = Q1[n] = n;
dn(n - 1, 1, i){
if( R[i] == 0) Q0[i] = i; else
if( R[i + 1] == 0) Q0[i] = i; else Q0[i] = Q0[i + 1];
if(!R[i] == 0) Q1[i] = i; else
if(!R[i + 1] == 0) Q1[i] = i; else Q1[i] = Q1[i + 1];
}
up(1, n - 1, i){
int t = A[i + 1], f = F[t]; // (i, t) 是特殊点
F[t] = F[i + 1] = ++ o; // 给特殊点分配编号
if(f)
if(X[f]) Y[f] = o, calc(G[f] + 1, i - 1, V[f], C[t]);
else X[f] = o, calc(G[f] + 1, i - 1, U[f], C[t]);
W[o] = R[i] ^ C[t], G[o] = i;
}
up(1, n, i){ // 最后一层都是特殊点
int t = i, f = F[t]; ++ o, W[o] = R[n] ^ C[i];
if(f)
if(X[f]) Y[f] = o, calc(G[f] + 1, n - 1, V[f], C[t]);
else X[f] = o, calc(G[f] + 1, n - 1, U[f], C[t]);
}
dfs(1); printf("%lld\n", max(I[1], J[1]));
return 0;
}