Luogu4630 APIO2018 Duathlon 圆方树、树形DP
要求的是一条按顺序经过\(s,t,c\)三个点的简单路径。简单路径的计数问题不难想到点双联通分量,进而使用圆方树进行求解。
首先将原图缩点,对于一个大小为\(size\)的点双联通分量内,在这个分量内部任意选择\(s,t,c\)都是可行的,可以贡献\(P_{size}^3\)的答案。
接下来就需要计算跨越点双联通分量的\(s,t,c\)了。这个可以在圆方树上进行树形DP统计答案。
但是考虑到割点可能会被统计多次,我们令圆方树中所有的方点都不包含它的父亲,这样对于一个圆点就只会被计入一次答案了。当然了,为了不重不漏地统计每一种可行方案,上面的一系列的贡献都会有一些变化。
具体是这样子的:
首先缩点,对于一个点双联通分量,只贡献\(P_{size-1}^3 + P_{size-1}^2\)的答案,因为从去掉方点父亲的点双联通分量中拿出两个点作为\(s,t\),将方点父亲作为\(c\)的情况可以在树形DP的过程中统计到
接着在圆方树上树形DP。设\(f_x\)表示\(x\)及其子树中取一个点作为\(s\)的方案数(其实就是子树中的圆点个数),\(g_x\)表示\(x\)及其子树中取两个点作为\(s,t\)的方案数
注意到取一个点作为\(s\)与取一个点作为\(c\)是等价的,所以DP贡献的答案中需要乘上2
在合并过程中,对于一个圆点,它对\(f\)和\(g\)的贡献放在它的父亲方点上进行。
对于一个圆点\(x\),它的孩子方点\(ch\)到它的转移为:
\(ans += 2 \times ( f_x \times g_{ch} + f_{ch} \times g_x + f_x \times f_{ch})\)
\(f_x += f_{ch}\)
\(g_x += g_{ch}\)
额外乘的\(f_x \times f_{ch}\)表示以当前这个圆点作为\(t\)的方案数
对于一个方点\(y\),先合并它的孩子节点\(ch\):
\(ans += 2 \times ( f_y \times g_{ch} + f_{ch} \times g_y + f_y \times f_{ch} \times size_y)\)
\(f_y += f_{ch}\)
\(g_y += g_{ch}\)
其中\(size_y\)表示\(y\)对应的点双联通分量的大小
然后考虑当前这个点双联通分量的贡献:
\(ans += 2 \times (f_x \times P_{size_y - 1} ^ 2 + g_y \times (size_y - 1))\)
\(f_y += size_y - 1\)
\(g_y += P_{size_y - 1}^2\)
最后对于树根\(root\)并没有计算贡献,最后答案加上\(2 \times g_{root}\)即可。
注意图可能不连通
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 2e5 + 7;
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , N , M , cnt , cntEd;
int dfn[MAXN] , low[MAXN] , st[MAXN] , top , ts;
long long ans , dp1[MAXN] , dp2[MAXN];
vector < int > ch[MAXN];
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
void pop(int t , int bot){
++cnt;
ch[t].push_back(cnt);
long long c = 0;
do{
++c;
ch[cnt].push_back(st[top]);
}while(st[top--] != bot);
ans += c * (c - 1) / 2 + c * (c - 1) * (c - 2) / 2;
}
void tarjan(int x , int p){
dfn[x] = low[x] = ++ts;
st[++top] = x;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p)
if(!dfn[Ed[i].end]){
tarjan(Ed[i].end , x);
low[x] = min(low[x] , low[Ed[i].end]);
if(low[Ed[i].end] >= dfn[x])
pop(x , Ed[i].end);
}
else
low[x] = min(low[x] , dfn[Ed[i].end]);
}
void dfs(int x){
for(int i = 0 ; i < ch[x].size() ; ++i){
dfs(ch[x][i]);
ans += dp1[x] * dp2[ch[x][i]] + dp1[ch[x][i]] * dp2[x] + dp1[x] * dp1[ch[x][i]] * (x <= N ? 1 : ch[x].size() + 1);
dp2[x] += dp2[ch[x][i]];
dp1[x] += dp1[ch[x][i]];
}
if(x > N){
ans += dp1[x] * ch[x].size() * (ch[x].size() - 1) + dp2[x] * ch[x].size();
dp2[x] = dp2[x] + dp1[x] * ch[x].size() + 1ll * ch[x].size() * (ch[x].size() - 1);
dp1[x] += ch[x].size();
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
N = cnt = read();
M = read();
for(int i = 1 ; i <= M ; ++i){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
for(int i = 1 ; i <= N ; ++i)
if(!dfn[i]){
top = 0;
tarjan(i , 0);
dfs(i);
ans += dp2[i];
}
cout << ans * 2;
return 0;
}
Luogu4630 APIO2018 Duathlon 圆方树、树形DP的更多相关文章
- [APIO2018]铁人两项——圆方树+树形DP
题目链接: [APIO2018]铁人两项 对于点双连通分量有一个性质:在同一个点双里的三个点$a,b,c$,一定存在一条从$a$到$c$的路径经过$b$且经过的点只被经过一次. 那么我们建出原图的圆方 ...
- loj2587 「APIO2018」铁人两项[圆方树+树形DP]
主要卡在一个结论上..关于点双有一个常用结论,也经常作为在圆方树/简单路径上的良好性质,对于任意点双内互不相同的三点$s,c,t$,都存在简单路径$s\to c\to t$,证明不会.可以参见clz博 ...
- 2019.03.29 bzoj5463: [APIO2018] 铁人两项(圆方树+树形dp)
传送门 题意简述:给你一张无向图,问你满足存在从a−>b−>ca->b->ca−>b−>c且不经过重复节点的路径的有序点对(a,b,c)(a,b,c)(a,b,c) ...
- [APIO2018] Duathlon 铁人两项 圆方树,DP
[APIO2018] Duathlon 铁人两项 LG传送门 圆方树+简单DP. 不会圆方树的话可以看看我的另一篇文章. 考虑暴力怎么写,枚举两个点,答案加上两个点之间的点的个数. 看到题面中的一句话 ...
- LOJ.2587.[APIO2018]铁人两项Duathlon(圆方树)
题目链接 LOJ 洛谷P4630 先对这张图建圆方树. 对于S->T这条(些)路径,其对答案的贡献为可能经过的所有点数,那么我们把方点权值设为联通分量的大小,可以直接去求树上路径权值和. 因为两 ...
- 【Luogu4630】【APIO2018】 Duathlon 铁人两项 (圆方树)
Description 给你一张\(~n~\)个点\(~m~\)条边的无向图,求有多少个三元组\(~(x, ~y, ~z)~\)满足存在一条从\(~x~\)到\(~z~\)并且经过\(~y~\)的 ...
- [BZOJ5463][APIO2018]铁人两项(圆方树DP)
题意:给出一张图,求满足存在一条从u到v的长度大于3的简单路径的有序点对(u,v)个数. 做了上一题[HDU5739]Fantasia(点双连通分量+DP),这个题就是一个NOIP题了. 一开始考虑了 ...
- 【NOI P模拟赛】仙人掌(圆方树,树形DP)
题面 n n n 个点, m m m 条边. 1 ≤ n ≤ 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 1\leq n\leq 10^5,n-1\leq m\leq 2\times1 ...
- 洛谷P4630 [APIO2018] Duathlon 铁人两项 【圆方树】
题目链接 洛谷P4630 题解 看了一下部分分,觉得树的部分很可做,就相当于求一个点对路径长之和的东西,考虑一下能不能转化到一般图来? 一般图要转为树,就使用圆方树呗 思考一下发现,两点之间经过的点双 ...
随机推荐
- TurboLinux系统管理习题一
TurboLinux系统管理习题一 1. 使用vi编辑文本只读时,强制存盘并退出的命令是?(单选题)A :w! B :q! C :wq! D :e!答案 ...
- linux之sort和uniq
uniq uniq命令: uniq不加参数,只对相邻的相同行内容去重. 例子如下: [root@localhost ~]# pwd /root [root@localhost ~]# cat oldb ...
- .Net Core(二)EFCore
EFCore与之前的EF基本类似,区别在于配置的时候有一些差异:也取消了DB First和Model First,仅保留广泛使用的Code First模式:也不再支持LazyLoad.这里就感受一下 ...
- Kotlin入门(4)声明与操作数组
上一篇文章介绍了基本变量类型在Kotlin中的用法,不过这只针对单个变量,如果要求把一组相同类型的变量排列起来,形成一个变量数组,那又该如何声明和操作呢? 在Java中声明数组,跟在C语言中声明是一样 ...
- (网页)java中Collections.sort排序详解(转)
转自CSDN: Comparator是个接口,可重写compare()及equals()这两个方法,用于比价功能:如果是null的话,就是使用元素的默认顺序,如a,b,c,d,e,f,g,就是a,b, ...
- Spark程序数据结构优化
场景: 1.scala中的对象:对象头是16个字节(包含指向对象的指针等源数据信息),如果对象中只有一个int的属性,则会占用20个字节,也就是说对象的源数据占用了大部分的空间,所以在封装数据的时候尽 ...
- 洗礼灵魂,修炼python(43)--巩固篇—经典类/新式类
经典类 1.什么是经典类 就是在使用class关键词时,括号内不添加object类的就叫经典类,前面的博文里是绝对解析过的,所以你应该知道,经典类现在已经仅存在于python2了,因为python3不 ...
- php二维数组去重
php二维数组去重 前言:php一维数组去重很简单,直接array_unique($arr)即可,但是二维数组去重就得自己去写了 二维数组去重方法: /* * 二维数组去重 * 注意:二维数组中的元素 ...
- Linux 小知识翻译 - 「路径设置」
这次聊聊路径的使用,这里的路径是「命令搜索路径」的简称. 在Linux上执行命令的时候,本来是需要命令的所在位置的绝对路径的,就像「/usr/bin/passwd」这样. 但是,对于经常使用的命令,如 ...
- C#异步编程の----Threadpool( 线程池)
简介: 一个托管线程的创建需要数千个CPU周期,并且当发生线程切换时也会带来明显的开销.考虑线程的重用,避免不断重复创建新的线程是提高系统效率的一种方式. 线程池是一种提供效率的方式,它创建好一些线程 ...