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;
}
posted @ 2019-01-19 10:10  cjoier_Itst  阅读(306)  评论(0编辑  收藏  举报