题意

现在有m个人,每一个人都特别喜欢狗。另外还有一棵n个节点的树。

现在每个人都想要从树上的某个节点走到另外一个节点,且满足要么这个人自带一条狗m,要么他经过的所有边h上都有一条狗。

2<=n<=2*104,1<=m<=104

输入格式

第一行为两个整数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;
}

【Codeforces】【网络流】【树链剖分】【线段树】ALT (CodeForces - 786E)的更多相关文章

  1. Water Tree CodeForces 343D 树链剖分+线段树

    Water Tree CodeForces 343D 树链剖分+线段树 题意 给定一棵n个n-1条边的树,起初所有节点权值为0. 然后m个操作, 1 x:把x为根的子树的点的权值修改为1: 2 x:把 ...

  2. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

  3. 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树

    [BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...

  4. BZOJ2243 (树链剖分+线段树)

    Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...

  5. POJ3237 (树链剖分+线段树)

    Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...

  6. bzoj4034 (树链剖分+线段树)

    Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...

  7. HDU4897 (树链剖分+线段树)

    Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...

  8. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  9. 【POJ3237】Tree(树链剖分+线段树)

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  10. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

随机推荐

  1. kubernetes云平台管理实战: pod资源共享(三)

    一.共享容器IP地址 1.查看容器进程 [root@k8s-node1 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS ...

  2. js 计算当年还剩多少时间的倒数计时 javascript 原理解析【复制到编辑器查看推荐】

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. Educational Codeforces Round 55 (Rated for Div. 2)

    D. Maximum Diameter Graph 题意 给出每个点的最大度,构造直径尽可能长的树 思路 让度数大于$1$的点构成链,考虑是否能在链的两端加度为$1$的点 代码 #include &l ...

  4. mybatis源码笔记

    mybatis设计总览 目录结构: 通过配置文件,获取SqlSessionFactory XMLConfigBuilder 解析配置文件,获取SqlSessionFactory private sta ...

  5. Tomcat zabbix监控、jmx监控、zabbix_java_gateway

    几种方式监控tomcat,如标题. 下面就是参考的网上的连接.自己可以试一下. 由于牵扯到jvm的很多东西, 在这里就只是粘贴处连接参考. http://www.cnblogs.com/chrisDu ...

  6. Jumpserver(堡垒机)的安装与应用

    官网:http://docs.jumpserver.org/zh/docs/introduce.html 作者:邓聪聪 环境 系统: CentOS 7.6 IP: 172.16.16.2 关闭 sel ...

  7. sed追加文本-sed脚本追加文本

    input为sed输入文件,内容如下: [root@node1 gitlab-test-]# cat inppu.txt aa bb cc dd 追加文本: 1.匹配  aa 行之后追加文本 We a ...

  8. Spring扩展自定义的XML标签

    在网上搜了许多,感觉不够全面,就找了官方文档,下面记录如何找到对应的文档进入 网上许多博客都是以dateformat为实例进行编写的,通过官方的foo,能够学到更多的东西,下面贴一段代码,在官方示例上 ...

  9. OpenCV3编程入门读书笔记5-边缘检测

    一.边缘检测的一般步骤 1.滤波 边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能. 2.增强 增强边缘的基础是确定图像各 ...

  10. Array数组小方法总结

    如果各位在阅读的时候,有任何问题,都可以留言: // push()方法会向数据末尾添加数据,并返回添加数据后的数组的长度var arr=[1,2,3]console.log(arr.push(4),a ...