洛谷 P4630 [APIO2018] Duathlon 铁人两项(圆方树)

题目链接
这题一直忘做了
考虑只是一棵树的情况,我们可以枚举中点,显然每个点作为中点产生的贡献就是经过它的所有路径的条数,这是一个很简单的树型DP
那么对于无向图而言,我们只要建出圆方树,就可以跑同样思路的树型DP,我们对于方点记录它所表示的点双的大小,因为两个点之间路径经过的任意一个点双上的任意一个点都可以做中点,所以方点的贡献要额外乘以点双的大小,但显然这样会有重复,得去重,考虑两个点之间的简单路径上有多少贡献是重复的,显然起点终点不行,两个方点中间的圆点算两遍,所以圆点权值设为-1就可以了

代码如下:

#include
using namespace std;

vector<int> g[100010],g2[400010];
int vis[400010],dfn[400010],low[400010],sta[400040],r[400040],size[400040],top,cnt,tot,n,m;
long long ans=0,sum;

void tarjan(int now)
{
	dfn[now]=low[now]=++cnt;
	sta[++top]=now;
	sum++;
	for(int i=0;i<g[now].size();i++)
	{
		if(!dfn[g[now][i]])
		{
			tarjan(g[now][i]);
			low[now]=min(low[now],low[g[now][i]]);
			if(dfn[now]<=low[g[now][i]])
			{
				tot++;
				r[tot]=1;
				g2[now].push_back(tot);
				g2[tot].push_back(now);
				int tmp=0;
				do
				{
					tmp=sta[top];
					g2[tmp].push_back(tot);
					g2[tot].push_back(tmp);
					r[tot]++;
					top--;
				}while(g[now][i]!=tmp);
			}
		}
		else
		{
			low[now]=min(low[now],dfn[g[now][i]]);
		}
	}
}

void dfs(int now,int fa)
{
	size[now]=now<=n;
	for(int i=0;i<g2[now].size();i++)
	{
		if(g2[now][i]==fa) continue;
		dfs(g2[now][i],now);
		ans+=2ll*r[now]*size[now]*size[g2[now][i]];
		size[now]+=size[g2[now][i]];
	}
	ans+=2ll*r[now]*size[now]*(sum-size[now]);
	// printf("%d %lld\n",now,ans);                                                                           
}

int main()
{
	scanf("%d%d",&n,&m);
	int from,to;
	tot=n;
	for(int i=1;i<=n;i++) r[i]=-1;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&from,&to);
		g[from].push_back(to);
		g[to].push_back(from);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		{
			sum=0;
			tarjan(i);
			dfs(i,0);
		}
	}
	printf("%lld\n",ans);
}

你可能感兴趣的:(洛谷,圆方树)