题意

求基环树森林所有基环树的直径之和

题解

考虑的一个基环树的直径,只会有两种情况,第一种是某个环上结点子树的直径,第二种是从两个环上结点子树内的最深路径,加上环上这两个结点之间的较长路径。

那就找环,然后环上每个结点做树形\(dp\)。然后把环断成长度为\(2n\)的链,记录环上的前缀和\(sum\)。假设结点\(u\)子树内最深路径为\(dep[u]\),那么就是求\(max(sum[i] - sum[j] + dep[i] + dep[j]),j < i\)。这个就转换成\(max(sum[i] + dep[i] + dep[j] - sum[j])\),用单调队列维护最大的\(dep[j] - sum[j]\)就行,类似滑动窗口。

#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std; typedef long long ll; const int N = 1e6 + 5; struct Edge {
int v, nxt, w;
} e[N << 1];
int hd[N], edge_num; void link(int u, int v, int w) {
e[edge_num] = (Edge) {v, hd[u], w};
hd[u] = edge_num ++;
} int n, loop[N], cnt, idx, dfn[N];
int fa[N], faw[N], loopw[N];
ll dep1[N], dep2[N], tree_d;
ll sum[N << 1], dep[N << 1];
bool onloop[N];
deque<int> q; void dfs(int u, int cur = -1) {
dfn[u] = ++ idx;
for(int i = hd[u]; ~ i; i = e[i].nxt) {
if(cur == i) continue ;
int v = e[i].v;
if(dfn[v]) {
if(dfn[v] > dfn[u] || cnt) continue ;
cnt ++;
loop[cnt] = u;
loopw[cnt] = e[i].w;
for(int j = u; j != v; j = fa[j]) {
cnt ++;
loop[cnt] = fa[j];
loopw[cnt] = faw[j];
}
} else fa[v] = u, faw[v] = e[i].w, dfs(v, i ^ 1);
}
} void dp(int u, int f = 0) {
dep1[u] = dep2[u] = 0;
for(int i = hd[u]; ~ i; i = e[i].nxt) {
int v = e[i].v;
if(v == f || onloop[v]) continue ;
dp(v, u);
ll dis = e[i].w + dep1[v];
if(dis > dep1[u]) {
dep2[u] = dep1[u];
dep1[u] = dis;
} else if(dis > dep2[u]) {
dep2[u] = dis;
}
}
tree_d = max(tree_d, dep1[u] + dep2[u]);
} int main() {
scanf("%d", &n);
fill(hd + 1, hd + n + 1, -1);
for(int i = 1, v, w; i <= n; i ++) {
scanf("%d%d", &v, &w);
link(v, i, w);
link(i, v, w);
}
ll ans = 0, d;
for(int i = 1; i <= n; i ++)
if(!dfn[i]) {
cnt = 0; dfs(i);
for(int j = 1; j <= cnt; j ++)
onloop[loop[j]] = 1;
d = 0;
for(int j = 1; j <= cnt; j ++) {
tree_d = 0;
dp(loop[j]);
dep[j] = dep[j + cnt] = dep1[loop[j]];
d = max(d, tree_d);
}
for(int j = 1; j <= cnt << 1; j ++)
sum[j] = sum[j - 1] + loopw[j <= cnt ? j : j - cnt];
q.clear();
for(int j = 1; j <= cnt << 1; j ++) {
//sum_j + dep_j + dep_i - sum_i
for(; !q.empty() && q.front() + cnt - 1 < j; q.pop_front()) ;
ll c = dep[j] - sum[j];
if(!q.empty()) d = max(d, sum[j] + dep[j] + dep[q.front()] - sum[q.front()]);
for(; !q.empty() && dep[q.back()] - sum[q.back()] <= c; q.pop_back()) ;
q.push_back(j);
}
ans += d;
}
printf("%lld\n", ans);
return 0;
}

什么,\(\text{BZOJ}\)上\(10^6\)会爆栈?那只能手工栈了。。

什么,手工栈会\(\text{MLE}\)?卡卡卡

然后代码就十分难看了(

\(block\)函数是把\(dfs\)到的结点的\(head\)复制一遍。

#include <algorithm>
#include <bitset>
#include <cstdio>
using namespace std; typedef long long ll; const int N = 1e6 + 5; struct Edge {
int v, nxt, w;
} e[N << 1];
int hd[N], h[N], edge_num; void link(int u, int v, int w) {
e[edge_num] = (Edge) {v, hd[u], w};
hd[u] = edge_num ++;
} int n, loop[N], loopw[N], cnt, idx, dfn[N];
int fa[N], faw[N], q[N << 1], node[N], qn, ql, qr;
ll tree_d, sum[N << 1], dep[N];
bitset<N> onloop, vis; void block(int s) {
ql = qr = qn = 0;
vis[s] = 1; q[qr ++] = s; node[qn ++] = s;
int u, i;
while(ql < qr) {
u = q[ql ++];
for(i = hd[u]; ~ i; i = e[i].nxt)
if(!vis[e[i].v]) {
vis[e[i].v] = 1;
q[qr ++] = e[i].v;
node[qn ++] = e[i].v;
}
}
for(i = 0; i < qn; i ++) vis[node[i]] = 0;
} int st[N][2], top;
void dfs(int s) {
block(s);
int i, j, u, v, cur;
for(i = 0; i < qn; i ++) h[node[i]] = hd[node[i]];
top = 0; st[top][0] = s; st[top][1] = -1; top ++;
while(top >= 1) {
u = st[top - 1][0]; cur = st[top - 1][1];
if(!dfn[u]) dfn[u] = ++ idx;
if(h[u] == -1) { top --; continue ; }
for(int &i = h[u]; ~ i; i = e[i].nxt) {
if(i == cur) continue ;
v = e[i].v;
if(!dfn[v]) {
fa[v] = u; faw[v] = e[i].w;
st[top][0] = v; st[top][1] = i ^ 1; top ++;
i = e[i].nxt;
break ;
} else if(dfn[v] < dfn[u]) {
cnt ++;
loop[cnt] = u;
loopw[cnt] = e[i].w;
onloop[u] = 1;
for(j = u; j != v; j = fa[j]) {
cnt ++;
loop[cnt] = fa[j];
loopw[cnt] = faw[j];
onloop[fa[j]] = 1;
}
}
}
}
} void block2(int s) {
ql = qr = qn = 0;
vis[s] = 1; q[qr ++] = s; node[qn ++] = s;
int u, i;
while(ql < qr) {
u = q[ql ++];
for(i = hd[u]; ~ i; i = e[i].nxt)
if(!vis[e[i].v] && !onloop[e[i].v]) {
vis[e[i].v] = 1;
q[qr ++] = e[i].v;
node[qn ++] = e[i].v;
}
}
for(i = 0; i < qn; i ++) vis[node[i]] = 0;
} ll dep1[N], dep2[N];
ll dp(int s) {
block2(s);
int i, j, u, v, cur; ll dis, dep2;
for(i = 0; i < qn; i ++) h[node[i]] = hd[node[i]];
top = 0; st[top][0] = s; st[top][1] = -1; top ++;
while(top >= 1) {
u = st[top - 1][0]; cur = st[top - 1][1];
if(h[u] == -1) {
dep2 = dep1[u] = 0;
for(int i = hd[u]; ~ i; i = e[i].nxt) {
if((v = e[i].v) == cur || onloop[v]) continue ;
dis = e[i].w + dep1[v];
if(dis > dep1[u]) {
dep2 = dep1[u];
dep1[u] = dis;
} else if(dis > dep2) {
dep2 = dis;
}
tree_d = max(tree_d, dep1[u] + dep2);
}
top --;
continue ;
}
for(int &i = h[u]; ~ i; i = e[i].nxt) {
if((v = e[i].v) == cur || onloop[v]) continue ;
st[top][0] = v; st[top][1] = u; top ++;
}
}
return dep1[s];
} int main() {
scanf("%d", &n);
fill(hd + 1, hd + n + 1, -1);
int i, j, v, w;
for(i = 1; i <= n; i ++) {
scanf("%d%d", &v, &w);
link(v, i, w); link(i, v, w);
}
ll ans = 0, d, c;
for(i = 1; i <= n; i ++)
if(!dfn[i]) {
cnt = d = 0; dfs(i);
for(j = 1; j <= cnt; j ++) {
tree_d = 0;
dep[j] = dp(loop[j]);
d = max(d, tree_d);
}
for(j = 1; j <= cnt << 1; j ++)
sum[j] = sum[j - 1] + loopw[j <= cnt ? j : j - cnt];
ql = qr = 0;
#define _dep(u) (u <= cnt ? dep[u] : dep[u - cnt])
for(j = 1; j <= cnt << 1; j ++) {
//sum_j + dep_j + dep_i - sum_i
for(; ql < qr && q[ql] + cnt - 1 < j; ql ++) ;
c = _dep(j) - sum[j];
if(ql < qr) d = max(d, sum[j] + _dep(j) + _dep(q[ql]) - sum[q[ql]]);
for(; ql < qr && _dep(q[qr - 1]) - sum[q[qr - 1]] <= c; qr --) ;
q[qr ++] = j;
}
#undef _dep
ans += d;
}
printf("%lld\n", ans);
return 0;
}

「BZOJ 1791」「IOI 2008」Island「基环树」的更多相关文章

  1. Solution -「基环树」做题记录

    写的大多只是思路,比较简单的细节和证明过程就不放了,有需者自取. 基环树简介 简单说一说基环树吧.由名字扩展可得这是一类以环为基础的树(当然显然它不是树. 通常的表现形式是一棵树再加一条非树边,把图画 ...

  2. 「BZOJ 3242」「NOI 2013」快餐店「基环树」

    题意 基环树上找到一个点(可以在边上)使得它到树上最远点的距离最小,输出最小距离 题解 如果是一棵树,答案就是树的直径\(/2\) 如果是基环树,那么很好证明删去环上的某一条边是不影响答案的.于是断环 ...

  3. BZOJ 1791: [IOI2008]Island 岛屿 - 基环树

    传送门 题解 题意 = 找出无向基环树森林的每颗基环树的直径. 我们首先需要找到每颗基环树的环, 但是因为是无向图,用tarjan找环, 加个手工栈, 我也是看了dalao的博客才知道tarjan找无 ...

  4. bzoj 2878: [Noi2012]迷失游乐园【树上期望dp+基环树】

    参考:https://blog.csdn.net/shiyukun1998/article/details/44684947 先看对于树的情况 设d[u]为点u向儿子走的期望长度和,du[u]为u点的 ...

  5. [IOI 2008] Island

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1791 [算法] 不难看出,要求的是这个基环树森林中每棵基环树的直径之和 [代码] # ...

  6. 「BZOJ 4228」Tibbar的后花园

    「BZOJ 4228」Tibbar的后花园 Please contact lydsy2012@163.com! 警告 解题思路 可以证明最终的图中所有点的度数都 \(< 3\) ,且不存在环长是 ...

  7. 「BZOJ 3645」小朋友与二叉树

    「BZOJ 3645」小朋友与二叉树 解题思路 令 \(G(x)\) 为关于可选大小集合的生成函数,即 \[ G(x)=\sum[i\in c ] x^i \] 令 \(F(x)\) 第 \(n\) ...

  8. 「BZOJ 4502」串

    「BZOJ 4502」串 题目描述 兔子们在玩字符串的游戏.首先,它们拿出了一个字符串集合 \(S\),然后它们定义一个字符串为"好"的,当且仅当它可以被分成非空的两段,其中每一段 ...

  9. 「BZOJ 4289」 PA2012 Tax

    「BZOJ 4289」 PA2012 Tax 题目描述 给出一个 \(N\) 个点 \(M\) 条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点 \(1\) 到点 \( ...

随机推荐

  1. 5 Things You Should Know About the New Maxwell GPU Architecture

    The introduction this week of NVIDIA’s first-generation “Maxwell” GPUs is a very exciting moment for ...

  2. 自定义mysql函数时报错,[Err] 1418 - This function has none of DETERMINISTIC......

    今天在我执行自定义mysql函数的SQL时发生了错误,SQL如下: /** 自定义mysql函数 getChildList */delimiter //CREATE FUNCTION `pengwif ...

  3. Linux python安装

    Linux 安装 Python3.5   1. 准备 操作系统:Red Hat Enterprise Linux Server release 6.5 (Santiago) Python 安装包:Py ...

  4. MongoTemplate聚合操作

    Aggregation简单来说,就是提供数据统计.分析.分类的方法,这与mapreduce有异曲同工之处,只不过mongodb做了更多的封装与优化,让数据操作更加便捷和易用.Aggregation操作 ...

  5. python风格指南

    还在让别人一眼就看出你是一只野生程序猿嘛,快来看看谷歌的python风格指南提升逼格吧! http://zh-google-styleguide.readthedocs.io/en/latest/go ...

  6. adb基本命令总结(Android Debug Bridge)

    adb 是PC和设备连接的桥梁,可以通过adb对devices进行相关操作 adb devices           列出你的devices adb kill-server         杀掉ad ...

  7. 2011-03-17免Oracle客户端连远程Oracle的方法

    1.http://www.oracle.com/technetwork/topics/winsoft-085727.html上下载对应版本的instanctclinet zip包 34M 解压后92M ...

  8. MySQL学习笔记之一---字符编码和字符集

    前言: 一般来说,出现中文乱码,都是客户端和服务端字符集不匹配导致的原因. (默认未指定字符集创建的数据库表,都是latinl字符集, 强烈建议使用utf8字符集)   保证不出现乱码的思想:保证客户 ...

  9. windows下查看端口占用(砖)

    第一步,执行命令:netstat -ano -a 显示所有连接和监听端口 -n 以数字形式显示地址和端口号. 此选项一般与 -a选项组合使用 -o 显示与每个连接相关的所属进程 ID. Active ...

  10. 记录一次手机联系人整理(XML文件格式处理)

    场景:1.IOS手机和Android手机联系人同步时有部分重复联系人. 2.很早以前的HTC手机导出的联系人中备注信息有大量乱码,且很多联系人生日被设置为1970-01-01,导致生日提醒软件产生骚扰 ...