题意

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

题解

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

那就找环,然后环上每个结点做树形\(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. Spring MVC 项目示例

    Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架.Spring的web框架围绕DispatcherServlet设计, 作用是将请求分发到不同 ...

  2. Oracle 多表查询(2)

    四.统计函数及分组查询 1.统计函数 在之前学习过一个COUNT()函数,此函数的功能可以统计出表中的数据量,实际上这个就是一个统计函数,而常用的统计函数有如下几个: COUNT():查询表中的数据记 ...

  3. django的settings.py设置session

    ############ # SESSIONS # ############ SESSION_CACHE_ALIAS = 'default' # Cache to store session data ...

  4. 注解:@interface 自定义注解的语法

      自定义注解: 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节.在定义注解时,不能继承其他的注解或接口 ...

  5. Vue指令学习

    # new Vue({ vue所有的数据都是放到data里面的 # data:{ vue对象的数据 # a:1,对象 # b:[] , # } # methods:{vue对象的方法 # dosomt ...

  6. leetcode643

    double findMaxAverage(vector<int>& nums, int k) { double max = INT_MIN; int len = nums.siz ...

  7. 删除CentOS系统自带的jdk

    转自:https://www.cnblogs.com/linjiqin/archive/2013/03/23/2977377.html 在安装CentOS6.4时,系统会自动安装jdk,先把它下载掉, ...

  8. 安卓如何将TXT文件写到特定路径

    其实就一个方法,就不贴所有代码了. /** * 写入文件方法 * @param content */ public static void write(String content) { try { ...

  9. eclipse中创建maven web项目

    本文主要说明将maven web项目转成eclipse支持的web项目. 创建一个maven项目设置打包类型为war则其为web项目 结构如下 将mavenweb项目转成eclipse识别的web项目 ...

  10. latex如何进行多行注释

    单行注释:当LATEX 处理一个源文件时,如果遇到一个百分号%,LATEX 将忽略% 后的该行内容,换11行符以及下一行前的空白字符.多行注释:\begin{comment}rather stupid ...