1. 程式人生 > >[bzoj5463][loj#2587][圓方樹][樹形dp]鐵人兩項

[bzoj5463][loj#2587][圓方樹][樹形dp]鐵人兩項

Description

位元鎮的路網由 m 條雙向道路連線的 n 個交叉路口組成。
最近,位元鎮獲得了一場鐵人兩項錦標賽的主辦權。這場比賽共有兩段賽程:選手先完成一段長跑賽程,然後騎自行車完成第二段賽程。
比賽的路線要按照如下方法規劃: 1、先選擇三個兩兩互不相同的路口 s, c 和 f
,分別作為比賽的起點、切換點(運動員在長跑到達這個點後,騎自行車前往終點)、終點。 2、選擇一條從 s 出發,經過 c 最終到達 f
的路徑。考慮到安全因素,選擇的路徑經過同一個點至多一次。 在規劃路徑之前,鎮長想請你幫忙計算,總共有多少種不同的選取 s, c 和 f
的方案,使得在第 2 步中至少能設計出一條滿足要求的路徑。

Input

第一行包含兩個整數 n 和 m ,分別表示交叉路口和雙向道路的數量。 接下來 m 行,每行兩個整數 v_i, u_i
。表示存在一條雙向道路連線交叉路口 v_i, u_i (1 <= v_i, u_i <= n, v_i != u_i)。
保證任意兩個交叉路口之間,至多被一條雙向道路直接連線。 n<=1e5, m<=2e5

Output

輸出一行,包含一個整數,表示能滿足要求的不同的選取s,c和f的方案數

Sample Input

4 3

1 2

2 3

3 4

Sample Output

8

題解

建議前往loj服用以獲得更好體驗…
似乎我的做法和網上的都不太一樣啊…
感覺我的就是個弟弟…
首先先考慮樹怎麼做…可以用一個 f [ i ] f[i] 表示以i點為根的子樹中找出兩個合法點的方案數
然後二次掃描就可以獲得10分的好成績了
拓展到圖上
可以考慮縮一個點雙出來,比如建一棵圓方樹
同樣預處理這個 f

[ i ] f[i] ,在方點的時候就記錄的是下面的圓點的 f [ i ] f[i] ,圓點就和樹一樣做
轉移可以看 程式碼…大概很好懂
同樣二次掃描
進環的時候要特殊處理一下…大致就是環上的點可以和這整棵子樹上的所有點構成一個合法的二元組
估計也 能看懂的…

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
LL stack[20];
inline void write(LL x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    LL top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(LL x){write(x);putchar(' ');}
inline void pr2(LL x){write(x);putchar('\n');}
const LL MAXN=500005;
const LL MAXM=500005;
struct node{LL x,y,next;}a[2*MAXM],b[2*MAXM];LL len,last[MAXN],lenb,lastb[2*MAXN];
void ins(LL x,LL y){len++;a[len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;}
void ins_b(LL x,LL y){lenb++;b[lenb].x=x;b[lenb].y=y;b[lenb].next=lastb[x];lastb[x]=lenb;}
LL dfn[MAXN],low[MAXN],sta[MAXN],cnt,id,tp,root,n,m;
LL r[MAXN];
void link(){for(LL i=1;i<=r[0];i++)ins_b(r[i],cnt),ins_b(cnt,r[i]);}
void tarjan(LL x,LL fa)
{
	low[x]=dfn[x]=++id;sta[++tp]=x;
	for(LL k=last[x];k;k=a[k].next)
		if(a[k].y!=fa)
		{
			LL y=a[k].y;
			if(!dfn[y])
			{
				tarjan(y,x);low[x]=min(low[x],low[y]);
				if(low[y]>dfn[x])ins_b(y,x),ins_b(x,y),tp--;
				else if(low[y]==dfn[x])
				{
					cnt++;r[r[0]=1]=x;
					LL i;
					do
					{
						i=sta[tp--];
						r[++r[0]]=i;
					}while(i!=y);
					link();
				}
			}
			else low[x]=min(low[x],dfn[y]);
		}
}
bool vis[2*MAXN];
LL f[2*MAXN],g[2*MAXN],ans,tot[MAXN],siz[MAXN];//這個方點在的環有多少個點 
void dp1(LL x,LL fa)
{
	tot[x]=(x<=n);vis[x]=true;
	for(LL k=lastb[x];k;k=b[k].next)
	{
		LL y=b[k].y;
		if(y!=fa)
		{
			dp1(y,x);tot[x]+=tot[y];
			if(y<=n)
			{
				if(x<=n)f[x]+=f[y]+tot[y],g[x]+=tot[y];
				else f[x]+=f[y],siz[x]++,g[x]+=g[y];
			}
			else f[x]+=(siz[y])*(tot[y]-1)+tot[y]+f[y]-g[y],g[x]+=tot[y];
		}
	}
}
LL s1;
void dp2(LL x,LL fa,LL u1)//上面選兩個點的方案數   有多少點在上面 
{
	if(x<=n)ans+=u1;LL u2=s1-tot[x];
	for(LL k=lastb[x];k;k=b[k].next)
	{
		LL y=b[k].y;
		if(y!=fa)
		{
			if(y>n)
			{
				ans+=(siz[y])*(tot[y]-1)+f[y]-g[y];
				dp2(y,x,u1+f[x]-(siz[y]*(tot[y]-1)+tot[y]+f[y]-g[y])+u2);//這是個方點
			}
			else//這是個圓點 
			{
				if(x<=n)
				{
					ans+=f[y];
					dp2(y,x,u1+f[x]-(f[y]+tot[y])+u2);
				}
				else
				{
					LL ring=siz[x]+1;//這個環有這麼多點 
					LL sum=f[x]-f[y]+u1;
					sum+=(LL)(s1-tot[y]-ring+1)*(ring-2)+(ring-1)*(ring-2);//剩下有這麼多 
					dp2(y,x,sum);
				}
			}
		}
	}
}
int main()
{
//    freopen("a.in","r",stdin);
//    freopen("a.out","w",stdout);
	n=read();m=read();
	for(LL i=1;i<=m;i++)
	{
		LL x=read(),y=read();
		ins(x,y);ins(y,x);
	}
	cnt=n;
	for(LL i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
//	puts("");
//	for(LL i=1;i<=lenb;i+=2)printf("%d %d\n",b[i].x,b[i].y);
	for(LL i=1;i<=n;i++)if(!vis[i]){
		dp1(i,0);s1=tot[i];
		dp2(i,0,0);
	}
	pr2(ans);
	return 0;
}
/*
4 4
1 2
2 3
3 4
4 2
14
*/