题意

有一棵 \(n\) 个点的树和 \(m\) 个人,第 \(i\) 个人从 \(u_i\) 走到 \(v_i\)

现在要发宠物,要求一个人要么他自己发到宠物,要么他走的路径上的都有宠物。

求最小代价,并输出任意方案。

\(n,m \leq 20000\)

传送门

思路

对每个人和每条树边都建一个点。

源点向每个人连容量 \(1\) 的边,每条树边向汇点连容量 \(1\) 的边。每个人向他要走到的所有边连容量 \(+\infty\) 的边。

给人发就是割掉人与源的边,放边上就是割树边与汇的边,人与树边间的边不能割。当源汇不连通的时候,就是满足题意的,题目转化为最小割。

问题出在连边上,这是 \(nm\) 的,考虑如何优化连边。线段树?树上问题有点麻烦,想不到倍增。

对于一段相邻的\(2^i\)个点,都建一个点与它们所有相连,其实又有点像线段树,然后就会对于每条链得到一棵类似线段树的东西?大区间连向小区间,看起来就是。



然后对每个人,将他的路径在 LCA 处分成两条路径,这两条路径分别向对应区间覆盖。

那么我们得到的新图有 \(n\log n+m\) 个点和 \(n\log n+4m\) 条边,可以通过。

这里还要输出方案。

我们知道,最小割中的边一定满流。(全局最大流,分成两个集合,其间的边一定是满流的,而也就是最小割)因此我们从源点开始 dfs,只走没满流的边,并标记被 dfs 到的点。则图被分成两部分,一部分被访问过,一部分没被访问过。其中间那些边就是一个最小割。

然后,如果一个人代表的点没被访问过,则说明他所属那条边被割了。如果一条树边代表的点被访问过,则说明它所属那条边被割了。记录标号输出

#include <bits/stdc++.h>
using std::queue;
const int W=14,N=20005*(W+1),M=N+N*4;
int to[M<<1],w[M<<1],Next[M<<1],edge,n,x,y,f[20005][W+1],idn[N],deep[N],last[N],b[20005][W+1];
int s,t,m,cnt,ans,tag[N],cur[N];
queue <int> q;
void add(int x,int y,int z){
to[++edge]=y;
Next[edge]=last[x];
last[x]=edge;
w[edge]=z;
}
void dfs(int x,int fa){
f[x][0]=fa,deep[x]=deep[fa]+1;
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa) {
idn[to[i]]=(i+1)/2;
dfs(to[i],x);
}
}
void Add(int x,int y,int w){
add(x,y,w);
add(y,x,0);
}
int lca(int x,int y){
if (deep[x]<deep[y]) std::swap(x,y);
for (int i=W;i>=0;i--)
if (deep[f[x][i]]>=deep[y]) x=f[x][i];
if (x==y) return x;
for (int i=W;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void addedge(int x,int y,int id){
for (int i=W;i>=0;i--)
if (deep[f[x][i]]>=deep[y]){
Add(id,b[x][i],n+1);
x=f[x][i];
}
}
bool bfs(){
for (int i=0;i<=t;i++) cur[i]=last[i],deep[i]=0;
deep[s]=1;
q.push(s);
while (!q.empty()){
int x=q.front();
q.pop();
for (int i=last[x];i;i=Next[i])
if (w[i] && !deep[to[i]]){
deep[to[i]]=deep[x]+1;
q.push(to[i]);
}
}
return (deep[t]);
}
int c(int x) { return x&1?x+1:x-1; }
int dfs(int x){
if (x==t) return 1;
for (int i=cur[x];i;i=Next[i]){
cur[x]=i;
int u=to[i];
if (deep[u]>deep[x] && w[i]){
int di=dfs(to[i]);
if (di){
w[i]--;
w[c(i)]++;
return 1;
}
}
}
return 0;
}
void DFS(int x){
tag[x]=1;
for (int i=last[x];i;i=Next[i])
if (w[i] && !tag[to[i]]) DFS(to[i]);
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y,0),add(y,x,0);
}
s=0;
dfs(1,0);
edge=0;memset(last,0,sizeof(last));
cnt=n;
for (int i=2;i<=n;i++) b[i][0]=i;
for (int i=1;i<=W;i++)
for (int j=1;j<=n;j++)
if (f[f[j][i-1]][i-1]){
f[j][i]=f[f[j][i-1]][i-1];
Add(++cnt,b[j][i-1],n+1);
Add(cnt,b[f[j][i-1]][i-1],n+1);
b[j][i]=cnt;
}
for (int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
Add(s,++cnt,1);
idn[cnt]=i;
int l=lca(x,y);
addedge(x,l,cnt),addedge(y,l,cnt);
}
t=cnt+1;
for (int i=2;i<=n;i++) Add(i,t,1);
while (bfs())
while (dfs(s)==1) ans++;
printf("%d\n",ans);
DFS(s);
ans=0;
for (int i=last[s];i;i=Next[i])
if (tag[to[i]]!=tag[s]) ans++;
printf("%d ",ans);
for (int i=last[s];i;i=Next[i])
if (tag[to[i]]!=tag[s]) printf("%d ",idn[to[i]]);
puts("");
ans=0;
for (int i=last[t];i;i=Next[i])
if (tag[to[i]]!=tag[t]) ans++;
printf("%d ",ans);
for (int i=last[t];i;i=Next[i])
if (tag[to[i]]!=tag[t]) printf("%d ",idn[to[i]]);
return 0;
}

后记

追随神仙的脚步

抄袭于此Mrsrz

CF786E ALT的更多相关文章

  1. cf786E ALT (最小割+倍增优化建图)

    如果把“我全都要”看作是我全不要的话,就可以用最小割解决啦 源点S,汇点T 我们试图让每个市民作为一个等待被割断的路径 把狗狗给市民:建边(S,i,1),其中i是市民 把狗狗给守卫:建边(j,T,1) ...

  2. 一句话题解&&总结

    CF79D Password: 差分.两点取反,本质是匹配!最短路+状压DP 取反是套路,匹配是发现可以把操作进行目的化和阶段化,从而第二次转化问题. 且匹配不会影响别的位置答案 sequence 计 ...

  3. [No000093]按住Alt 再按数字键敲出任意汉字和字符!

    1.在notepad里,(中文系统下) 按住Alt 然后按52946最后放开Alt 按住Alt 然后按45230最后放开Alt 按住Alt 然后按50403最后放开Alt 你会看到"我爱你& ...

  4. [No00008B]远程桌面发送“Ctrl+Alt+Delete”组合键调用任务管理器

    向远程桌面发送"Ctrl+Alt+Delete"组合键的两种方法 1.在本地按下Ctrl+Alt+End,可以成功发送"Ctrl+Alt+Delete"组合键! ...

  5. 平常看到的Alt+xx 快捷键用法

    1. 先按Alt, 哪一个菜单对应的字符是有划线的. 2. 输入对应的字符打开相应的菜单, 3 再输入相应的字符打开子菜单

  6. windows 中去除Ctrl+Alt+Del才能登录

    安装windows 7后登录的时候有一样很麻烦的步骤是需要先按Ctrl+Alt+Del,才能输入用户密码进行登录.这里笔者介绍一下如何取消这个东西. 点击“开始菜单”,点击“控制面板”. [管理工具] ...

  7. TSql 巧用Alt 键

    1,查看表的信息 在TSql 编辑器中,选中一个表,如图 点击Alt+F1,就可以查看表的属性定义 2,使用alt批量插入逗号 在Tsql中使用 in 子句,在(value_List)列表中,经常有很 ...

  8. title与alt的区别

    html中的title属性和alt属性让人有些混淆. 以前不知道有title这个属性,第一次用到它时,就和alt产生了混淆.一位朋友告诉我说,alt是图片img标签里用的,title是超链接里用的, ...

  9. UML序列图总结(Loop、Opt、Par和Alt)

    序列图主要用于展示对象之间交互的顺序. 序列图将交互关系表示为一个二维图.纵向是时间轴,时间沿竖线向下延伸.横向轴代表了在协作中各独立对象的类元角色.类元角色用生命线表示.当对象存在时,角色用一条虚线 ...

随机推荐

  1. PHP即时实时输出内容

    一般情况下,PHP都是将整个页面全部执行完成后,才会把要输出的内容发送回客户端. for ($i = 0; $i < 10; $i++) { echo $i; sleep(1); } 这段代码会 ...

  2. RabbitMQ 应用二

    在应用一中,基本的消息队列使用已经完成了,在实际项目中,一定会出现各种各样的需求和问题,RabbitMQ内置的很多强大机制和功能会帮助我们解决很多的问题,下面就一个一个的一起学习一下. 消息响应机制 ...

  3. Asp.net Report动态生成

    rdlc报表实质上是一个xml文件,如果要实现动态报表,就需要动态生成rdlc文件,实质上就是读写xml文件: protected XmlDocument GenerationAddReportCol ...

  4. cmake用法及常用命令总结(全)

    CMakeLists.txt 的语法比较简单,由命令.注释和空格组成,其中命令是不区分大小写的.指令是大小写无关的,参数和变量是大小写相关的.但推荐全部使用大写指令.符号 # 后面的内容被认为是注释. ...

  5. 【转载】IIS网站如何同时解析带www和不带www的域名

    针对公网上线的网站系统,很多网站的域名会同时含有带www和不带www的域名解析记录,如果需要同时解析带www和不带www的域名信息,则需要在相应的域名解析平台(如阿里云域名解析平台.腾讯云域名解析平台 ...

  6. Jmeter学习笔记(四)配置元件之计数器

    在接口测试中,有时候需要重复跑一个接口,里面的某个参数的值不能每次都一样,这个时候我们可以使用计数器来实现,名称+计数器. 1.线程组->添加->配置元件->计数器 2.添加效果如下 ...

  7. java网络编程--httpurlconnection

    HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现.如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行.可以通过以下两个语句来 ...

  8. MongoDB官方推荐的GUI工具-Compass的使用

    探索和操作MongoDB数据的最简单方法 用于MongoDB的GUI.可视化地查看数据.以秒为单位运行临时查询.使用完整的CRUD功能与数据交互.查看和优化查询性能.可在Linux.Mac或Windo ...

  9. python接口自动化1-requests-html支持JavaScript渲染页面

    前言 requests虽好,但有个遗憾,它无法加载JavaScript,当访问一个url地址的时候,不能像selenium一样渲染整个html页面出来.requests-html终于可以支持JavaS ...

  10. vue 打印html

    1.https://github.com/xyl66/vuePlugs_printjs从这个路径下载print.js.放到你的代码中 2.我是放到我本地一个js文件中. 3.引入当前文件 //打印插件 ...