我切的三個題,感覺都挺好的,來補個題解。
B - Building Forest Trails
如果不規定交叉算連通的話,那直接加邊!加邊!加邊!然后,並查集查詢。
不難發現兩條邊 \((a,b),(c,d)\) 交叉,當且僅當:設 \(a<b,c<d\),則 \(a<c<b<d\) 或 \(c<a<d<b\)。
我們要做的是,連接 \((x,y)\) 后,不光合並 \(x,y\),還要將所有滿足內部有邊與 \((x,y)\) 交叉的連通塊與 \(x,y\) 所在連通塊合並。不難發現,我們根本不需要關心連通塊內到底有哪些邊,因為若 \(z,w\) 都在該連通塊,且 \((z,w)\) 與 \((x,y)\) 交叉,則不論 \((z,w)\) 這條邊是否真的存在,該連通塊內都存在與 \((x,y)\) 相交的邊。證明的話,在 \(z\to w\) 上應用一下介值定理即可。反之,不難發現上面說的顯然是必要條件,而我們用介值定理證明了充分性,自然就是充要條件。
那么設連通塊內元素排序之后為 \(p_{1\sim s}\),該連通塊應與 \(x,y\) 所在連通塊合並當且僅當存在 \(p_{i-1}<x<p_i<y\) 或 \(x<p_i<y<p_{i+1}\)。合並次數顯然是線性的,我只需要命中率盡量高,讓失敗次數控制在線性以內即可。不難想到這樣一個方案:每次找區間 \((x,y)\) 內后繼最大的,直到后繼落在該區間內為止;對前驅類似。不難想到對每個連通塊用 set
維護前驅后繼,合並的時候可以啟發式合並,將一個點加入 set
時改變的前驅后繼數量顯然是常數,於是可以線段樹維護區間前驅 / 后繼最值。
總復雜度大常數 2log(啟發式 + set
),不過 TL = 3s 加上 cf 神機(再神也沒有 20034 神,然而它掛了()),還是讓我過了。
code
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f;
const int N=200010;
int n,qu;
set<int> st[N];
struct ufset{
int fa[N];
ufset(){memset(fa,0,sizeof(fa));}
int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
void mrg(int x,int y){
x=root(x),y=root(y);
fa[x]=y;
}
}ufs;
struct segtree{
struct node{pair<int,int> mn,mx;}nd[N<<2];
#define mn(p) nd[p].mn
#define mx(p) nd[p].mx
void init(){
for(int i=0;i<N<<2;i++)nd[i]=node({mp(inf,inf),mp(-inf,-inf)});
}
void sprup(int p){mn(p)=min(mn(p<<1),mn(p<<1|1)),mx(p)=max(mx(p<<1),mx(p<<1|1));}
void chgmn(int x,int v,int p=1,int tl=1,int tr=n){
if(tl==tr)return mn(p)=mp(v,tl),void();
int mid=tl+tr>>1;
if(x<=mid)chgmn(x,v,p<<1,tl,mid);
else chgmn(x,v,p<<1|1,mid+1,tr);
sprup(p);
}
void chgmx(int x,int v,int p=1,int tl=1,int tr=n){
if(tl==tr)return mx(p)=mp(v,tl),void();
int mid=tl+tr>>1;
if(x<=mid)chgmx(x,v,p<<1,tl,mid);
else chgmx(x,v,p<<1|1,mid+1,tr);
sprup(p);
}
pair<int,int> _mn(int l,int r,int p=1,int tl=1,int tr=n){
if(l>r)return mp(inf,inf);
if(l<=tl&&r>=tr)return mn(p);
int mid=tl+tr>>1;pair<int,int> res(inf,inf);
if(l<=mid)res=min(res,_mn(l,r,p<<1,tl,mid));
if(r>mid)res=min(res,_mn(l,r,p<<1|1,mid+1,tr));
return res;
}
pair<int,int> _mx(int l,int r,int p=1,int tl=1,int tr=n){
if(l>r)return mp(-inf,-inf);
if(l<=tl&&r>=tr)return mx(p);
int mid=tl+tr>>1;pair<int,int> res(-inf,-inf);
if(l<=mid)res=max(res,_mx(l,r,p<<1,tl,mid));
if(r>mid)res=max(res,_mx(l,r,p<<1|1,mid+1,tr));
return res;
}
}segt;
void insert(int x,int to){
st[to].insert(x);
set<int>::iterator fd=st[to].find(x);
if(fd!=st[to].begin()){
set<int>::iterator prv=fd--;swap(fd,prv);
segt.chgmn(*fd,*prv),segt.chgmx(*prv,*fd);
}else segt.chgmn(*fd,*fd);
if(fd!=--st[to].end()){
set<int>::iterator nxt=fd++;swap(fd,nxt);
segt.chgmx(*fd,*nxt),segt.chgmn(*nxt,*fd);
}else segt.chgmx(*fd,*fd);
}
void mrg(int x,int y){
x=ufs.root(x),y=ufs.root(y);
if(st[x].size()>st[y].size())swap(x,y);
ufs.mrg(x,y);
while(st[x].size())insert(*st[x].begin(),y),st[x].erase(st[x].begin());
}
int main(){
cin>>n>>qu;
segt.init();
for(int i=1;i<=n;i++)st[i].insert(i);
string out;
while(qu--){
int tp,x,y;
scanf("%d%d%d",&tp,&x,&y);
x=ufs.root(x),y=ufs.root(y);
if(tp==2){out+=(x==y)^48;continue;}
if(x==y)continue;
if(x>y)swap(x,y);
mrg(x,y);
while(true){
pair<int,int> mn=segt._mn(x+1,y-1);
if(mn.X>=x)break;
mrg(mn.Y,x);
}
while(true){
pair<int,int> mx=segt._mx(x+1,y-1);
// cout<<mx.X<<"!\n";
if(mx.X<=y)break;
mrg(mx.Y,x);
}
}
puts(out.c_str());
return 0;
}
J - Just Kingdom
這題我拿了「二血」(當然是場外的),感覺很妙的一題啊。又一次現場切 3100
不難發現,子樹 \(x\) 的需求量為 \(sum_x=\sum\limits_{y\in\mathrm{subt}(x)}a_y\)。而某個節點將一些錢分發給兒子們的過程,就像往一個柱狀水槽里注水一樣,每個兒子對應一個柱子,高度為其 \(sum\) 值。那么 \(x\) 想要得到 \(sum_x\),可以轉化為父親要得到多少。是多少呢?將父親的兒子序列的 \(sum\) 值(除去自己!)排序得到 \(p\),則父親要得到 \(\sum\limits_{p_i\leq sum_x}p_i+\left(1+\sum\limits_{p_i>sum_x}1\right)p_i\)。父親要得到這么多,還可以往父親的父親轉化,這樣一直轉化上去,直到 root。至此我們可以得到一個暴力做法:對每個點暴力往上迭代,復雜度是 \(\sum dep_i\) 的 polylog ,亦即 \(\sum sz_i\) 的 polylog。
這個 \(\sum sz_i\) 就很有趣啊,啟示我們用 dsu on tree。先考慮怎么由 \(\sum dep_i\) 轉化到 \(\sum sz_i\) 的這樣一個過程,其實就是每個點處有一個 todo-list,都是后代們迭代上來的,它的任務是把這些 todo-list 里的值進行加工並且上傳給父親。這不就是一個子樹合並問題嗎?
考慮 dsu on tree,最難的問題是如何直接繼承重兒子,復雜度不能跟重兒子的 size 相關。但是跟該節點的度數相關是沒問題的呀!因為 \(\sum\deg_i=\mathrm O(n)\)。我們設兒子序列 \(sum\) 值排序得到 \(p\),那么對同一個 \(i\),\(p_i\leq x< p_{i+1}\) 的待加工值 \(x\) 的加工方式都是一樣的,都是作用上同一個一次函數 \(ax+b\)。也就是我們要對若干個區間 \([l,r]\),將 todo-list 中處於這個區間中的值都作用上一個一次函數,這顯然可以平衡樹 + 加乘懶標記維護,因為單調性不變。這樣就實現了直接繼承。
然后輕兒子們暴力一個一個值插入就很 trivial 了,直接在兒子序列里二分得到處理之后的值,然后 insert 進平衡樹。總復雜度大常數 2log(dsu on tree + fhq-treap),而且 \(n\leq 3\mathrm e5\),不過 TL = 5s,直接沖就是了。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=600010;
int n;
int a[N];
int fa[N];
vector<int> nei[N];
int sz[N],wson[N],sum[N];
void dfs(int x=n+1){
sz[x]=1;sum[x]=a[x];
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
dfs(y);
sz[x]+=sz[y],sum[x]+=sum[y];
if(sz[y]>sz[wson[x]])wson[x]=y;
}
}
mt19937 rng(20060729);
struct fhq_treap{
int sz,root;
struct node{unsigned key;int lson,rson,sz,v,u,ad,mu;}nd[N+1];
#define key(p) nd[p].key
#define lson(p) nd[p].lson
#define rson(p) nd[p].rson
#define sz(p) nd[p].sz
#define v(p) nd[p].v
#define u(p) nd[p].u
#define ad(p) nd[p].ad
#define mu(p) nd[p].mu
void init(){nd[sz=root=0]=node({0,0,0,0,0,0,0,0});}
void sprup(int p){sz(p)=sz(lson(p))+1+sz(rson(p));}
void tag(int p,int A,int M){
v(p)*=M,v(p)+=A;
// if(M==1&&A==6)cout<<v(p)<<"?\n";
mu(p)*=M,ad(p)*=M,ad(p)+=A;
}
void sprdwn(int p){
tag(lson(p),ad(p),mu(p)),tag(rson(p),ad(p),mu(p));
ad(p)=0,mu(p)=1;
}
pair<int,int> split(int x,int p=-1){~p||(p=root);
if(!x)return mp(0,p);
sprdwn(p);
pair<int,int> sp;
if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
return sp=split(x-sz(lson(p))-1,rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
}
int mrg(int p,int q){
if(!p||!q)return p|q;
sprdwn(p),sprdwn(q);
if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
return lson(q)=mrg(p,lson(q)),sprup(q),q;
}
int lss(int v,int p=-1){~p||(p=root);
if(!p)return 0;
sprdwn(p);
if(v(p)<v)return sz(lson(p))+1+lss(v,rson(p));
return lss(v,lson(p));
}
int leq(int v,int p=-1){~p||(p=root);
if(!p)return 0;
sprdwn(p);
if(v(p)<=v)return sz(lson(p))+1+leq(v,rson(p));
return leq(v,lson(p));
}
int nwnd(int v,int u){return nd[++sz]=node({rng(),0,0,1,v,u,0,1}),sz;}
void insert(int v,int u){
pair<int,int> sp=split(lss(v));
root=mrg(mrg(sp.X,nwnd(v,u)),sp.Y);
}
void dfs(vector<pair<int,int> > &vec,int p=-1){~p||(p=root);
if(!p)return;
sprdwn(p);
dfs(vec,lson(p));
vec.pb(mp(v(p),u(p)));
dfs(vec,rson(p));
}
void prt(int p=-1){~p||(p=root);
if(!p)return;
sprdwn(p);
prt(lson(p));
cout<<v(p)<<","<<u(p)<<" ";
prt(rson(p));
}
void am(int l,int r,int ad,int mu){
// cout<<l<<" to "<<r<<":";
l=lss(l),r=leq(r);
// cout<<l<<" "<<r<<"!\n";
pair<int,int> sp=split(l),sp0=split(r-l,sp.Y);
tag(sp0.X,ad,mu);
root=mrg(sp.X,mrg(sp0.X,sp0.Y));
}
}trp;
vector<pair<int,int> > v[N];
void dfs0(int x=n+1){
if(nei[x].empty())return trp.insert(a[x],x);
vector<int> u,w;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
w.pb(sum[y]);
if(y==wson[x])continue;
u.pb(sum[y]);
dfs0(y);
trp.dfs(v[y]),trp.init();
}
u.pb(-inf),u.pb(inf),sort(u.begin(),u.end());
if(wson[x]){
dfs0(wson[x]);
vector<int> U(u.size(),0);
for(int i=1;i<u.size();i++)U[i]=U[i-1]+u[i];
for(int i=u.size()-2;~i;i--){
// if(x==6)cout<<u.size()-i-1<<" "<<U[i]<<"!!!\n";
trp.am(u[i],u[i+1]-1,U[i],u.size()-i-1);
// trp.prt(),puts("!!!!!!!!!!");
}
}
if(x<=n)trp.insert(sum[x],x);
sort(w.begin(),w.end());
vector<int> Sum(w.size()+1,0);
for(int i=1;i<Sum.size();i++)Sum[i]=Sum[i-1]+w[i-1];
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
u.pb(sum[y]);
if(y==wson[x])continue;
vector<pair<int,int> > &vec=v[y];
for(int j=0;j<vec.size();j++){
int x=vec[j].X,id=vec[j].Y;
vector<int>::iterator fd=upper_bound(w.begin(),w.end(),x);
int A=Sum[fd-w.begin()],M=w.end()-fd+1;
if(fd==w.end()||sum[y]<*fd)A-=sum[y];
else M--;
trp.insert(M*x+A,id);
}
}
// cout<<x<<":";trp.prt();puts("");
}
int ans[N];
signed main(){
// freopen("j.in","r",stdin);freopen("j.out","w",stdout);
cin>>n;
if(!n)return 0;
for(int i=1;i<=n;i++){
scanf("%lld%lld",fa+i,a+i);
if(fa[i]==0)fa[i]=n+1;
nei[fa[i]].pb(i);
}
dfs();
trp.init();
dfs0();
vector<pair<int,int> > vec;
trp.dfs(vec);
assert((int)vec.size()==n);
for(int i=0;i<n;i++)ans[vec[i].Y]=vec[i].X;
for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
return 0;
}
L - Labyrinth
這個 2400 的題就略顯遜色了。
這樣考慮:當吃胖到一定程度時,某些邊就會消失。注意到:對於圖中邊權最小的邊 \((x,y)\),其它邊消失都不早於它,於是可以先讓它割開,如果不分裂那就沒事了,否則分裂成 \(x,y\) 分別所在的兩個連通塊 \(p,q\)。我們要在 \((x,y)\) 消失前將 \(p\) 或者 \(q\) 吃完,然后沿着這條邊走到 \(q,p\),將其吃完。
問題就轉化為兩個連通塊(子圖)的子問題,考慮求出其答案(最大初始寬度)\(dp_{p/q}\)。那么考慮原圖答案 \(dp_G\),應該是很容易通過 \(dp_{p/q}\) 求出的。假設先吃完 \(p\),再到 \(q\),那么我們有如下限制:
- \(dp_G\) 作為 \(p\) 的初始值要可行,即 \(dp_G\leq dp_p\)。
- 吃完 \(p\) 后要能通過 \((x,y)\),即 \(dp_G+sum_p\leq w(x,y)\)。
- \(dp_G+sum_p\) 作為 \(q\) 的初始值要可行,即 \(dp_G+sum_p\leq dp_q\)。
取個 min 即可。對先 \(q\) 類似,兩者取個 max。
分裂不太好處理,考慮時光倒流轉化為合並,用並查集輔助維護這個「無向圖上 DP」,跟最大生成樹 Kruskal 過程差不多。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=100010;
int n,m;
int a[N];
pair<int,pair<int,int> > eg[N];
struct ufset{
int fa[N];
ufset(){memset(fa,0,sizeof(fa));}
int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
}ufs;
int dp[N],sum[N];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(int i=1;i<=m;i++)scanf("%lld%lld%lld",&eg[i].Y.X,&eg[i].Y.Y,&eg[i].X);
sort(eg+1,eg+m+1);
for(int i=1;i<=n;i++)dp[i]=inf,sum[i]=a[i];
for(int i=m;i;i--){
int x=eg[i].Y.X,y=eg[i].Y.Y,w=eg[i].X;
x=ufs.root(x),y=ufs.root(y);
if(x==y)continue;
int dx=dp[x],dy=dp[y];
dp[x]=max(min(dx,min(w,dy)-sum[x]),min(dy,min(w,dx)-sum[y]));
ufs.fa[y]=x,sum[x]+=sum[y];
}
for(int i=1;i<=n;i++)if(ufs.fa[i]==0)return cout<<(dp[i]<=0?-1:dp[i])<<"\n",0;
return 0;
}