原文链接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. SQL Server异常汇总

    1.特定用户名无法访问数据库 例如需要使用sa用户名访问School数据库失败,提示如下: (你要设置的)数据库--属性--文件--所用者设为Sa,回到用户映射查看,已勾选上. 还有一些情况 1)将登 ...

  2. python深浅拷贝与赋值

    初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一——python的变量及其存储 在详细的了解python中赋 ...

  3. 如何获取STM32 MCU的唯一ID

    前段时间由于应用需要对产品授权进行限制,所以研究了一下有关STM32 MCU的唯一ID的资料,并最终利用它实现了我们的目标. 1.基本描述 在STM32的全系列MCU中均有一个96位的唯一设备标识符. ...

  4. InstallUtil操作WindowsService

    要安装windows service 首先要找到 InstallUtil.exe,InstallUtil.exe位置在 C:\Windows\Microsoft.NET\Framework\v4.0. ...

  5. java-pdf转word

    注:原文来至 < java-pdf转word   > 一: java Pdf 文字 转 Word 废话不说,直接上图 很简单的用法:1.new个PDFBox对象2.调用pdfToDoc() ...

  6. TabLayout和ViewPager

    这里就说下tablayout+viewpager的实现方式:tablayout是android5.0推出来的一个MaterialDesign风格的控件,是专门用来实现tab栏效果的:功能强大,使用方便 ...

  7. 理解call及apply

    转载自:http://www.zhihu.com/question/20289071 //call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改 ...

  8. Mycat实现mysql主从复制(读写分离)

    数据库性能瓶颈主要原因: 随着用户数的增多,带来的是数据库连接的大幅度增长 随着业务体量的增长,表数据量(空间存储的问题)的大幅增长,其中涉及到索引的优化,mysql默认的索引是硬盘级别的,BTREE ...

  9. 添加按钮 table增加一行 删减按钮 table去掉一行

    需求描述:做的一个AA新增功能,同时可以为这个即将新增的AA添加内容,而且AA的内容默认展示一行列表,点击添加按钮后出现下一行列表 解决思路:页面首先展示一个表头和列表的一行,作为默认展示的一行列表, ...

  10. Metasploit

    1.启动Metasploit 声明:本次渗透测试的主机是我自己在自己的攻击主机上搭建的另一个操作系统,为了真实性设置了常见的IP地址,如有重合但绝对不是任何实体公司或者单位的IP地址. 所以不承担任何 ...