NOI.ac2020省选模拟赛7

比赛链接

A.t1

problem

如果一棵\(n\)个节点的数,每个节点上有一个元素,元素为集合\(S\)中的元素,初始为\(a_1,a_2,a_3...a_n\)

\(S\)中的元素可以进行加法操作,这里加法满足:

  • 对于任意的两个元素,得到的结果仍然属于\(S\)
  • 满足交换律
  • 满足结合律
  • 满足幂等性(\(x+x=x\)对于任意的\(x\)成立)

现在输入\(Q\)次询问,每次输入两个节点\(s\)\(t\),你需要找出对应的树上路径,设路径上的点依次为\(c_1,c_2,...,c_m\),你需要输出\(a_{c_1}+a_{c_2}+a_{c_3}+...+a_{c_m}\)

你需要自己构造S数组。

\(n\le k\le 2000000\)

solution

题意就是构造一个不超过\(2000000\)的集合,每次询问的路径和都能用集合中的一个数表示。

利用倍增的思想,用\(st[i][j]\)表示从\(i\)往上跳\(2^j\)步的和用\(st[i][j]\)这个数表示。这样先预处理出来\(nlogn\)个元素,每次查询的时候,按照求\(LCA\)的方法向上跳,又只会增加不超过\(logn\)个节点。

code

/*
* @Author: wxyww
* @Date:   2020-06-07 09:44:14
* @Last Modified time: 2020-06-07 10:43:19
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 100010,logN = 20;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
struct node {
	int v,nxt;
}e[N << 1];
int head[N],ejs;
void add(int u,int v) {
	e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;
}
#define pi pair<int,int>
int lca[N][logN + 1],st[N][logN + 1],nownum;
vector<pi>ans;
int Upd(int x,int y) {
	if(!x) return y;
	if(!y) return x;
	ans.push_back(make_pair(x,y));
	return ++nownum;
}
int dep[N];
void dfs(int u,int fa) {
	dep[u] = dep[fa] + 1;
	for(int i = 1;i <= logN;++i) {
		lca[u][i] = lca[lca[u][i - 1]][i - 1];
		st[u][i] = Upd(st[u][i - 1],st[lca[u][i - 1]][i - 1]);
		if(!lca[u][i]) break;
	}

	for(int i = head[u];i;i = e[i].nxt) {
		int v = e[i].v;
		if(v == fa) continue;
		lca[v][0] = u;
		st[v][0] = v;
		dfs(v,u);
	}
}

int ansss[N];
int query(int x,int y) {
	if(dep[x] < dep[y]) swap(x,y);
	int ret = 0;
	for(int i = logN;i >= 0;--i) {
		if(dep[lca[x][i]] >= dep[y]) {
			ret = Upd(ret,st[x][i]);
			x = lca[x][i];
		}
	}
	if(x == y) return Upd(ret,st[x][0]);
	
	for(int i = logN;i >= 0;--i) {
		if(lca[x][i] != lca[y][i]) {
			ret = Upd(ret,st[x][i]);
			ret = Upd(ret,st[y][i]);
			y = lca[y][i];
			x = lca[x][i];
		}
	}
	ret = Upd(ret,st[x][1]);
	ret = Upd(ret,st[y][0]);
	return ret;
}
int main() {
	// freopen("1.in","r",stdin);
	int n = read(),Q = read();
	nownum = n;
	for(int i = 1;i < n;++i) {
		int u = read(),v = read();
		add(u,v);add(v,u);
	}
	st[1][0] = 1;
	dfs(1,0);
	for(int i = 1;i <= Q;++i) {
		int s = read(),t = read();
		ansss[i] = query(s,t);
	}
	printf("%d\n",nownum);
	for(vector<pi>::iterator it = ans.begin();it != ans.end();++it) {
		printf("%d %d\n",(*it).first,(*it).second);
	}

	for(int i = 1;i <= Q;++i) printf("%d\n",ansss[i]);
	return 0;
}

B.ZYB的染色计划

咕咕咕

C.逃课

problem

给出一个长度为\(n\)的整数序列,可以选择两个距离不超过\(K\)的位置,求这两个位置上面数字的和最大是多少。有\(Q\)次修改,每次修改某个位置,每次修改后都要回答一次答案。

\(2\le n \le 10^6,0\le Q \le 10^5\)

solution

我们需要找出两个距离不超过\(k-1\)的位置,使他们的和尽量大。被修改过的位置比较少,所以我们可以先求出两个位置都没有被修改过的答案,然后只关心那些涉及到至少一个被修改过的位置的方案。

我们可以对每个位置维护一个multiset,表示可以和当前位置同时选择的被修改过的位置的值的集合。我们需要知道每个位置本来的值加上multiset的最大值的最大值。

由于修改某个位置时需要在这个位置左右两边的两个区间的multiset中同时插入或删除一个值,我们可以用线段树维护集合,某个区间的节点上的multiset中的元素代表这个区间中每个multiset里都有一个当前元素。我们还需要维护区间最大值。由于每次修改最多引起\(O(\log N)\)次multiset操作,复杂度为\(O(n + q \log ^2 n)\)

code

/*
* @Author: wxyww
* @Date:   2020-06-07 11:42:14
* @Last Modified time: 2020-06-08 07:50:21
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
const int N = 1000010;

#define int ll
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
multiset<int>s[N << 2];
int tree[N << 2],mx[N << 2];
int a[N],q[N],H,T,flag[N],I[N],X[N];

void up(int rt) {
	mx[rt] = max(mx[rt << 1],mx[rt << 1 | 1]);
	tree[rt] = max(tree[rt << 1],tree[rt << 1 | 1]);
	if(!s[rt].empty()) tree[rt] = max(tree[rt],*s[rt].rbegin() + mx[rt]);
}

void build(int rt,int l,int r) {
	if(l == r) {
		mx[rt] = a[l];tree[rt] = 0;return;
	}
	int mid = (l + r) >> 1;
	build(rt << 1,l,mid);build(rt << 1 | 1,mid + 1,r);
	up(rt);
}

void update(int rt,int l,int r,int pos,int c) {
	if(l == r) {
		mx[rt] = c;tree[rt] = 0;
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) update(rt << 1,l,mid,pos,c);
	else update(rt << 1 | 1,mid + 1,r,pos,c);
	up(rt);
}
int FFF;
void Update(int rt,int l,int r,int L,int R,int c,int opt) {
	if(L > R) return;
	if(L <= l && R >= r) {
		if(opt == 1) {s[rt].insert(c);}
		else s[rt].erase(s[rt].find(c));
		tree[rt] = max(tree[rt << 1],tree[rt << 1 | 1]);
		if(!s[rt].empty()) tree[rt] = max(tree[rt],(*s[rt].rbegin()) + mx[rt]);
		return;
	}
	int mid = (l + r) >> 1;
	if(L <= mid) Update(rt << 1,l,mid,L,R,c,opt);
	if(R > mid) Update(rt << 1 | 1,mid + 1,r,L,R,c,opt);
	up(rt);
}

signed main() {
	// freopen("1.in","r",stdin);
	// freopen("C.out","w",stdout);
	int n = read(),K = read(),Q = read();
	int fans = 0;
	for(int i = 1;i <= n;++i) a[i] = read();
	
	build(1,1,n);

	for(int i = 1;i <= Q;++i) {
		I[i] = read();X[i] = read();
		flag[I[i]] = 1;
	}

	H = 1;

	for(int i = 1;i <= n;++i) {
		if(flag[i]) {
			int l = max(1ll,i - K + 1),r = min(n,i + K - 1);
			Update(1,1,n,l,i - 1,a[i],1);
			Update(1,1,n,i + 1,r,a[i],1);
			continue;
		}

		while(H <= T && q[H] <= i - K) ++H;
		fans = max(fans,a[i] + a[q[H]]);
		while(H <= T && a[q[T]] <= a[i]) --T;
		q[++T] = i;
	}

	printf("%lld\n",max(fans,tree[1]));
	for(int i = 1;i <= Q;++i) {
		if(i == 5) FFF = 1;
		int l = max(1ll,I[i] - K + 1),r = min(n,I[i] + K - 1);

		Update(1,1,n,l,I[i] - 1,a[I[i]],-1);
		Update(1,1,n,I[i] + 1,r,a[I[i]],-1);
		update(1,1,n,I[i],X[i]);
		a[I[i]] = X[i];
		Update(1,1,n,l,I[i] - 1,a[I[i]],1);
		Update(1,1,n,I[i] + 1,r,a[I[i]],1);
		FFF = 0;
		printf("%lld\n",max(fans,tree[1]));
	}
	return 0;
}

posted @ 2020-06-08 22:22  wxyww  阅读(21)  评论(0编辑  收藏  举报