「BZOJ 1791」「IOI 2008」Island「基环树」
题意
求基环树森林所有基环树的直径之和
题解
考虑的一个基环树的直径,只会有两种情况,第一种是某个环上结点子树的直径,第二种是从两个环上结点子树内的最深路径,加上环上这两个结点之间的较长路径。
那就找环,然后环上每个结点做树形\(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「基环树」的更多相关文章
- Solution -「基环树」做题记录
写的大多只是思路,比较简单的细节和证明过程就不放了,有需者自取. 基环树简介 简单说一说基环树吧.由名字扩展可得这是一类以环为基础的树(当然显然它不是树. 通常的表现形式是一棵树再加一条非树边,把图画 ...
- 「BZOJ 3242」「NOI 2013」快餐店「基环树」
题意 基环树上找到一个点(可以在边上)使得它到树上最远点的距离最小,输出最小距离 题解 如果是一棵树,答案就是树的直径\(/2\) 如果是基环树,那么很好证明删去环上的某一条边是不影响答案的.于是断环 ...
- BZOJ 1791: [IOI2008]Island 岛屿 - 基环树
传送门 题解 题意 = 找出无向基环树森林的每颗基环树的直径. 我们首先需要找到每颗基环树的环, 但是因为是无向图,用tarjan找环, 加个手工栈, 我也是看了dalao的博客才知道tarjan找无 ...
- bzoj 2878: [Noi2012]迷失游乐园【树上期望dp+基环树】
参考:https://blog.csdn.net/shiyukun1998/article/details/44684947 先看对于树的情况 设d[u]为点u向儿子走的期望长度和,du[u]为u点的 ...
- [IOI 2008] Island
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1791 [算法] 不难看出,要求的是这个基环树森林中每棵基环树的直径之和 [代码] # ...
- 「BZOJ 4228」Tibbar的后花园
「BZOJ 4228」Tibbar的后花园 Please contact lydsy2012@163.com! 警告 解题思路 可以证明最终的图中所有点的度数都 \(< 3\) ,且不存在环长是 ...
- 「BZOJ 3645」小朋友与二叉树
「BZOJ 3645」小朋友与二叉树 解题思路 令 \(G(x)\) 为关于可选大小集合的生成函数,即 \[ G(x)=\sum[i\in c ] x^i \] 令 \(F(x)\) 第 \(n\) ...
- 「BZOJ 4502」串
「BZOJ 4502」串 题目描述 兔子们在玩字符串的游戏.首先,它们拿出了一个字符串集合 \(S\),然后它们定义一个字符串为"好"的,当且仅当它可以被分成非空的两段,其中每一段 ...
- 「BZOJ 4289」 PA2012 Tax
「BZOJ 4289」 PA2012 Tax 题目描述 给出一个 \(N\) 个点 \(M\) 条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点 \(1\) 到点 \( ...
随机推荐
- laravel count的使用
rt 学习源头: https://blog.csdn.net/chajinglong/article/details/51954010 四.聚合 查询构建器还提供了各种聚合方法,如统计,马克斯,min ...
- QtCreator开启-O编译优化的方式
首先,编译优化必须是在Release模式下进行,保证程序没有任何bug的条件下进行执行.编译优化能极大提升程序的运行效率,级别越高速度越快,但是对代码健壮性要求也越高! 选择编译release模式,在 ...
- java代码----方法类练习中~~~未领会
总结:今天我很失败,问了老师继承的问题还是没弄懂.因为,技术宅的能力,我好怕啊.太强了. package com.da.ima2; public class yut {// 使用substring方法 ...
- 四川第七届 I Travel(bfs)
Travel The country frog lives in has nn towns which are conveniently numbered by 1,2,…,n1,2,…,n. Amo ...
- AngularJS:HTML DOM
ylbtech-AngularJS:HTML DOM 1.返回顶部 1. AngularJS HTML DOM AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令. ng-d ...
- AngularJS:Bootstrap
ylbtech-AngularJS:Bootstrap 1.返回顶部 1. AngularJS Bootstrap AngularJS 的首选样式表是 Twitter Bootstrap, Twitt ...
- RVO和NRVO
返回值优化(Return Value Optimization,简称RVO),是这么一种优化机制:当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(C ...
- 生产环境该如何选择lvs的工作模式,和哪一种算法
lvs的工作模式有这几种: 1.RR : 轮叫算法,平均分配,你一个,我一个: 2.WRR :加权轮叫算法,谁的处理能力强,谁的权重就高: 3.LC :最少链接算法,谁的连接数最少,谁就处理更多的链接 ...
- java反射专题三
一丶调用运行时类中指定的属性 Class clazz = Person.class; //1.获取指定的属性 Field name = clazz.getField("name") ...
- 关于RandomizedSearchCV 和GridSearchCV(区别:参数个数的选择方式)
# -*- coding: utf-8 -*- """ Created on Tue Aug 09 22:38:37 2016 @author: Administrato ...