Loading

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;
}



posted @ 2024-12-15 20:17  ShyGray  阅读(44)  评论(0)    收藏  举报