【考试总结】2022-02-23
定位系统
本文旨在对 OneInDark 的做法进行记录,JZM yyds!
设 \(f_x\) 表示 \(x\) 子树外面已经有被选择的节点的时候让所有子树内部的权值被区分开的最小代价
如果 \(x\) 有多个儿子,那么最多只有一个儿子的子树中可以不放置任何标记点,那么据此得到
\[f_x=\left(\sum_{y\in son_x} \max(1,f_y)\right)-\left[0=\prod_{y\in son_x} dp_y\right] \]注意在 \(x\) 是时,只有 \(x\) 度数超过 \(2\) 才能直接使用 \(f_x\) 回答询问
由于要加边删边,只能使用 \(\rm LCT\) 上 动态 \(\rm DP\) 的方式来维护
考虑 \(f_x\) 可以写作一个关于 \(x\) 的重儿子的分段函数:如果 \(f_{son}=0\) 那么就是轻儿子权值对 \(1\) 取 \(\max\) 之后的和(记作 \(sum_x\),另计轻儿子中权值为 \(0\) 的点的数量是 \(img_x\)),否则还要减掉是不是存在轻儿子 \(f\) 值为 \(0\)
由于后面都是可以维护的常量,在每个节点维护 \((z=sum_x,b=sum_x-img_x)\) 分别表示 \(f_{son}=0\) 时得到 \(f_x=z\) 否则当前点 \(f_x=b+f_{son}\)
这个分段函数的值域是定义域的子集,那么可以 函数复合,函数复合是有结合律的,在 \(\rm LCT\) 上做 push_up
也是合法的
但是函数复合不等于迭代复合反函数,而出于 make_root
的需求,我们还需要维护上述函数复合的反方向过程,即 \(f_{rig}(f_{rt}(f_{lef}(x)))\) 翻转的时候交换即可
回答询问时考虑找一个三度点 makeroot
,把其所在的 splay
的链底的权值传到这棵树剩下的点的复合函数结果求值即可
实现时注意 access
将轻边改成重边之前要把儿子转到根,因为计算贡献时树的结构发生了变化
Code Display
const int N=5e5+10;
struct Func{
int b,z; Func(){z=b=0;}
Func(int bb,int zz){z=zz; b=bb;}
inline int get_val(int x){return x==0?z:x+b;}
}lef[N],rig[N];
int ls[N],rs[N],fa[N],stk[N],top,n,rev[N],sum[N],img[N];
inline Func Merge(Func a,Func b){
return Func(a.b+b.b,a.get_val(b.z));
}
inline void push_up(int x){
lef[x]=rig[x]=Func(sum[x]-(bool)img[x],sum[x]);
if(ls[x]){
lef[x]=Merge(lef[ls[x]],lef[x]);
rig[x]=Merge(rig[x],rig[ls[x]]);
}
if(rs[x]){
lef[x]=Merge(lef[x],lef[rs[x]]);
rig[x]=Merge(rig[rs[x]],rig[x]);
}
return ;
}
inline int isroot(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y)) if(ls[z]==y) ls[z]=x; else rs[z]=x;
if(ls[y]==x) ls[y]=rs[x],fa[rs[x]]=y,rs[x]=y;
else rs[y]=ls[x],fa[ls[x]]=y,ls[x]=y;
fa[x]=z; fa[y]=x;
return push_up(y),push_up(x);
}
inline void push_rev(int p){
swap(ls[p],rs[p]);
swap(lef[p],rig[p]);
rev[p]^=1;
return ;
}
inline void push_down(int p){
if(rev[p]){
if(ls[p]) push_rev(ls[p]);
if(rs[p]) push_rev(rs[p]);
rev[p]^=1;
} return ;
}
inline void splay(int x,int tar=0){
int y=x; stk[top=1]=y;
while(!isroot(y)) stk[++top]=y=fa[y];
while(top) push_down(stk[top--]);
while(!isroot(x)&&fa[x]!=tar){
int y=fa[x],z=fa[y];
if(z==tar){rotate(x); continue;}
if(!isroot(y)) rotate(((ls[z]==y)^(ls[y]==x))?x:y);
rotate(x);
} return ;
}
inline int f_img(int x){return sum[x]-(bool)img[x];}
inline int calc(int x,int fat=0){
splay(x,fat);
while(rs[x]) push_down(x),x=rs[x];
splay(x,fat);
if(!ls[x]) return f_img(x);
else return lef[ls[x]].get_val(f_img(x));
}
inline void obtain(int x,int y){
int dp=calc(y,x);
sum[x]+=max(dp,1ll); img[x]+=!dp;
push_up(x);
}
inline void lost(int x,int y){
int dp=calc(y,0);
sum[x]-=max(dp,1ll); img[x]-=!dp;
push_up(x);
}
inline void access(int x){
for(int y=0;x;x=fa[y=x]){
splay(x);
if(rs[x]) obtain(x,rs[x]);
if(y) lost(x,y),splay(y);
rs[x]=y;
push_up(x);
} return ;
}
inline void make_root(int x){access(x); splay(x); push_rev(x);}
inline void cut(int x,int y){
access(x); splay(y);
if(fa[y]!=x){
access(y); splay(y);
splay(x); fa[x]=0;
lost(y,x);
}else{
splay(y); fa[y]=0;
lost(x,y);
}
return ;
}
inline void link(int x,int y){
make_root(x); access(y); splay(y);
rs[y]=x; fa[x]=y; push_up(y);
return ;
}
int deg[N];
set > st;
inline int get_ans(){
int rt=prev(st.end())->sec;
make_root(rt);
return calc(rt);
}
inline void erase(int x){st.erase(st.find(make_pair(deg[x],x)));}
inline void ins(int x){st.insert(make_pair(deg[x],x));}
signed main(){
freopen("location.in","r",stdin); freopen("location.out","w",stdout);
n=read();
for(int i=1,u,v;ifir<=2) print(1);
else print(get_ans());
int Q=read();
while(Q--){
int a=read(),b=read();
erase(a); erase(b); deg[a]--; deg[b]--; ins(a); ins(b);
cut(a,b);
int c=read(),d=read();
erase(c); erase(d); deg[c]++; deg[d]++; ins(c); ins(d);
link(c,d);
if(n==1) print(0);
else if(prev(st.end())->fir<=2) print(1);
else print(get_ans());
}
return 0;
}
签到题
注意到点的度数不超过 \(3\),那么最小割也一定不超过 \(3\),对每个联通块分开讨论:
-
最小割是 \(1\) 时,两个点之间的路径必然存在割边,那么求出来连通块总点对数再减去每个边双点对数即可
-
否则分边双考虑,先求出来最小割是 \(3\) 的点对数再用每个边双里面的总点对数减去
建立边双的 dfs 树,对于一对点而言,选择任意两条割掉后能造成边双不连通的边割掉之后两个点总在一个连通块中,那么根据 最小割树 那套理论,他们的最小割就是 \(3\)
割掉两条具有 祖先-儿子 关系的树边是可能让图不连通的,我们给每个非树边一个随机权值,每个树边的权值就是所有跨过其的非树边权值异或和,如果两条树边权值相同那么就有较大概率割掉后不连通
实际上,使用随机零一向量异或的手段,其产生线性相关的概率可以忽略不计
还有一种情况即割掉树边和非树边,判定条件就是树边的权值和非树边相同
对于分割开的连通块,可以给每个连通块中的点一个随机权值,最后判定 mincut=3 的条件就是所有随机权值异或起来相同
使用子树打标记最后统一下方的方式可以得到具体权值,但是树边部分有 \(\Theta(n^2)\) 对边
由于权值相同的树边具有 祖先-儿子 关系,所以只保留相邻的等权边打标记即可
Code Display
const int N=1e6+10;
mt19937_64 Rand((unsigned)time(0));
ull val[N],ehs[N];
int dfn[N],low[N],tim,n,m;
vector nds[N];
int stk[N],top,dcc;
int bel[N],num,sum,ans;
vector G[N],now;
inline void tarjan(int x,int fat){
dfn[x]=low[x]=++tim; stk[++top]=x;
for(auto t:G[x]) if(t^fat){
if(!dfn[t]) tarjan(t,x),ckmin(low[x],low[t]);
else ckmin(low[x],dfn[t]);
}
if(low[x]==dfn[x]){
++dcc;
do{
bel[stk[top]]=dcc;
nds[dcc].push_back(stk[top]);
}while(stk[top--]!=x);
sum+=nds[dcc].size()*(nds[dcc].size()-1)/2;
} return ;
}
bool pas[N];
set app;
inline void get_nodes(int x){
if(pas[x]) return ;
pas[x]=1,++num,now.emplace_back(x);
for(auto t:G[x]) get_nodes(t);
return ;
}
int Root,dep[N];
inline void dfs(int x,int fat){
dep[x]=dep[fat]+1;
for(auto t:G[x]) if(t!=fat&&bel[x]==bel[t]){
if(!dep[t]){
dfs(t,x);
ehs[x]^=ehs[t];
}else{
if(dep[t]>dep[x]) continue;
ull tmp=Rand();
ehs[x]^=tmp; ehs[t]^=tmp;
app.insert(tmp);
}
} return ;
}
inline void push_xor(int x,int fat){
for(auto t:G[x]) if(dep[t]==dep[x]+1&&bel[x]==bel[t]){
val[t]^=val[x];
push_xor(t,x);
} return ;
}
signed main(){
freopen("juice.in","r",stdin); freopen("juice.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
G[u].push_back(v);
G[v].push_back(u);
}
for(int x=1;x<=n;++x) if(!pas[x]){
num=sum=0; now.clear();
get_nodes(x);
int lst=dcc;
tarjan(x,0);
ans+=num*(num-1)/2-sum;
rep(i,lst+1,dcc){
Root=nds[i][0]; app.clear();
dfs(nds[i][0],0);
map >ee;
for(auto t:nds[i]) if(t!=Root){
if(app.count(ehs[t])){
ull a=Rand(),b=Rand();
val[t]^=a^b;
val[Root]^=a;
}
ee[ehs[t]].push_back(t);
}
for(auto &hs:ee){
int siz=hs.sec.size();
sort(hs.sec.begin(),hs.sec.end(),[&](const int a,const int b){return dep[a] buc;
for(auto t:nds[i]) buc[val[t]]++;
int thr=0,siz=nds[i].size();
for(auto t:buc) thr+=t.sec*(t.sec-1)/2;
ans+=thr*3;
ans+=(siz*(siz-1)/2-thr)*2;
}
} print(ans);
return 0;
}
卷王
根据树上邻域理论不难发现 \(|S|\ge 4\) 的时候必然无解,也可以理解为直径端点个数超过 \(2\) 时每个点选择方式不止 \(1\) 个
先特判掉 \(n=1\) 的情况
如果 \(|S|=1\) 构造一个菊花来应付全是 \(-1\) 的 case
如果 \(|S|=2\),分是不是有 \(-1\) 来讨论:
-
集合中的数字是 \(-1\) 和 \(x\),如果 \(-1\) 只有 \(1\) 个,那么造成 $x\to (-1)\to $ 菊花即可;否则给 \(-1\) 造长度为 \(2\) 的链,额外的 \(-1\) 放链上最后一个点处造菊花,后面还是造 \(x\) 的菊花
-
集合中是两个非 \(-1\) 数 \(x,y\),分别生成 \(\min(num_x,num_y)\) 的链,多出来的在链底造菊花,再把链底连起来即可
\(|S|=3\) 时,一定是 \(x,y,-1\),和上面的方法完全一样,只不过中间夹了菊花 \(-1\)
Code Display
const int N=1e6+10;
int p[N],n;
set diff;
inline void imp(){
puts("Impossible"),exit(0);
}
vector >edge;
inline void output(){
puts("Possible");
assert(edge.size()==n-1);
for(auto e:edge) print(e.fir),print(e.sec),putchar('\n');
exit(0);
}
signed main(){
freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
n=read(); rep(i,1,n) p[i]=read(),diff.insert(p[i]);
if(n==1){
if(~(*diff.begin())) puts("Possible");
else puts("Impossible");
exit(0);
}
if(diff.size()==1){
if(n<=3||(*diff.begin())!=-1) imp();
puts("Possible");
rep(i,2,n) print(1),print(i),putchar('\n');
exit(0);
}
if(diff.size()>3) imp();
if(diff.size()==2){
if((*diff.begin())==-1){
set nds,pos;
int val=*prev(diff.end());
if(~p[val]) imp();
rep(i,1,n) if(p[i]==-1) nds.insert(i); else pos.insert(i);
nds.erase(val);
if(!nds.size()) imp();
if(nds.size()==1){
edge.emplace_back(val,*nds.begin());
if(pos.size()==1) imp();
int e=*pos.begin(); pos.erase(pos.begin());
edge.emplace_back(e,*nds.begin());
for(auto t:pos) edge.emplace_back(e,t);
}
else{
int lst=*nds.begin();
edge.emplace_back(val,lst);
nds.erase(nds.begin());
edge.emplace_back(lst,*nds.begin());
lst=*nds.begin();
nds.erase(nds.begin());
for(auto t:nds) edge.emplace_back(lst,t);
if(pos.size()<=2) imp();
edge.emplace_back(lst,*pos.begin());
lst=*pos.begin();
pos.erase(lst);
for(auto t:pos) edge.emplace_back(t,lst);
}
output();
exit(0);
}
set A,B;
int a=*diff.begin(),b=*prev(diff.end());
rep(i,1,n) if(p[i]==a) A.insert(i); else B.insert(i);
if(!A.count(b)||!B.count(a)) imp();
int lsa=b,lsb=a; A.erase(b); B.erase(a);
while(A.size()&&B.size()){
edge.emplace_back(*prev(A.end()),lsa); lsa=*prev(A.end());
edge.emplace_back(*prev(B.end()),lsb); lsb=*prev(B.end());
A.erase(prev(A.end()));
B.erase(prev(B.end()));
}
edge.emplace_back(lsa,lsb);
if(A.size()){
if(edge.size()<=3) imp();
for(auto t:A) edge.emplace_back(t,lsa);
}
if(B.size()){
if(edge.size()<=3) imp();
for(auto t:B) edge.emplace_back(t,lsb);
}
output();
}
if((*diff.begin())!=-1) imp();
int A=*prev(diff.end()),B=*prev(prev(diff.end()));
if(p[A]!=B||p[B]!=A) imp();
set a,b,neg;
rep(i,1,n){
if(p[i]==B) b.insert(i);
else if(p[i]==A) a.insert(i);
else neg.insert(i);
}
if(b.size()==1){
if(a.size()!=1) imp();
if(neg.size()!=1) imp();
edge.emplace_back(*b.begin(),*neg.begin());
edge.emplace_back(*a.begin(),*neg.begin());
output();
}
int lsa=B,lsb=A; b.erase(A); a.erase(B);
while(a.size()&&b.size()){
edge.emplace_back(*prev(a.end()),lsa); lsa=*prev(a.end());
edge.emplace_back(*prev(b.end()),lsb); lsb=*prev(b.end());
a.erase(prev(a.end()));
b.erase(prev(b.end()));
}
if(edge.size()<=2&&(a.size()||b.size())) imp();
int cen=*neg.begin(); neg.erase(cen);
edge.emplace_back(cen,lsb);
edge.emplace_back(cen,lsa);
for(auto t:neg) edge.emplace_back(t,cen);
if(a.size()) for(auto t:a) edge.emplace_back(lsa,t);
if(b.size()) for(auto t:b) edge.emplace_back(lsb,t);
output();
return 0;
}