【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;
    }
posted @ 2018-12-26 01:12  T_Y_P_E  阅读(313)  评论(0编辑  收藏  举报