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