【Codeforces】【网络流】【树链剖分】【线段树】ALT (CodeForces - 786E)
题意
现在有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)的更多相关文章
- Water Tree CodeForces 343D 树链剖分+线段树
Water Tree CodeForces 343D 树链剖分+线段树 题意 给定一棵n个n-1条边的树,起初所有节点权值为0. 然后m个操作, 1 x:把x为根的子树的点的权值修改为1: 2 x:把 ...
- 【BZOJ-2325】道馆之战 树链剖分 + 线段树
2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 1153 Solved: 421[Submit][Statu ...
- 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树
[BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...
- BZOJ2243 (树链剖分+线段树)
Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...
- POJ3237 (树链剖分+线段树)
Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...
- bzoj4034 (树链剖分+线段树)
Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...
- HDU4897 (树链剖分+线段树)
Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...
- 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 ...
- 【POJ3237】Tree(树链剖分+线段树)
Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...
- HDU 2460 Network(双连通+树链剖分+线段树)
HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...
随机推荐
- UOJ#348 州区划分
解:有一个很显然的状压...... 就设f[s]表示选的点集为s的时候所有方案的权值和. 于是有f[s] = f[s \ t] * (sum[t] / sum[s])P. 这枚举子集是3n的. 然后发 ...
- Day040--HTML&CSS
内容回顾: 标签分类: (1)行内标签 span 小跨度的标签 i em a 特点: (1)在一行内显示 (2)不能设置宽高,如果不设置宽高,默认是内容的宽高 (2)块级标签 h1~h6 h1页面中尽 ...
- 2017-12-19python全栈9期第四天第二节之列表的增删查改之按切片删除
#!/user/bin/python# -*- coding:utf-8 -*-li = ['zs','ls','ww','zl','xx']# del li[1:] #1到最后# print(li) ...
- python 线程/线程锁/信号量
单线程 #常规写法 import threading import time def sayhi(num): # 定义每个线程要运行的函数 print("running on number: ...
- 分布式监控系统开发【day37】:监控客户端开发(五)
一.目录结构 二.模块方法调用关系总图 三.入口文件main 1.解决了说明问题 1.客户端就干了一件事情,干什么事情 收集数据汇报给服务端? 但是我这个客户端是插件形式2.首先必须要传一个参数,st ...
- 第十六节:语法总结(3)(C#6.0和C#7.0新语法)
一. C# 6.0 新语法 1. 自动属性初始化可以赋值 /// <summary> /// 自动属性初始化 /// </summary> public class UserI ...
- [再寄小读者之数学篇](2014-06-20 Beta 函数)
令 $\dps{B(m,n)=\sum_{k=0}^n C_n^k \cfrac{(-1)^k}{m+k+1}}$, $m,n\in\bbN^+$. (1) 证明 $B(m,n)=B(n,m)$; ( ...
- vue中使用swiper-slide时,循环轮播失效?
前言 vue 项目中使用时,组件swiper-slide 如果用v-for循环的话,loop:true 就不能无缝轮播,每次轮播到最后一张就停止了??? 正文 代码如下: <swiper :op ...
- SQL Server - Partition by 和 Group by对比
参考:https://www.cnblogs.com/hello-yz/p/9962356.html —————————————————— 今天大概弄懂了partition by和group by的区 ...
- SQL Server - ISNULL
ISNULL 使用指定的替换值替换 NULL. 语法 ISNULL ( check_expression , replacement_value ) 参数 check_express ...