orz
\text{orz}
orz 一波现场
A
\text{A}
A 掉
D1T3
\text{D1T3}
D1T3 的神仙 nealchen
D2T3 centroid
Solution
考虑每个点
u
u
u 作为重心的贡献
假设以
u
u
u 为根时,存在
u
u
u 的一个子节点
v
v
v
现在要在
v
v
v 的子树内删掉一个子树,使得
u
u
u 成为重心
考虑删子树之后,
v
v
v 的子树大小需要满足什么条件
设
u
u
u 除
v
v
v 之外,所有子树大小的和为
s
s
s ,最大子树大小为
m
m
m
(1)
v
v
v 的子树大小不能比
u
u
u 其他子树大小的和加
1
1
1 还大:
s
i
z
e
v
≤
s
+
1
size_v\le s+1
sizev≤s+1
(2)除
v
v
v 之外的最大子树大小不能比
u
u
u 其他子树大小的和加
1
1
1 还大:
m
≤
s
−
m
+
s
i
z
e
v
+
1
m\le s-m+size_v+1
m≤s−m+sizev+1
于是得出
s
i
z
e
v
∈
[
2
m
−
s
−
1
,
s
+
1
]
size_v\in[2m-s-1,s+1]
sizev∈[2m−s−1,s+1]
问题转化为
v
v
v 的子树内有多少个点的子树大小在某个区间范围内
由于我们不能每次都以
u
u
u 为根重新求一遍,所以任选一个点为根后,如果
v
v
v 是
u
u
u 的子节点,那么可以直接利用各种方法(如线段树合并)统计,否则分删掉的子树是否在
1
1
1 到
u
u
u 的路径上进行处理
O
(
n
log
n
)
O(n\log n)
O(nlogn)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);if(bo) res =~res +1;}template<classT>inlinevoidSwap(T &a, T &b){T t = a; a = b; b = t;}typedeflonglong ll;constint N =3e5+5, M = N <<1, L =1e7+5;int n, ecnt, nxt[M], adj[N], go[M], sze[N], rt[N], ToT, A[N], sum[N];
ll ans;struct node
{int lc, rc, sum;} T[L];voidchange(int x,int v){for(; x <= n; x += x &-x)
A[x]+= v;}intask(int x){int res =0;for(; x; x -= x &-x)
res += A[x];return res;}voidins(int l,int r,int pos,int v,int&p){if(!p) p =++ToT;
T[p].sum += v;if(l == r)return;int mid = l + r >>1;if(pos <= mid)ins(l, mid, pos, v, T[p].lc);elseins(mid +1, r, pos, v, T[p].rc);}intquery(int l,int r,int s,int e,int p){if(!p || e < l || s > r)return0;if(s <= l && r <= e)return T[p].sum;int mid = l + r >>1;returnquery(l, mid, s, e, T[p].lc)+query(mid +1, r, s, e, T[p].rc);}intmer(int x,int y){if(!x ||!y)return x ^ y;
T[x].sum += T[y].sum;
T[x].lc =mer(T[x].lc, T[y].lc);
T[x].rc =mer(T[x].rc, T[y].rc);return x;}voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;
nxt[++ecnt]= adj[v]; adj[v]= ecnt; go[ecnt]= u;}voiddfs(int u,int fu){
sze[u]=1;for(int e = adj[u], v; e; e = nxt[e])if((v = go[e])!= fu)dfs(v, u), sze[u]+= sze[v];}voidsolve(int u,int fu){int pm =0, pc =0;for(int e = adj[u], v; e; e = nxt[e]){if((v = go[e])== fu)continue;if(sze[v]> pm) pc = pm, pm = sze[v];elseif(sze[v]> pc) pc = sze[v];}if(fu){if(n - sze[u]> pm) pc = pm, pm = n - sze[u];elseif(n - sze[u]> pc) pc = n - sze[u];}for(int e = adj[u], v; e; e = nxt[e]){if((v = go[e])== fu)continue;change(sze[v],1);solve(v, u);change(sze[v],-1);int mx = sze[v]== pm ? pc : pm;int l = n - sze[v], r = mx -(l - mx);
l = sze[v]- l; r = sze[v]- r;if(l > r || r <1|| l > n){rt[u]=mer(rt[u], rt[v]);continue;}if(l <1) l =1;if(r > n) r = n;
ans +=1ll* u *query(1, n, l, r, rt[v]);
rt[u]=mer(rt[u], rt[v]);}if(u ==1)return;int mx = n - sze[u]== pm ? pc : pm;int l = sze[u], r = mx -(l - mx);
l = n - sze[u]- l; r = n - sze[u]- r;if(l > r || r <1|| l > n)returnins(1, n, sze[u],1, rt[u]);if(l <1) l =1;if(r > n) r = n;int cnt = sum[r]- sum[l -1]-query(1, n, l, r, rt[u]);
cnt -=ask(r)-ask(l -1);
l = n - l; r = n - r;Swap(l, r);if(l > r || r <1|| l > n)returnins(1, n, sze[u],1, rt[u]);if(l <1) l =1;if(r > n) r = n;
cnt +=ask(r)-ask(l -1); ans +=1ll* u * cnt;ins(1, n, sze[u],1, rt[u]);}voidwork(){int x, y;read(n);
ecnt = ToT =0; ans =0;memset(adj,0,sizeof(adj));memset(rt,0,sizeof(rt));memset(A,0,sizeof(A));memset(sum,0,sizeof(sum));for(int i =1; i < n; i++)read(x),read(y),add_edge(x, y);dfs(1,0);for(int i =2; i <= n; i++) sum[sze[i]]++;for(int i =1; i <= n; i++) sum[i]+= sum[i -1];solve(1,0);printf("%lld\n", ans);for(int i =1; i <= ToT; i++) T[i].lc = T[i].rc = T[i].sum =0;}intmain(){int T;read(T);while(T--)work();return0;}
D2T2 partition
Solution
我们大胆猜想:当答案取到最优时,最后一段的长度取到最小值
证明略 (显然)
设
f
i
f_i
fi 表示以
i
i
i 为结尾,倒数第二段结束位置的最大值,如果最优答案下只有一段则为
0
0
0
设
s
u
m
sum
sum 为前缀和数组
那么我们有
f
i
=
max
{
j
∣
s
u
m
i
−
s
u
m
j
≥
s
u
m
j
−
s
u
m
f
j
}
f_i=\max\{j|sum_i-sum_j\ge sum_j-sum_{f_j}\}
fi=max{j∣sumi−sumj≥sumj−sumfj}
也就是
f
i
=
max
{
j
∣
0
≤
j
<
i
,
s
u
m
i
≥
2
s
u
m
j
−
s
u
m
f
j
}
f_i=\max\{j|0\le j<i,sum_i\ge2sum_j-sum_{f_j}\}
fi=max{j∣0≤j<i,sumi≥2sumj−sumfj}
考虑维护一个以
2
s
u
m
j
−
s
u
m
f
j
2sum_j-sum_{f_j}
2sumj−sumfj 为关键字的,关于后缀最小值的单调栈
那么我们每次要选取的就是单调栈中,从右到左第一个不超过
s
u
m
i
sum_i
sumi 的元素
由于
s
u
m
sum
sum 单调递增,故可以用一个指针维护这个元素的位置
注意单调栈退栈的时候,如果这时指针不在栈内了,那么要把指针重新放到栈顶
最后从
i
=
n
i=n
i=n 开始,不断让
i
←
f
i
i\leftarrow f_i
i←fi ,期间把
(
s
u
m
i
−
s
u
m
f
i
)
2
(sum_i-sum_{f_i})^2
(sumi−sumfi)2 计入答案,需要使用到高精度
O
(
n
)
O(n)
O(n)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);if(bo) res =~res +1;}typedeflonglong ll;constint N =4e7+5, M =1e5+5, rqy =1<<30, dmy =1e9;int n, ty, f[N], X, Y, Z, m, p[M], l[M], r[M], top, stk[N];
ll sum[N], tmp[4];struct gao
{int a[4];friendinline gao operator+(gao a, gao b){
gao res;
res.a[0]= res.a[1]= res.a[2]= res.a[3]=0;
res.a[0]+= a.a[0]+ b.a[0];if(res.a[0]>= dmy) res.a[1]++, res.a[0]-= dmy;
res.a[1]+= a.a[1]+ b.a[1];if(res.a[1]>= dmy) res.a[2]++, res.a[1]-= dmy;
res.a[2]+= a.a[2]+ b.a[2];if(res.a[2]>= dmy) res.a[3]++, res.a[2]-= dmy;return res.a[3]+= a.a[3]+ b.a[3], res;}} ans, tm;
gao sqr(gao x){
tmp[0]=1ll* x.a[0]* x.a[0]; tmp[1]=2ll* x.a[0]* x.a[1];
tmp[2]=1ll* x.a[1]* x.a[1]; tmp[3]=0;for(int i =0; i <3; i++)
tmp[i +1]+= tmp[i]/ dmy, tmp[i]%= dmy;
gao res; res.a[0]= tmp[0]; res.a[1]= tmp[1];return res.a[2]= tmp[2], res.a[3]= tmp[3], res;}intmain(){read(n);read(ty);if(ty){read(X);read(Y);read(Z);read(sum[1]);read(sum[2]);read(m);for(int i =3; i <= n; i++)
sum[i]=(sum[i -1]* X + sum[i -2]* Y + Z)% rqy;for(int i =1; i <= m; i++)read(p[i]),read(l[i]),read(r[i]);for(int j =1; j <= m; j++)for(int i = p[j -1]+1; i <= p[j]; i++)
sum[i]%= r[j]- l[j]+1, sum[i]+= l[j];}elsefor(int i =1; i <= n; i++)read(sum[i]);for(int i =1; i <= n; i++) sum[i]+= sum[i -1];
stk[top =1]=0;int p =1;for(int i =1; i <= n; i++){while(p < top && sum[stk[p +1]]*2- sum[f[stk[p +1]]]<= sum[i]) p++;
f[i]= stk[p];while(top && sum[stk[top]]*2- sum[f[stk[top]]]>= sum[i]*2- sum[f[i]]) top--;if(p > top) p = top; stk[++top]= i;}for(int i = n; i; i = f[i]){
ll num = sum[i]- sum[f[i]];
tm.a[0]= num % dmy; tm.a[1]= num / dmy;
ans = ans +sqr(tm);}bool is =0;for(int i =3; i >=0; i--){if(!ans.a[i]&&!is)continue;if(!is)printf("%d", ans.a[i]), is =1;elseprintf("%09d", ans.a[i]);}if(is)puts("");elseputs("0");return0;}
D1T3 tree
Solution
先按字典序从左到右贪心,设数
i
i
i 在点
u
u
u
现在要为
u
u
u 选定一个编号最小的点
v
v
v (
u
≠
v
u\ne v
u=v),且需要满足一些条件
设前
i
−
1
i-1
i−1 个数已经定好了位置,我们现在要判定的就是如果想要把数
i
i
i 移到点
v
v
v ,那么是否存在一个操作次序
这时从
u
u
u 到
v
v
v 连一条路径,可以得出:
(1)路径上第一条边比
u
u
u 出发的任意其他边的操作次序都早
(2)路径上最后一条边比
v
v
v 出发的任意其他边的操作次序都晚
(3)对于路径上任意相邻的两条边
e
1
,
e
2
e_1,e_2
e1,e2 ,如果它们有公共点
x
x
x ,那么就
x
x
x 出发的所有边中,
e
1
e_1
e1 和
e
2
e_2
e2 的操作次序必须相邻,并且
e
1
e_1
e1 先于
e
2
e_2
e2
不难发现产生的所有限制关系都在有公共点的两条边之间产生
同时,由于这是一棵树,所以如果对于任意
u
u
u 都满足
u
u
u 出发的任意边之间都不会产生矛盾,那么整棵树都不会产生矛盾(因为可以不断删叶子)
由于我们有两条边操作次序相邻的限制,故可以对每个点,用并查集或链表维护连续段,对
i
i
i 确定位置
v
v
v 时判断是否合法
如何判断合法性:
(1)设
u
u
u 到
v
v
v 的路径上第一条边为
e
e
e ,那么需要满足
e
e
e 是某个连续段的开头,并且
u
u
u 出发的所有边已经被合成一个连续段,或者
e
e
e 所在连续段的末尾没有被钦定为最后一次操作
(2)最后一条边同理
(3)对于路径上连续的两条边
e
1
,
e
2
e_1,e_2
e1,e2 ,需要满足:
①
e
1
e_1
e1 是某个连续段的末尾,
e
2
e_2
e2 是某个连续段的开头,且
e
1
e_1
e1 和
e
2
e_2
e2 不属于同一连续段
②
e
1
e_1
e1 没有被钦定为最后一次操作,并且
e
2
e_2
e2 没有被钦定为第一次操作
③ 如果
e
1
e_1
e1 所在连续段的开头被钦定为第一次操作,且
e
2
e_2
e2 所在连续段的末尾被钦定为最后一次操作,那么需要满足以
u
u
u 出发的边中,除了
e
1
e_1
e1 和
e
2
e_2
e2 各自所在的连续段之外,不能有其他的连续段
找到了对应的
v
v
v 时,需要把
u
u
u 到
v
v
v 的路径上所有相邻的两条边合并连续段,并把第一条边钦定为第一次操作,最后一条边钦定为最后一次操作
O
(
T
n
2
)
O(Tn^2)
O(Tn2)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);if(bo) res =~res +1;}constint N =2005, M = N <<1;int n, a[N], ecnt, nxt[M], adj[N], go[M], res, d[N],
st[M], ed[M], fir[M], lst[M], par[N];bool ist[M], ied[M], vis[N];voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;
nxt[++ecnt]= adj[v]; adj[v]= ecnt; go[ecnt]= u;
d[u]++; d[v]++;}voiddfs(int u,int fu,int fe){if(ied[fe]&&(d[u]==1|| st[fe]!= fir[u])&& u < res &&!vis[u]) res = u;for(int e = adj[u], v; e; e = nxt[e]){if((v = go[e])== fu)continue;if(!ied[fe]||!ist[e]|| ed[e]== fe)continue;if(fir[u]== e || lst[u]== fe)continue;if(fir[u]== st[fe]&& lst[u]== ed[e]&& d[u]>2)continue;dfs(v, u, par[v]= e ^1);}}voidwork(){int x, y;read(n); ecnt =1;memset(adj,0,sizeof(adj));memset(vis,0,sizeof(vis));memset(fir,0,sizeof(fir));memset(lst,0,sizeof(lst));memset(ist,1,sizeof(ist));memset(ied,1,sizeof(ied));memset(d,0,sizeof(d));for(int i =1; i <= n; i++)read(a[i]);for(int i =1; i < n; i++)read(x),read(y),add_edge(x, y);for(int i =2; i <= ecnt; i++) st[i]= ed[i]= i;for(int i =1; i <= n; i++){int u = a[i]; res = n;for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])if(ist[e]&&(d[u]==1|| ed[e]!= lst[u]))dfs(v, u, par[v]= e ^1);printf("%d ", res); vis[res]=1;
lst[res]= par[res];int e = par[res]^1;for(int v = go[par[res]]; v != u; v = go[par[v]]){int f = par[v], l = st[f], r = ed[e];
st[r]= l; ed[l]= r; ied[f]= ist[e]=0;
e = f ^1; d[v]--;}
fir[u]= e;}puts("");}intmain(){int T;read(T);while(T--)work();return0;}