传送门:
Goodbye Wuxu
题解
D,E暂咕
A. 新年的拯救计划
一共 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)条边,所以 m ≤ ⌊ n 2 ⌋ m\leq \lfloor \frac {n}{2}\rfloor m≤⌊2n⌋。
上限是一定可以卡满的:考虑构造
⌊
n
2
⌋
\lfloor \frac {n}{2}\rfloor
⌊2n⌋条起终点均不同的链,分奇偶讨论。
(然后就想歪了,还是没想到怎么
n
2
n^2
n2复杂度以内构造这么多条链)
一档部分分叫“ n n n是素数”:
可以联想到对于整数 1 ≤ k < n 1\leq k<n 1≤k<n, gcd ( k , n − k ) = 1 \gcd(k,n-k)=1 gcd(k,n−k)=1。
由于 n n n必然为奇数( n ≥ 3 n\geq 3 n≥3),所以可以构造 ⌊ n 2 ⌋ \lfloor \frac {n}{2}\rfloor ⌊2n⌋棵树,对于第 i ( 1 ≤ i ≤ ⌊ n 2 ⌋ ) i(1\leq i\leq \lfloor \frac {n}{2}\rfloor) i(1≤i≤⌊2n⌋)棵树,将所有 ∣ u − v ∣ = i |u-v|=i ∣u−v∣=i或 ∣ u − v ∣ = n − i |u-v|=n-i ∣u−v∣=n−i连边(连出来是个环,随便断一条边即可)。
再考虑 n n n为偶数的情况:
假设已经构造出了第一棵树
T
0
T_0
T0。
对于每条边
(
u
,
v
)
(u,v)
(u,v),在第
1
≤
i
<
n
2
1\leq i<\frac n2
1≤i<2n棵树中连接
(
(
u
+
i
)
m
o
d
n
,
(
v
+
i
)
m
o
d
n
)
((u+i) \ mod\ n,(v+i)\ mod\ n)
((u+i) mod n,(v+i) mod n)。
对于一条边 ( u , v ) ( u < v ) (u,v)(u<v) (u,v)(u<v),定义其长度为 min ( v − u , u − v + n ) \min(v-u,u-v+n) min(v−u,u−v+n),考虑每条边不断循环右移 [ 0 , n 2 ) [0,\frac n2) [0,2n)中长度变化的过程:
长度相同且
<
n
2
<\frac n2
<2n的边最多可以存在两条:
(
u
,
v
)
(u,v)
(u,v)和
(
(
u
+
n
2
)
m
o
d
n
,
(
v
+
n
2
)
m
o
d
n
)
((u+\frac n2) \ mod \ n,(v+\frac n2 )\ mod \ n)
((u+2n) mod n,(v+2n) mod n)。
长度为
n
2
\frac n2
2n的边只能存在一条。
所以对于
T
0
T_0
T0,构造
2
×
(
n
2
−
1
)
+
1
=
n
−
1
2\times (\frac n2-1)+1=n-1
2×(2n−1)+1=n−1条边,下面是一种例子:
1
1
1连向
[
2
,
n
2
+
1
]
[2,\frac n2 +1]
[2,2n+1]之间的所有点,
n
2
+
1
\frac n2+1
2n+1连向
[
n
2
+
2
,
n
]
[\frac n2+2,n]
[2n+2,n]之间的所有点。
n n n为奇数时范围反而变宽了(长度为 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor ⌊2n⌋的边也能存在两条),取同样的构造方法即可。
(A题都做不出来,给跪了)
B. 新年的Dog划分
假设二分图左右点集分别为 S , T S,T S,T。
首先考虑怎么暴力:
2 n 2^n 2n枚举左右点集合 A , B A,B A,B,询问时只保留 A B AB AB之间的连边,若是二分图,则必然存在一种情况的答案是连通:
假设
A
∩
S
≠
ϕ
A\cap S\neq \phi
A∩S̸=ϕ。
若
A
∩
T
≠
ϕ
A\cap T\neq \phi
A∩T̸=ϕ,则
A
∩
S
A\cap S
A∩S与
A
∩
T
A\cap T
A∩T不连通(它们分别只能和
B
∩
T
B\cap T
B∩T与
B
∩
S
B\cap S
B∩S相连),矛盾,所以
A
∩
T
=
ϕ
A\cap T=\phi
A∩T=ϕ。
若
B
∩
S
≠
ϕ
B\cap S\neq \phi
B∩S̸=ϕ,则
A
∩
S
A\cap S
A∩S与
B
∩
S
B\cap S
B∩S不连通,矛盾,所以
B
∩
S
=
ϕ
B\cap S=\phi
B∩S=ϕ。
所以 A = S , B = T A=S,B=T A=S,B=T。
正解:
只需要找到原图的一个生成树:
从
1
1
1开始向外
B
F
S
BFS
BFS维护当前连通块。
每次取出队顶元素
x
x
x,不断在所有未加入连通块的点中二分是否存在最小的
y
y
y,使得删掉连通块与这个点之间的连边之后图不连通,
(
x
,
y
)
(x,y)
(x,y)则是生成树上的一条边,询问次数上限
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
构造生成树后黑白点染色得到二分图,还需要判断是否是二分图:
删去二分图左右部之间的所有连边(生成树上的边除外),若不是二分图,则必然在树上有环,则枚举删掉一条树边判断图是否连通即可。
在提交记录里翻到一个十分优秀简洁的写法:
每次直接维护当前连通块中二分图左右部的点,二分找到不在连通块内且删去该点与连通块连边后图不连通的点 y y y,分别判断删去左部/右部与 y y y连边后是否连通,若两边删去均连通,则不是二分图,否则就找到了 y y y所在部分(左/右)。
#include <bits/stdc++.h>
#define pb push_back
#define pii pair<int,int>
#define mkp make_pair
using namespace std;
#include "graph.h"
const int N=205;
int col[N];
map<int,bool> result;
vector<int> check_bipartite(int n)
{
int i,j,k,l,r,mid,res,t;
bool isb=true;col[0]=1;
for(i=1;i<n;++i){
vector<int>s;
for(j=1;j<n;++j) if(!col[j]) s.pb(j);
l=0,r=s.size()-1;res=s.size();
for(;l<=r;){
mid=(l+r)>>1;
vector<pair<int,int> > le;
for(j=mid;j<s.size();++j)
for(k=0;k<n;++k) if(col[k]) le.pb(mkp(s[j],k));
query(le)?r=mid-1:l=(res=mid)+1;
}
result[-1]=result[1]=false;
for(t=-1;t<=1;t+=2){
vector<pair<int,int> > le;
for(j=res+1;j<s.size();++j)
for(k=0;k<n;++k) if(col[k])le.pb(mkp(s[j],k));
for(k=0;k<n;++k) if(col[k]==t)le.pb(mkp(s[res],k));
result[t]=query(le);
}
if(result[-1]&&result[1]){isb=false;break;}
col[s[res]]=result[1]?1:-1;
}
vector<int> ans;
if(isb)
for(i=0;i<n;++i)
if(col[i]>0) ans.push_back(i);
return ans;
}
C. 新年的小黄鸭
35 p t s 35pts 35pts的 n 2 n^2 n2暴力:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i点向上的连续重链长度为
j
j
j时,
i
i
i子树内的最优解。
转移时直接枚举重儿子。
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=5005;
int lg[N],n,sz[N],f[N][N];
vector<int>g[N];
inline void dn(int &x,int y){if(y<x) x=y;}
void dfs(int x,int fr)
{
int i,sum=0;sz[x]=1;
for(int y:g[x]) if(y^fr){
dfs(y,x);sz[x]+=sz[y];sum+=f[y][0];
for(i=0;i+1<n;++i) dn(f[x][i],f[y][i+1]-f[y][0]-(i?sz[y]*lg[i]:0));
}
for(i=0;i<n;++i) f[x][i]+=(sum+sz[x]*lg[i]);
}
int main(){
int i,x,y;lg[0]=1;
scanf("%d",&n);
for(i=1;i<n;i<<=1) lg[i+1]=1;
for(i=1;i<n;++i) lg[i]+=lg[i-1];
for(i=1;i<n;++i){scanf("%d%d",&x,&y);g[x].pb(y);g[y].pb(x);}
dfs(1,0);printf("%d",f[1][0]-n);
return 0;
}
正解:
设 t r a n s ( x ) = ⌈ log 2 x ⌉ + 1 trans(x)=\lceil\log_2 x \rceil+1 trans(x)=⌈log2x⌉+1。
观察到 f [ i ] [ j ] f[i][j] f[i][j]中对于 t r a n s ( j ) trans(j) trans(j)相等的 j j j其实可以用同一个 f [ i ] [ k ] f[i][k] f[i][k]来表示,所以只需要记 f [ i ] [ t r a n s ( j ) ] f[i][trans(j)] f[i][trans(j)]的值,状态就变成 n log n n\log n nlogn的了。
考虑转移:
对于
f
[
i
]
[
j
]
f[i][j]
f[i][j],枚举
i
i
i重链上(子树内)第一个满足
j
j
j的值不同或是叶结点的点,设其为
y
y
y,则
f
[
i
]
[
j
]
=
f
[
y
]
[
j
+
1
]
+
f
[
y
到
i
路
径
上
所
有
轻
儿
子
]
[
0
]
+
(
y
到
i
路
径
上
除
y
外
所
有
点
的
值
)
f[i][j]=f[y][j+1]+f[y到i路径上所有轻儿子][0]+(y到i路径上除y外所有点的值)
f[i][j]=f[y][j+1]+f[y到i路径上所有轻儿子][0]+(y到i路径上除y外所有点的值)。
……
还是比较玄学,算了,直接讲做法吧:
首先预处理出来每个点可以由它子树内哪些点转移而来(所有距离 = 1 =1 =1或 2 i + 1 2^i+1 2i+1),记为 n t [ x ] nt[x] nt[x]数组,总数量级别是 O ( n log n ) O(n\log n) O(nlogn)的,且按 d f s dfs dfs序给每个叶结点标号, [ i n x , o u t x ] [in_x,out_x] [inx,outx]表示在子树 x x x中的叶节点范围。
设
d
p
[
x
]
=
f
[
x
]
[
0
]
−
s
z
[
x
]
dp[x]=f[x][0]-sz[x]
dp[x]=f[x][0]−sz[x](即
x
x
x为根的答案,叶结点的
d
p
dp
dp值为
0
0
0)。
对于
d
p
[
x
]
dp[x]
dp[x],本质上是选中一个子树内叶结点
y
y
y,
x
x
x到
y
y
y这条路径就是重链。
d
p
[
x
]
=
m
i
n
(
v
(
x
到
y
路
径
上
所
有
点
)
)
(
i
n
x
≤
y
≤
o
u
t
x
)
dp[x]=min(v(x到y路径上所有点))(in_x\leq y\leq out_x)
dp[x]=min(v(x到y路径上所有点))(inx≤y≤outx)。
v ( y ) = t r a n s ( d i s ( x , y ) ) + ( ∑ t ∈ s o n [ f a t h e r y ] d p [ t ] + s z [ t ] ( 1 + t r a n s ( d i s ( x , t ) ) ) ) − d p [ y ] − s z [ y ] ( 1 + t r a n s ( d i s ( x , y ) ) ) = ( ∑ t ∈ s o n [ f a t h e r y ] d p [ t ] + s z [ t ] ) − d p [ y ] − s z [ y ] + ( s z [ f a t h e r y ] − s z [ y ] ) t r a n s ( d i s ( x , y ) ) v(y)=trans(dis(x,y))+(\sum \limits_{t\in son[father_y]}dp[t]+sz[t](1+trans(dis(x,t))))-dp[y]-sz[y](1+trans(dis(x,y))) \\ \qquad =(\sum \limits_{t\in son[father_y]}dp[t]+sz[t])-dp[y]-sz[y]+(sz[father_y]-sz[y])trans(dis(x,y)) v(y)=trans(dis(x,y))+(t∈son[fathery]∑dp[t]+sz[t](1+trans(dis(x,t))))−dp[y]−sz[y](1+trans(dis(x,y)))=(t∈son[fathery]∑dp[t]+sz[t])−dp[y]−sz[y]+(sz[fathery]−sz[y])trans(dis(x,y))。
对于前一部分: ∑ ( ( ∑ t ∈ s o n [ f a t h e r y ] d p [ t ] + s z [ t ] ) − d p [ y ] − s z [ y ] ) \sum((\sum \limits_{t\in son[father_y]}dp[t]+sz[t])-dp[y]-sz[y]) ∑((t∈son[fathery]∑dp[t]+sz[t])−dp[y]−sz[y])直接在线段树上按关于叶结点的 d f s dfs dfs序维护即可。
对于后一部分:实际上就是
y
y
y和所有轻儿子子树这些点到根的贡献为
t
r
a
n
s
(
x
,
y
)
trans(x,y)
trans(x,y),
而
y
y
y子树中的重链部分的贡献还要另算,轻链部分子树中的
v
(
)
v()
v()会算到。
但注意
t
r
a
n
s
(
x
,
y
)
trans(x,y)
trans(x,y)只有
log
n
\log n
logn中取值,我们把将一个点
×
t
r
a
n
s
(
x
,
y
)
\times trans(x,y)
×trans(x,y)转成对于每个点的在它的
1
,
2
0
+
1
,
2
1
+
1
,
.
.
.
,
2
k
+
1
1,2^0+1,2^1+1,...,2^k+1
1,20+1,21+1,...,2k+1级祖先处分别被算一次。
也就是线段树中将
n
t
[
x
]
nt[x]
nt[x]中所有点
q
q
q在其对应的范围
[
i
n
q
,
o
u
t
q
]
[in_q,out_q]
[inq,outq]内
+
s
z
[
q
]
+sz[q]
+sz[q]。
d p [ x ] dp[x] dp[x]就是查询线段树 [ i n x , o u t x ] [in_x,out_x] [inx,outx]内最小值。
面向代码做题(大雾)
#include<bits/stdc++.h>
#define lc k<<1
#define rc k<<1|1
#define mid ((l+r)>>1)
#define pb push_back
using namespace std;
const int N=1e5+10;
int n,f[N][17],in[N],ot[N],sz[N],dfn,dp[N];
int mn[N<<2],lzy[N<<2];
vector<int>g[N],nt[N];
inline void ad(int k,int l,int r,int L,int R,int v)
{
if(L<=l && r<=R) {mn[k]+=v;lzy[k]+=v;return;}
if(L<=mid) ad(lc,l,mid,L,R,v);
if(R>mid) ad(rc,mid+1,r,L,R,v);
mn[k]=min(mn[lc],mn[rc])+lzy[k];
}
inline int ask(int k,int l,int r,int L,int R)
{
if(L<=l && r<=R) return mn[k];
if(R<=mid) return ask(lc,l,mid,L,R);
if(L>mid) return ask(rc,mid+1,r,L,R);
return min(ask(lc,l,mid,L,R),ask(rc,mid+1,r,L,R))+lzy[k];
}
void dfs1(int x,int fr)
{
int i;f[x][0]=fr;if(fr) nt[fr].pb(x);
for(i=1;i<17;++i) f[x][i]=f[f[x][i-1]][i-1];
for(i=0;i<17;++i) if(f[f[x][i]][0]) nt[f[f[x][i]][0]].pb(x);
sz[x]=1;in[x]=dfn+1;if(g[x].size()==1 && fr) dfn++;
for(int y:g[x]) if(y^fr) {dfs1(y,x);sz[x]+=sz[y];}
ot[x]=dfn;
}
void dfs2(int x,int fr)
{
int sum=0;
for(int y:g[x]) if(y^fr) {dfs2(y,x);sum+=dp[y]+sz[y];}
for(int y:g[x]) if(y^fr) ad(1,1,dfn,in[y],ot[y],sum-dp[y]-sz[y]);
for(int y:nt[x]) ad(1,1,dfn,in[y],ot[y],sz[y]);
dp[x]=ask(1,1,dfn,in[x],ot[x]);
for(int y:nt[x]) ad(1,1,dfn,in[y],ot[y],-sz[y]);
}
int main(){
int i,x,y;scanf("%d",&n);
for(i=1;i<n;++i) {scanf("%d%d",&x,&y);g[x].pb(y);g[y].pb(x);}
dfs1(1,0);dfs2(1,0);printf("%d",dp[1]);
return 0;
}