[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);
}

 

 

 

 

 

 

 

 

 

 

 

 

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