【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) ][1]类似的
[1]: https://www.cnblogs.com/T-Y-P-E/p/10176648.html 有一个比较暴力的想法,就是直接按照最原始的方式建图:
源点连向每一个人,然后每一个人连向每一条这个人的路径经过的边,然后所有的边再连向汇点。然后跑最大流得到最小割,割掉的边如果是与人相连的就是这个人自带狗;如果是与边相连的,就是这条边上放了狗,就可以求方案了。
然而现在问题来了,我们可以很轻松的构造一些数据使得每一个人连出去的边高达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;
}