原文链接https://www.cnblogs.com/zhouzhendong/p/CF980F.html

题目传送门 - CF980F

题意

  给定一个 $n$ 个节点 $m$ 条长为 $1$ 的边的每个最多只属于一个环的仙人掌。

  现在请你通过删边把仙人掌转化成树。

  对于每一个点,输出在所有不同的删边方案中,  距离该点最远的点与他之间的距离值 的最小值。

  $n\leq 5\times 10^5$

题解

  首先,我们跑一跑 Tarjan ,找出每一个双联通分量。

  然后我们把每一个双联通分量里面的点按照顺序存在 vector 里面。

  我们称每一个环中连向环的父亲环的节点为该环的 环根

  然后我们考虑从下网上跑一跑 树形DP ,处理出以每一个点为根的子仙人掌中所有不同的删边方式中最远点距离的最小值。

  这次 DP 结束后,我们记第 $i$ 个节点的结果为 $Deep_i$ 。其中,对于任意环根 $i$,特殊地, $Deep_i$ 的涉及范围为以当前环为根的子仙人掌。

  为了后面的方便,对于任意环根我们还需要记录两个值 : $spDeep_i,\ \ usDeep_i$ 。

  具体含义见下图:

  

  对于环根 $x$ ,显然有 $Deep_x=\max(spDeep_x,usDeep_x)$,要求 $spDeep_x$ 可以通过预处理前后缀 $\max$,然后枚举该环的断边来得到。

  

  下一步,我们需要处理一个 $Far_i$。

  对于环根: $Far_i$ 表示下一步从该环根上行,最优情况下的最远距离。

  对于普通的点: $Far_i$ 表示下一步从该点向同环的点走,最优情况下的最远距离。

  只要处理出这个东西,则第 $i$ 个节点的答案就是 $\max(Far_i,Deep_i)$ 了。

  我们考虑一下那些会对 $Far_i$ 有贡献的节点。

  第一种,当前点不是环根,则向同环的节点走会有贡献。

  第二种,当前点是环根,向同环的点和向父亲走都会有贡献。注意,这个向父亲走的贡献有两种,一种是由父亲的其他子节点来的,一种是由父亲的同环节点来的(父亲的 $Far$ )。

  如果是环根,那么我们特殊处理即可。

  现在考虑处理非环根的 $Far$ 。

  通过证(感性)明(理解),我们可以发现,对于当前环,当起始点在被顺时针遍历的时候,最优方案下删掉的边也是 大致 顺时针移动的。

  我们可以维护当前点两个方向的节点最大贡献值尽量平衡来获取最优解。

  于是我们可以用两个单调队列来实现。这一部分是关键。需要把环铺成三倍长度,并加上特定的权值。

  这里具体不展开描述了,可以参见代码。

  我由于弹出单调队列 QR 的时候少弹了一个位置, wa on test 18,找了5个多小时QAQ 。

代码

#include <bits/stdc++.h>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=500005,M=N*4;
struct Gragh{
//2M*2*4B+0.5M*4B=18MB
int cnt,y[M],nxt[M],fst[N];
void clear(){
cnt=1;
memset(fst,0,sizeof fst);
}
void add(int a,int b){
y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g,g2;
int n,m;
int dfn[N],low[N],inst[N],st[N],vis[N],id[N],Time,top,tot;
int used[M];
int Fa[N];
vector <int> cir[N],son[N];
void Tarjan_Prepare(){
Time=top=tot=0;
memset(id,0,sizeof id);
memset(st,0,sizeof st);
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
memset(vis,0,sizeof vis);
memset(inst,0,sizeof inst);
memset(used,0,sizeof used);
}
void Tarjan(int x){
dfn[x]=low[x]=++Time;
vis[x]=inst[x]=1;
st[++top]=x;
for (int i=g.fst[x];i;i=g.nxt[i]){
if (used[i/2])
continue;
used[i/2]=1;
if (!vis[g.y[i]]){
Tarjan(g.y[i]);
low[x]=min(low[x],low[g.y[i]]);
}
else if (inst[g.y[i]])
low[x]=min(low[x],low[g.y[i]]);
}
if (dfn[x]==low[x]){
tot++;
id[st[top]]=tot;
inst[st[top]]=0;
while (st[top--]!=x){
id[st[top]]=tot;
inst[st[top]]=0;
}
}
}
void Get_cir(int x){
if (vis[x])
return;
vis[x]=1;
cir[id[x]].push_back(x);
for (int i=g.fst[x];i;i=g.nxt[i]){
int y=g.y[i];
if (id[y]==id[x])
Get_cir(y);
}
}
void build(int x,int pre){
int ID=id[x];
Fa[ID]=pre;
cir[ID].clear();
Get_cir(x);
for (int i=0;i<cir[ID].size();i++){
int u=cir[ID][i];
son[u].clear();
for (int j=g.fst[u];j;j=g.nxt[j]){
int v=g.y[j];
if (id[v]==ID||id[v]==pre)
continue;
son[u].push_back(v);
build(v,ID);
}
}
}
int Deep[N];
// The Minimum value of the distance between
// x and the deepest posterity of x
// 环根: 以该环为根的子树中,距离 x 最远的点与其距离的 最小值
// 非环根: 该节点所有子节点(显然都是环根)的 Deep 最大值 + 1
int spDeep[N],usDeep[N];
// spDeep[x] x 为环根,环边节点给他的贡献
// usDeep[x] x 为环根,桥边子树给他的贡献
int Lmax[N],Rmax[N];
void Get_Deep(int x){
int ID=id[x];
for (int i=0;i<cir[ID].size();i++){
int u=cir[ID][i],v;
Deep[u]=0;
for (int j=0;j<son[u].size();j++){
v=son[u][j];
Get_Deep(v);
Deep[u]=max(Deep[u],Deep[v]+1);
}
}
int n=cir[ID].size()-1;
Lmax[0]=Rmax[n+1]=0;
for (int i=1;i<=n;i++)
Lmax[i]=max(Lmax[i-1],Deep[cir[ID][i]]+i);
for (int i=n;i>=1;i--)
Rmax[i]=max(Rmax[i+1],Deep[cir[ID][i]]+(n-i+1));
int v=1e9;
for (int i=1;i<=n+1;i++)
v=min(v,max(Lmax[i-1],Rmax[i]));
Deep[x]=max(usDeep[x]=Deep[x],spDeep[x]=v);
}
int Far[N];
// The Minimum value of the distance between
// x and the farthest vetrex which isn't a posterity of x
// 环根: 由其父亲继承
// 非环根: 环内单调队列跑出来
int val[N*3];
int Lv[N*3],Rv[N*3];
struct Monotone_Queue{
int head,tail,q[N*3],d[N*3];
void clear(){
head=1,tail=0;
}
int front(){
return head<=tail?q[head]:-1e9;
}
void push(int v,int _d){
while (head<=tail&&q[tail]<=v)
tail--;
q[++tail]=v,d[tail]=_d;
}
void pop(int _d){
while (head<=tail&&d[head]<_d)
head++;
}
int Next_front(int _d){
if (head>tail)
return -1e9;
if (d[head]!=_d)
return q[head];
return head==tail?-1e9:q[head+1];
}
}QL,QR;
vector <int> LRmax[N];
void Solve(int x,int far){
int ID=id[x];
Far[x]=far;
int n=cir[ID].size();
for (int i=1;i<n;i++)
val[i]=Deep[cir[ID][i]];
val[0]=max(usDeep[x],Far[x]);
for (int i=n;i<n*3;i++)
val[i]=val[i%n];
for (int i=0;i<n*3;i++){
Lv[i]=val[i]+n*3-i;
Rv[i]=val[i]+i+1;
}
QL.clear(),QR.clear();
int Lp=2,Rp=n+1;
for (int i=Lp;i<=n;i++)
QL.push(Lv[i],i);
for (int i=n+1;i<n*2;i++){
int result=1e9;
int v1=n*3-i,v2=i+1;
while (Rp<i){
Lp++,QL.pop(Lp);
Rp++;
if (Rp!=i)
QR.push(Rv[Rp],Rp);
}
while (QL.Next_front(Lp)-v1>max(QR.front(),Rv[Rp+1])-v2){
result=min(result,QL.front()-v1);
Lp++,QL.pop(Lp);
Rp++,QR.push(Rv[Rp],Rp);
}
result=min(result,QL.front()-v1);
result=min(result,max(QR.front(),Rv[Rp+1])-v2);
QL.push(Lv[i],i);
QR.pop(i+2);
Far[cir[ID][i%n]]=result;
}
for (int i=0;i<n;i++){
int u=cir[ID][i];
int m=son[u].size();
Lmax[0]=Rmax[m+1]=0;
for (int j=1;j<=m;j++)
Lmax[j]=max(Lmax[j-1],Deep[son[u][j-1]]+1);
for (int j=m;j>=1;j--)
Rmax[j]=max(Rmax[j+1],Deep[son[u][j-1]]+1);
LRmax[u].clear();
for (int j=0;j<m;j++)
LRmax[u].push_back(max(Lmax[j],Rmax[j+2]));
for (int j=0;j<m;j++)
Solve(son[u][j],max(spDeep[u],max(Far[u],LRmax[u][j]))+1);
}
}
int main(){
scanf("%d%d",&n,&m);
g.clear();
for (int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b);
g.add(a,b);
g.add(b,a);
}
Tarjan_Prepare();
for (int i=1;i<=n;i++)
if (!vis[i])
Tarjan(i);
memset(vis,0,sizeof vis);
build(1,0);
Get_Deep(1);
Solve(1,0);
for (int i=1;i<=n;i++)
printf("%d ",max(Far[i],Deep[i]));
return 0;
}

  

再放一份打满调试语句的代码:

#include <bits/stdc++.h>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=500005,M=N*4;
struct Gragh{
//2M*2*4B+0.5M*4B=18MB
int cnt,y[M],nxt[M],fst[N];
void clear(){
cnt=1;
memset(fst,0,sizeof fst);
}
void add(int a,int b){
y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g,g2;
int n,m;
int dfn[N],low[N],inst[N],st[N],vis[N],id[N],Time,top,tot;
int used[M];
int Fa[N];
vector <int> cir[N],son[N];
void Tarjan_Prepare(){
Time=top=tot=0;
memset(id,0,sizeof id);
memset(st,0,sizeof st);
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
memset(vis,0,sizeof vis);
memset(inst,0,sizeof inst);
memset(used,0,sizeof used);
}
void Tarjan(int x){
dfn[x]=low[x]=++Time;
vis[x]=inst[x]=1;
st[++top]=x;
for (int i=g.fst[x];i;i=g.nxt[i]){
if (used[i/2])
continue;
used[i/2]=1;
if (!vis[g.y[i]]){
Tarjan(g.y[i]);
low[x]=min(low[x],low[g.y[i]]);
}
else if (inst[g.y[i]])
low[x]=min(low[x],low[g.y[i]]);
}
if (dfn[x]==low[x]){
tot++;
id[st[top]]=tot;
inst[st[top]]=0;
while (st[top--]!=x){
id[st[top]]=tot;
inst[st[top]]=0;
}
}
}
void Get_cir(int x){
if (vis[x])
return;
vis[x]=1;
cir[id[x]].push_back(x);
for (int i=g.fst[x];i;i=g.nxt[i]){
int y=g.y[i];
if (id[y]==id[x])
Get_cir(y);
}
}
void build(int x,int pre){
int ID=id[x];
Fa[ID]=pre;
cir[ID].clear();
Get_cir(x);
for (int i=0;i<cir[ID].size();i++){
int u=cir[ID][i];
son[u].clear();
for (int j=g.fst[u];j;j=g.nxt[j]){
int v=g.y[j];
if (id[v]==ID||id[v]==pre)
continue;
son[u].push_back(v);
build(v,ID);
}
}
}
int Deep[N];
// The Minimum value of the distance between
// x and the deepest posterity of x
// 环根: 以该环为根的子树中,距离 x 最远的点与其距离的 最小值
// 非环根: 该节点所有子节点(显然都是环根)的 Deep 最大值 + 1
int spDeep[N],usDeep[N];
// spDeep[x] x 为环根,环边节点给他的贡献
// usDeep[x] x 为环根,桥边子树给他的贡献
int Lmax[N],Rmax[N];
void Get_Deep(int x){
int ID=id[x];
for (int i=0;i<cir[ID].size();i++){
int u=cir[ID][i],v;
Deep[u]=0;
for (int j=0;j<son[u].size();j++){
v=son[u][j];
Get_Deep(v);
Deep[u]=max(Deep[u],Deep[v]+1);
}
}
int n=cir[ID].size()-1;
Lmax[0]=Rmax[n+1]=0;
for (int i=1;i<=n;i++)
Lmax[i]=max(Lmax[i-1],Deep[cir[ID][i]]+i);
for (int i=n;i>=1;i--)
Rmax[i]=max(Rmax[i+1],Deep[cir[ID][i]]+(n-i+1));
int v=1e9;
for (int i=1;i<=n+1;i++)
v=min(v,max(Lmax[i-1],Rmax[i]));
Deep[x]=max(usDeep[x]=Deep[x],spDeep[x]=v);
}
int Far[N];
// The Minimum value of the distance between
// x and the farthest vetrex which isn't a posterity of x
// 环根: 由其父亲继承
// 非环根: 环内单调队列跑出来
int val[N*3];
int Lv[N*3],Rv[N*3];
struct Monotone_Queue{
int head,tail,q[N*3],d[N*3];
void clear(){
head=1,tail=0;
}
int front(){
return head<=tail?q[head]:-1e9;
}
void push(int v,int _d){
while (head<=tail&&q[tail]<=v)
tail--;
q[++tail]=v,d[tail]=_d;
}
void pop(int _d){
while (head<=tail&&d[head]<_d)
head++;
}
int Next_front(int _d){
if (head>tail)
return -1e9;
if (d[head]!=_d)
return q[head];
return head==tail?-1e9:q[head+1];
}
void print(){
printf("size=%d, sit=:",tail-head+1);
for (int i=head;i<=tail;i++)
printf("(%d,%d) ",q[i],d[i]);
puts("");
}
}QL,QR;
vector <int> LRmax[N];
void Solve(int x,int far){
int ID=id[x];
Far[x]=far;
int n=cir[ID].size();
for (int i=1;i<n;i++)
val[i]=Deep[cir[ID][i]];
val[0]=max(usDeep[x],Far[x]);
for (int i=n;i<n*3;i++)
val[i]=val[i%n];
for (int i=0;i<n*3;i++){
Lv[i]=val[i]+n*3-i;
Rv[i]=val[i]+i+1;
}
// for (int i=0;i<n*3;i++)
// printf("==>%d %d(%d) %d(%d)\n",val[i],Lv[i],n*3-i,Rv[i],i+1);
QL.clear(),QR.clear();
int Lp=2,Rp=n+1;
for (int i=Lp;i<=n;i++)
QL.push(Lv[i],i);
for (int i=n+1;i<n*2;i++){
int result=1e9;
int v1=n*3-i,v2=i+1;
// printf("i=%d,v1=%d,v2=%d\n",i,v1,v2);
while (Rp<i){
Lp++,QL.pop(Lp);
Rp++;
if (Rp!=i)
QR.push(Rv[Rp],Rp);
}
// printf("x=%d,i=%d,Lp=%d,Rp=%d,Q=..(L,R)\n",x,i,Lp,Rp);QL.print(),QR.print();
while (QL.Next_front(Lp)-v1>max(QR.front(),Rv[Rp+1])-v2){
result=min(result,QL.front()-v1);
Lp++,QL.pop(Lp);
Rp++,QR.push(Rv[Rp],Rp);
}
// printf("x=%d,i=%d,Lp=%d,Rp=%d,Q=..(L,R)\n",x,i,Lp,Rp);QL.print(),QR.print();
result=min(result,QL.front()-v1);
result=min(result,max(QR.front(),Rv[Rp+1])-v2);
QL.push(Lv[i],i);
QR.pop(i+2);
Far[cir[ID][i%n]]=result;
}
for (int i=0;i<n;i++){
int u=cir[ID][i];
int m=son[u].size();
Lmax[0]=Rmax[m+1]=0;
for (int j=1;j<=m;j++)
Lmax[j]=max(Lmax[j-1],Deep[son[u][j-1]]+1);
for (int j=m;j>=1;j--)
Rmax[j]=max(Rmax[j+1],Deep[son[u][j-1]]+1);
/* printf("u=%d,son=:",u);
for (int j=0;j<m;j++)
printf("%d ",son[u][j]);
for (int i=1;i<=m;i++)
printf ("(%d,%d) ",Lmax[i],Rmax[i]);puts("");*/
LRmax[u].clear();
for (int j=0;j<m;j++)
LRmax[u].push_back(max(Lmax[j],Rmax[j+2]));
for (int j=0;j<m;j++)
Solve(son[u][j],max(spDeep[u],max(Far[u],LRmax[u][j]))+1);
}
}
int main(){
scanf("%d%d",&n,&m);
g.clear();
for (int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b);
g.add(a,b);
g.add(b,a);
}
Tarjan_Prepare();
for (int i=1;i<=n;i++)
if (!vis[i])
Tarjan(i);
memset(vis,0,sizeof vis);
// for (int i=1;i<=n;i++)
// printf("%d: %d\n",i,id[i]);
build(1,0);
Get_Deep(1);
Solve(1,0);
/* for (int i=1;i<=tot;i++,puts(""))
for (int j=0;j<cir[i].size();j++)
printf("%d ",cir[i][j]);*/
/* for (int i=1;i<=n;i++){
printf("%d:",i);
for (int j=0;j<son[i].size();j++)
printf(" %d",son[i][j]);
puts("");
}*/
/* for (int i=1;i<=n;i++)
printf("%d: %d %d %d %d\n",i,Deep[i],spDeep[i],usDeep[i],Far[i]);*/
for (int i=1;i<=n;i++)
printf("%d ",max(Far[i],Deep[i]));
return 0;
}

  

Codeforces 980F Cactus to Tree 仙人掌 Tarjan 树形dp 单调队列的更多相关文章

  1. (noip模拟二十一)【BZOJ2500】幸福的道路-树形DP+单调队列

    Description 小T与小L终于决定走在一起,他们不想浪费在一起的每一分每一秒,所以他们决定每天早上一同晨练来享受在一起的时光. 他们画出了晨练路线的草图,眼尖的小T发现可以用树来描绘这个草图. ...

  2. Codeforces 791D Bear and Tree Jump(树形DP)

    题目链接 Bear and Tree Jumps 考虑树形DP.$c(i, j)$表示$i$最少加上多少后能被$j$整除. 在这里我们要算出所有$c(i, k)$的和. 其中$i$代表每个点对的距离, ...

  3. bzoj2500: 幸福的道路(树形dp+单调队列)

    好题.. 先找出每个节点的树上最长路 由树形DP完成 节点x,设其最长路的子节点为y 对于y的最长路,有向上和向下两种情况: down:y向子节点的最长路g[y][0] up:x的次长路的g[x][1 ...

  4. bzoj2500幸福的道路 树形dp+单调队列

    2500: 幸福的道路 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 434  Solved: 170[Submit][Status][Discuss ...

  5. HDU 4123 Bob’s Race 树形dp+单调队列

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4123 Time Limit: 5000/2000 MS (Java/Others) Memory L ...

  6. POJ - 3162 Walking Race 树形dp 单调队列

    POJ - 3162Walking Race 题目大意:有n个训练点,第i天就选择第i个训练点为起点跑到最远距离的点,然后连续的几天里如果最远距离的最大值和最小值的差距不超过m就可以作为观测区间,问这 ...

  7. hdu 4123 树形DP+单调队列

    http://acm.hust.edu.cn/vjudge/problem/25790 这题基本同poj 3162 要注意mx,mx2,vx,vx2每次都要初始化 #include <iostr ...

  8. [BZOJ 2500]幸福的道路 树形dp+单调队列+二分答案

    考试的时候打了个树链剖分,而且还审错题了,以为是每天找所有点的最长路,原来是每天起点的树上最长路径再搞事情.. 先用dfs处理出来每个节点以他为根的子树的最长链和次长链.(后面会用到) 然后用类似dp ...

  9. Codeforces 1077F2 Pictures with Kittens (hard version)(DP+单调队列优化)

    题目链接:Pictures with Kittens (hard version) 题意:给定n长度的数字序列ai,求从中选出x个满足任意k长度区间都至少有一个被选到的最大和. 题解:数据量5000, ...

随机推荐

  1. 【原创】运维基础之OpenResty(Nginx+Lua)+Kafka

    使用docker部署 1 下载 # wget https://github.com/doujiang24/lua-resty-kafka/archive/v0.06.tar.gz# tar xvf v ...

  2. appium+java (六) 手机chrome浏览器操作

    一.前言 早之前写过一段时间的appium for native app(即原生app脚本),但尴尬的是从未写过类似的文章,后期有时间我会陆续接着写,近一阶段有时间又把appium捡起来了,由于公司产 ...

  3. 业务侧有大量timeout请求超时日志

    故障背景:程序日志发现有程序请求数据库有大量的timeout请求故障时间:xxx~xxx 故障排查:排查应用服务器和数据库服务器网络和其它硬件监控没有断点,数据库监控请求数当时时间段几乎为0 故障分析 ...

  4. Oracle SQL高级编程——分析函数(窗口函数)全面讲解

    Oracle SQL高级编程--分析函数(窗口函数)全面讲解 注:本文来源于:<Oracle SQL高级编程--分析函数(窗口函数)全面讲解> 概述 分析函数是以一定的方法在一个与当前行相 ...

  5. 【Shared Server Mode】测试调整shared_servers参数对数据库的影响

    本文来源于:secooler  的 <[Shared Server Mode]测试调整shared_servers参数对数据库的影响> 关于Shared Server模式的配置方法请参见文 ...

  6. STL的注意事项

    template是一个泛化的:使用template时开始仅仅是声明,具体的例如:k<int> a;叫做实例化显式实例化:类似k<int>a:明确指出哪种类型:隐式实例化:类似k ...

  7. python网络爬虫笔记(二)

    一.函数调用的默认设置 1.def enroll(name,grnder,age=4,city='Shanghai'): print (''name:',name) print (''gender', ...

  8. bzoj 3529

    非常好的一道莫比乌斯反演题,对提升自己的能力有很大帮助. 首先我们分析一下题意:题意让我们求,其中 那么我们首先对后面的式子进行一下变形,变形过程详见https://blog.csdn.net/lle ...

  9. CF 1051F

    题意:给定一张n个点,m条边的无向联通图,其中m-n<=20,共q次询问,每次询问求给定两点u,v间的最短路长度 第一眼看见这题的时候,以为有什么神奇的全图最短路算法,满心欢喜的去翻了题解,发现 ...

  10. C++ friend友元函数和友元类(转)

    一个类中可以有 public.protected.private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员.现在,我们来介绍一种例外情 ...