题意

现在有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. elastalert 配置post告警方式(备忘)

      最近在做把elk告警日志发送到kinesis 流,供后续数据分析处理使用........ 基于尽量不修改elastalert ,把修改工作放到接收端服务的原则.计划把elk的告警数据通过远程api ...

  2. 洛谷P3369 普通平衡树

    刚学平衡树,分别用了Splay和fhq-treap交了一遍. 这是Splay的板子,貌似比较短? Splay #include <iostream> #include <cstdio ...

  3. thinkphp5.0 分页中伪静态的处理

    1.修改文件\thinkphp\library\think\Paginator.php(此文件用于分页) isurl是否为伪静态 加入isurl用于判断是否使用伪静态分页 */ protected $ ...

  4. goroutine 和 线程的区别

    我们在使用Go语言进行开发时,一般会使用goroutine来处理并发任务.那么大家有没有考虑过goroutine的实现机制是什么样的?很多同学会把goroutine与线程等同起来,但是实际上并不是这样 ...

  5. 金融量化分析【day113】:聚宽自带策略

    一.策略代码 # 导入函数库 from jqdata import * # 初始化函数,设定基准等等 def initialize(context): # 设定沪深300作为基准 set_benchm ...

  6. oldboy s21day09

    #!/usr/bin/env python# -*- coding:utf-8 -*- # 1.将函数部分知识点,整理到自己笔记中.(搞明白课上讲的案例.) # 2.写函数,检查获取传入列表或元组对象 ...

  7. NightWatch端到端测试

    NightWatch http://nightwatchjs.org/ Nightwatch.js Browser automated testing done easy. Write End-to- ...

  8. ES6走一波 数组的扩展

    Array flat 数组实例的扁平化方法(浏览器支持不佳) 建议使用 lodash的 flatten

  9. windows的git的安装和配置

    下载并安装git(安装过程中采用默认选项) 进入gitbash(gitbash集成了windows和linux的命令) 使用git --version查看是否安装成功: 用vim .gitconfig ...

  10. innobackupex的流备份【转】

    并行备份 innobackupex -p123123 --parallel= /backup 节流备份(节省IO) innobackupex -p123123 --throttle= /backup ...