【Codeforces】【網路流】【樹鏈剖分】【線段樹】ALT (CodeForces - 786E)
題意
現在有m個人,每一個人都特別喜歡狗。另外還有一棵n個節點的樹。
現在每個人都想要從樹上的某個節點走到另外一個節點,且滿足要麼這個人自帶一條狗m,要麼他經過的所有邊h上都有一條狗。
2<=n<=2*10^4,1<=m<=10^4
輸入格式
第一行為兩個整數n,m,分別表示樹的大小和人數。
接下來有n-1行,每一行有兩個整數u,v,表示書上有一條u-v的邊。
再接下來有m行,每一行兩個整數x[i],y[i]表示第i個人想從x[i]走到y[i]。
輸出格式
第一行為一個整數k,表示一共有多少條狗。
第二行開頭為一個整數表示一個有多少個人自帶狗,然後是帶狗的人的編號。
第三行開頭為一個整數e,表示有多少條邊上放了狗,然後是e個整數表示放了狗的邊的編號。
思路
首先,和Oleg and chess (CodeForces - 793G)類似的
源點連向每一個人,然後每一個人連向每一條這個人的路徑經過的邊,然後所有的邊再連向匯點。然後跑最大流得到最小割,割掉的邊如果是與人相連的就是這個人自帶狗;如果是與邊相連的,就是這條邊上放了狗,就可以求方案了。
然而現在問題來了,我們可以很輕鬆的構造一些資料使得每一個人連出去的邊高達O(n)條,是的總的邊數達到O(n*n)條,然後乖乖T掉,這就不太好了。
下面我們考慮優化。
根據網上的題解,k大概有兩種優化建圖的方式,也就是倍增優化建圖以及樹鏈剖分優化,這裡主要介紹後者。
話說樹剖還真是個好東西,直接就讓數軸上的演算法跑到了樹上,常數還小,甚至媲美O(nlogn)
回到正題,還是看這道題如何用樹剖優化。對於樹上的x[i]到y[i],我們可以在樹剖往上爬的時候,將這條路徑對應到線段樹上的若干個子線段,然後在讓第i個人與其一一連邊。個人感覺就是開頭那道題的簡化版拿到樹上之後的操作。這樣子連邊就是十分優秀的了,至少不是nn的了m,但估計是nlog^2(n)級別的複雜度。就這樣,我們就成功的將建圖優化了。
但是這都不夠!!因為它還要輸出方案。在前文所說的暴力的方法中,我們已經有了一個大概的思路,然而還是有具體的細節需要說明:
首先是如何找割邊。因為流量的特殊性,所以說割邊一定是與源點g或者是匯點直接相連的。我們可以在跑完網路流之後,再從源點開始遍歷整個圖,只走有殘餘容量的邊,然後能夠到達的點就一定是屬於S這半邊的,然後其餘的點就是屬於T那半邊的了。
分成兩部分之後,割邊自然就是u->v,其中u屬於S那邊,v屬於T那邊。然後就找到割邊了。
這裡值得一提的是,為了加快演算法,最後可以只看與源點或匯點直接相連的邊是不是割邊(自行理解)。
這裡還是給出圖的大概樣子。
其中右邊那個就是線段樹了。
程式碼
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 20000
#define INF 0x3FFFFFFF
using namespace std;
struct edge
{
int to,id;
edge(){};
edge(int _to,int _id):to(_to),id(_id){};
};
struct node
{
int ch[2];
}tree[MAXN*4];
struct Node
{
int to,cap;
Node *nxt,*bck;
}edges[MAXN*500];
Node *ncnt=&edges[0],*Adj[MAXN*10+5];
int tcnt;
vector<edge> G[MAXN+5];
//vector<int> seq;
int S,T;
int n,m,dcnt,rt;
int fro[MAXN+5],to[MAXN+5];
int edid[MAXN*500+5];
int treefa[MAXN+5],faid[MAXN+5],dep[MAXN+5],dfn[MAXN+5],rnk[MAXN+5];
int son[MAXN+5],top[MAXN+5],siz[MAXN+5];
int d[MAXN*10+5],vd[MAXN*10+5];
bool vis[MAXN*10+5],spedge[MAXN*500+5],spper[MAXN+5];
void AddEdge(int u,int v,int cap)
{
Node *p=++ncnt;
p->to=v;p->cap=cap;
p->nxt=Adj[u];Adj[u]=p;
Node *q=++ncnt;
q->to=u;q->cap=0;
q->nxt=Adj[v];Adj[v]=q;
p->bck=q,q->bck=p;
}
void DFS1(int u,int fa)
{
siz[u]=1;
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i].to,id=G[u][i].id;
if(v==fa)
continue;
dep[v]=dep[u]+1;
faid[v]=id;treefa[v]=u;
DFS1(v,u);
siz[u]+=siz[v];
if(son[u]==0||siz[v]>siz[son[u]])
son[u]=v;
}
}
void DFS2(int u,int fa,int tp)
{
dfn[u]=++dcnt;rnk[dcnt]=u;
top[u]=tp;
if(son[u]!=0)
DFS2(son[u],u,tp);
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i].to;
if(v==fa||v==son[u])
continue;
DFS2(v,u,v);
}
}
void Build_SegTree(int &p,int l,int r)
{
p=++tcnt;
if(l==r)
return;
int mid=(l+r)/2;
Build_SegTree(tree[p].ch[0],l,mid);
Build_SegTree(tree[p].ch[1],mid+1,r);
}
void Bianli_SegTree(int p,int l,int r)
{
if(l==r)
{
edid[p]=faid[rnk[l]];
AddEdge(p,T,1);
return;
}
int mid=(l+r)/2;
AddEdge(p,tree[p].ch[0],INF);
AddEdge(p,tree[p].ch[1],INF);
Bianli_SegTree(tree[p].ch[0],l,mid);
Bianli_SegTree(tree[p].ch[1],mid+1,r);
}
void Process_Tree()
{
DFS1(1,-1);
DFS2(1,-1,1);
tcnt=m;
Build_SegTree(rt,1,n);
S=0,T=tcnt+1;
Bianli_SegTree(rt,1,n);
}
void Query_SegTree(int p,int l,int r,int ql,int qr,int per)
{
if(qr<l||ql>r)
return;
if(ql<=l&&r<=qr)
{
AddEdge(per,p,INF);
return;
}
int mid=(l+r)/2;
Query_SegTree(tree[p].ch[0],l,mid,ql,qr,per);
Query_SegTree(tree[p].ch[1],mid+1,r,ql,qr,per);
}
void Query_Tree(int per,int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
Query_SegTree(rt,1,n,dfn[top[x]],dfn[x],per);
x=treefa[top[x]];
}
if(dep[x]>dep[y])
swap(x,y);
if(x!=y)
Query_SegTree(rt,1,n,dfn[son[x]],dfn[y],per);
}
void Build_Gragh()
{
for(int i=1;i<=m;i++)
{
AddEdge(S,i,1);
Query_Tree(i,fro[i],to[i]);//利用樹鏈剖分的詢問操作建邊
}
}
int aug(int u,int tot)
{
if(u==T)
return tot;
int sum=0,mind=T+1,delta,v;
for(Node *p=Adj[u];p!=NULL;p=p->nxt)
{
v=p->to;
if(p->cap>0)
{
if(d[u]==d[v]+1)
{
delta=min(tot-sum,p->cap);
delta=aug(v,delta);
sum+=delta;
p->cap-=delta,p->bck->cap+=delta;
if(d[S]>=T+1)
return sum;
if(sum==tot)
break;
}
mind=min(mind,d[v]);
}
}
if(sum==0)
{
vd[d[u]]--;
if(vd[d[u]]==0)
d[S]=T+1;
d[u]=mind+1;
vd[d[u]]++;
}
return sum;
}
int Isap()
{
int flow=0;
memset(d,0,sizeof(d));
memset(vd,0,sizeof(vd));
vd[0]=T+1;
while(d[S]<T+1)
flow+=aug(S,INF);
return flow;
}
void DFS(int u)
{
vis[u]=true;
for(Node *p=Adj[u];p!=NULL;p=p->nxt)
{
int v=p->to;
if(p->cap>0&&vis[v]==false)
DFS(v);
}
}
void Get_Plan()
{
DFS(S);//DFS求出與S相連的點是哪些
for(Node *p=Adj[S];p!=NULL;p=p->nxt)//直接列舉與源點相連的點
{
int j=p->to;
if(vis[j]==false)
spper[j]=true;//special person
}
for(Node *p=Adj[T];p!=NULL;p=p->nxt)//直接列舉與匯點相連的點
{
int j=p->to;
if(vis[j]==true)
spedge[edid[j]]=true;//special edge
}
int sppernum=0,spedgenum=0;
for(int i=1;i<=m;i++)
if(spper[i])
sppernum++;
for(int i=1;i<=n;i++)
if(spedge[faid[i]])
spedgenum++;
printf("%d",sppernum);
for(int i=1;i<=m;i++)
if(spper[i])
printf(" %d",i);
printf("\n");
printf("%d",spedgenum);
for(int i=1;i<n;i++)
if(spedge[i]==true)
printf(" %d",i);
printf("\n");
}
int main()
{
scanf("%d %d",&n,&m);
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
G[u].push_back(edge(v,i));
G[v].push_back(edge(u,i));
}
for(int i=1;i<=m;i++)
scanf("%d %d",&fro[i],&to[i]);
Process_Tree();//樹鏈剖分預處理
Build_Gragh();//網路流建圖
// if(n==20000)
// return 0;
int ans=Isap();//網路流求答案
printf("%d\n",ans);
Get_Plan();//求割掉的邊
return 0;
}