「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\) 到点 \( ...
随机推荐
- filter中获取spring bean
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import ja ...
- mybatis 学习三 关键文件解析
1: mybatis-config.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYP ...
- 2015.1.8 Left join 左连接
格式 select f1.a, f2.b form f1 left jion f2 on .... 注意:左边的查询部分只能有select和from,不能出现where order by等.若有必须在 ...
- redis使用测试
import redis conn=redis.Redis(host='127.0.0.1',port=6379) conn.set('nn','morgana',10) #过期时间10s v=con ...
- Oracle中REGEXP_SUBSTR函数(字符串转多行)
Oracle中REGEXP_SUBSTR函数 Oracle中REGEXP_SUBSTR函数的使用说明: 题目如下: 在oracle中,使用一条语句实现将'17,20,23'拆分成'17','20',' ...
- Linux 压缩文件 和解压文件
.zip 解压:unzip FileName.zip 压缩:zip FileName.zip DirName .rar 解压:rar -x FileName.zip 压缩:rar -a FileNam ...
- mysql工具Navicat批量执行SQL语句
例如:我现在要同时执行这么多语句 update community set xqmc=replace(xqmc,' ',''); update community set xqbm=replace(x ...
- 一些API的用法
//1.init初始化 NSString * str1 = [[NSString alloc] init]; NSLog(@"str1 = %@",str1); //2.initW ...
- Composite模式 组合模式
Android的ViewGroup 和 View 的关系,即是采用组合模式 1. 概述 在数据结构里面,树结构是很重要,我们可以把树的结构应用到设计模式里面. 例子1:就是多级树形菜单. 例子2:文件 ...
- Enumeration与Iterator的对比
Enumeration与Iterator的对比 Enumeration 接口 Iterator 接口 参数的含义 枚举类型 迭代器元素类型 所在包 java.util 父类 无 子类 StringTo ...