Educational Codeforces Round 172 (Rated for Div. 2)
E. Vertex Pairs
Solution
特别妙的题目……感觉自己根本想不出来。
后面部分 \(n\) 已经翻倍。首先一个非常重要的结论是,以 \(n\) 为根遍历树,找到 dfs 遍历回溯时第一个 \(2siz_u\geqslant n\) 的 \(u\) 为 \(\rm root\),可以证明 \(\rm root\) 一定在最终选取的集合中。当 \(2siz_u> n\) 时容易发现选取 \(n/2\) 个点一定会选到 \(\rm root\),我们需要稍微考虑 \(2siz_u=n\) 的情况:考虑 \(u\) 之外的节点全选可以达到 \(n/2\),但是要求合法的话,同时也说明直接选取 \(u\) 子树合法。但是 \(u\) 之外的节点一定包含 \(n\),由于二进制的性质,这一定是不优的。故结论得证。
于是以 \(\rm root\) 为根重新建树,我们找到相同数字的节点的 lca,可以保证 lca 到 \(\rm root\) 路径上的所有节点都要被选择,于是先选择。
然后从 \(n\) 到 1 遍历,ban 掉相同数字中编号更大的节点(如果编号更小的节点可以选取的话),同时记得 ban 掉编号更大节点的子树。
Code
嫖的网站上别人的提交(。
#include <bits/stdc++.h>
using namespace std;
const int c=500005, k=20;
int n, ossz, cent, si[c], f, szint[c], ert[c], ans[c], ut[c], par[c], fel[c][k];
vector<int> sz[c];
void dfs(int a, int el) {
si[a]=1;
for (auto x:sz[a]) {
if (x!=el) {
dfs(x, a);
si[a]+=si[x];
}
}
if (!cent && 2*si[a]>=n) {
cent=a;
}
}
void dfs2(int a, int el) {
for (int i=1; i<k; i++) {
fel[a][i]=fel[fel[a][i-1]][i-1];
}
for (auto x:sz[a]) {
if (x!=el) {
szint[x]=szint[a]+1;
fel[x][0]=a;
dfs2(x, a);
}
}
}
int lca(int a, int b) {
if (szint[a]<szint[b]) {
swap(a, b);
}
for (int i=k-1; i>=0; i--) {
if (szint[fel[a][i]]>=szint[b]) {
a=fel[a][i];
}
}
if (a==b) {
return a;
}
for (int i=k-1; i>=0; i--) {
if (fel[a][i]!=fel[b][i]) {
a=fel[a][i], b=fel[b][i];
}
}
return fel[a][0];
}
void add(int a) {
if (!a) {
return;
}
assert(ans[a]!=-1);
if (!ans[a]) {
ans[a]=1;
ossz++;
add(fel[a][0]);
}
}
void rem(int a) {
assert(ans[a]!=1);
if (!ans[a]) {
ans[a]=-1;
add(par[a]);
for (auto x:sz[a]) {
if (x!=fel[a][0]) {
rem(x);
}
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin >> n;
f=n;
n*=2;
for (int i=1; i<=n; i++) {
cin >> ert[i];
if (!ut[ert[i]]) {
ut[ert[i]]=i;
} else {
int s=ut[ert[i]];
par[i]=s, par[s]=i;
}
}
for (int i=1; i<n; i++) {
int a, b;
cin >> a >> b;
sz[a].push_back(b), sz[b].push_back(a);
}
dfs(n, 0);
szint[cent]=1;
dfs2(cent, 0);
add(cent);
for (int i=1; i<=n; i++) {
if (i<par[i]) {
add(lca(i, par[i]));
}
}
for (int i=n; i>=1; i--) {
if (!ans[i]) {
rem(i);
}
}
cout << ossz << "\n";
for (int i=1; i<=n; i++) {
if (ans[i]>0) {
cout << i << " ";
}
}
cout << "\n";
}
F. Two Subarrays
Solution
考虑只选择一个区间的时候是经典的线段树问题:维护 \(\text{maxv},L,R,\text{suma}\) 四个变量,其中 \(\rm maxv\) 是区间答案,\(L\) 表示前缀最大值(只计算前缀的 \(a\) 之和与前缀的右边界的 \(b\)),用于合并区间。
现在需要选取两个区间也是类似的:定义数组 \(dp_{0/1,0/1}\),其中 \(dp_{1,1}\) 表示已经选取两个区间的最大值,\(dp_{0,1}\) 表示右边区间已经选取,左边区间只有一个前缀(用于合并区间),而 \(dp_{0,0}\) 代表有一个前缀和一个后缀。维护时分类讨论即可。
Code
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; bool f=0; char s;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <set>
# include <queue>
# include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+5;
const ll infty = 1e18;
int n, a[MAXN], b[MAXN];
struct node {
ll dp[2][2], maxv, L, R, suma;
node operator + (const node& t) const {
node r;
r.suma = suma+t.suma;
r.maxv = max({maxv, t.maxv, R+t.L});
r.L = max(L, suma+t.L);
r.R = max(t.R, R+t.suma);
r.dp[1][1] = max({dp[1][1], t.dp[1][1], maxv+t.maxv, dp[1][0]+t.L, t.dp[0][1]+R});
r.dp[0][0] = max({dp[0][0]+t.suma, suma+t.dp[0][0], L+t.R});
r.dp[0][1] = max({dp[0][1], dp[0][0]+t.L, L+t.maxv, suma+t.dp[0][1]});
r.dp[1][0] = max({t.dp[1][0], R+t.dp[0][0], maxv+t.R, dp[1][0]+t.suma});
return r;
}
void cover(ll a, ll b) {
maxv = a+b*2;
L = R = a+b;
suma = a;
dp[0][0]=dp[0][1]=dp[1][0]=dp[1][1]=-infty;
}
} t[MAXN<<2];
void build(int o, int l, int r) {
if(l == r)
return t[o].cover(a[l], b[l]), void();
int mid = l+r>>1;
build(o<<1, l, mid), build(o<<1|1, mid+1, r);
t[o] = t[o<<1]+t[o<<1|1];
}
void modify(int o, int l, int r, int p) {
if(l == r)
return t[o].cover(a[l], b[l]), void();
int mid = l+r>>1;
if(p<=mid) modify(o<<1, l, mid, p);
else modify(o<<1|1, mid+1, r, p);
t[o] = t[o<<1]+t[o<<1|1];
}
node query(int o, int l, int r, int L, int R) {
if(l>=L && r<=R) return t[o];
int mid = l+r>>1;
if(L>mid)
return query(o<<1|1, mid+1, r, L, R);
if(R<=mid)
return query(o<<1, l, mid, L, R);
return query(o<<1, l, mid, L, R) + query(o<<1|1, mid+1, r, L, R);
}
int main() {
// freopen("r.in","r",stdin);
n = read(9);
for(int i=1; i<=n; ++i) a[i]=read(9);
for(int i=1; i<=n; ++i) b[i]=read(9);
build(1, 1, n);
for(int q=read(9); q; --q) {
int op=read(9), x=read(9), y=read(9);
if(op==1) a[x]=y, modify(1, 1, n, x);
else if(op==2) b[x]=y, modify(1, 1, n, x);
else {
node ans = query(1, 1, n, x, y);
print(ans.dp[1][1], '\n');
}
}
return 0;
}

浙公网安备 33010602011771号