[APIO2018] Duathlon 铁人两项 (圆方树赋权+拆分计算贡献)

题面

 

 

题解

比较容易想到建广义圆方树

关键是怎样给点赋权

如果我们枚举了路径的两个端点

那么有多少个中转点是合法的呢?

假设我们枚举到的是两个红色点,那么路径上的合法中转点(蓝色点)就是图中标记的部分

显然,这两点路径上的点双中的每个点都是可以取到的

我们可以把所有方点的权值赋为它所管辖的点双大小

这样,以两个红点为路径端点的合法中转点个数就是其路径的点权和

但是由于路径两端的端点是不能取到的,并且两个方点之间的公共圆点会被重复算

所以还应该将所有圆点的点权赋为-1

 

还有一个问题,我们不能直接枚举所有的点对来计算贡献

我们先把答案的式子列出来

\sum_{P}\sum_{i\in P} val_i(P是枚举的路径)

交换求和号

\sum_ival_i\sum_{i\in P}1

就变成了对经过i点的路径条数的计数问题

简单DP即可

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 200005
int n,m;
vector<int> G[N],gid[N];
int fir[N],to[2*N],nxt[2*N],cnt;
void adde(int a,int b)
{
	to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
}
int tot;
int dfn[N],low[N],stk[N],top,pbccnt,dc;
int pbcsiz[N],all;
void dfs(int u,int pp)
{
	if(u<=n)all++,pbcsiz[u]=-1;
	dfn[u]=low[u]=++dc;
	stk[top++]=u;
	for(int v,i=0;i<int(G[u].size());i++){
		if(gid[u][i]!=pp){
			v=G[u][i];
			if(!dfn[v]){
				dfs(v,gid[u][i]);
				low[u]=min(low[u],low[v]);
				if(low[v]>=dfn[u]){
					adde(u,++tot);pbcsiz[tot]=1;
					while(top>0){
						adde(tot,stk[--top]);
						pbcsiz[tot]++;
						if(stk[top]==v)break;
					}
				}
			}
			else low[u]=min(low[u],dfn[v]);
		}
	}
}
#define LL long long
LL f[N],ans;
void DP(int u,int ff)
{
	if(u<=n)f[u]=1;
	for(int v,p=fir[u];p;p=nxt[p]){
		if((v=to[p])!=ff){
			DP(v,u);
			ans+=1ll*f[u]*f[v]*pbcsiz[u];
			f[u]+=f[v];
		}
	}
	ans+=1ll*f[u]*(all-f[u])*pbcsiz[u];
}
int main()
{
	int i,u,v;
	n=gi();m=gi();
	for(i=1;i<=m;i++){
		u=gi();v=gi();
		G[u].push_back(v);gid[u].push_back(i);
		G[v].push_back(u);gid[v].push_back(i);
	}
	tot=n;
	for(i=1;i<=n;i++)
		if(!dfn[i]){
			all=0;dfs(i,0);
			DP(i,0);
		}
	printf("%lld\n",2ll*ans);
}

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章