zoukankan      html  css  js  c++  java
  • 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 imes ( f_x imes g_{ch} + f_{ch} imes g_x + f_x imes f_{ch}))

    (f_x += f_{ch})

    (g_x += g_{ch})

    额外乘的(f_x imes f_{ch})表示以当前这个圆点作为(t)的方案数

    对于一个方点(y),先合并它的孩子节点(ch)

    (ans += 2 imes ( f_y imes g_{ch} + f_{ch} imes g_y + f_y imes f_{ch} imes size_y))

    (f_y += f_{ch})

    (g_y += g_{ch})

    其中(size_y)表示(y)对应的点双联通分量的大小

    然后考虑当前这个点双联通分量的贡献:

    (ans += 2 imes (f_x imes P_{size_y - 1} ^ 2 + g_y imes (size_y - 1)))

    (f_y += size_y - 1)

    (g_y += P_{size_y - 1}^2)

    最后对于树根(root)并没有计算贡献,最后答案加上(2 imes 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;
    }
    
  • 相关阅读:
    ufw防火墙设置
    [从0到1搭建ABP微服务]
    .Net Core CI/CD环境搭建(正式版)
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
    [Abp vNext微服务实践]
  • 原文地址:https://www.cnblogs.com/Itst/p/10290597.html
Copyright © 2011-2022 走看看