G. Rikka with Intersections of Paths

G. Rikka with Intersections of Paths

題目大意

多組輸入開始一個數字 T 表示有 T 組輸入
給你一棵有 n 個節點的樹,然後這棵樹上有m條路被標記了,問你從這m條標記的路中選取k條,其中這k條最少有一個公共點,一共有多少種選法,最後的答案數對1e9+7取餘;
比如說樣例這組數據 我們可以選擇 2 3 這條路然後隨便選擇任何一條路都可以,因爲從2到3這條路已經包涵了所有節點,所以在選擇其他任何被標記的路都可以;
Example
input

1
3 6 2
1 2
1 3
1 1
2 2
3 3
1 2
1 3
2 3

output

10

解題思路

既然選擇的兩條路有至少一個相同點,那說明這兩條路徑肯定有一個公共祖先,然後我們記錄每個點被經過幾次和每個點作爲一條路的祖先被經過幾次;然後我們就可以枚舉每個節點,設這個節點一共被經過了 a 次,作爲祖先節點被經過 b 次,那麼以這個節點作爲k標記的路的重複節點的選法一共有C(a,k)-C(a-b,k)種方案;這是問什麼呢?因爲一共經過他的邊有 a 條,然後這a 條邊任意算k條就行,但是如果這樣選,會有重複的選擇;所以我們需要選擇一個不會有重複的策略,因爲我們知道任意一條邊都會有一個祖先,所以讓這個節點作爲祖先被經過的次數來表示這幾條邊,這樣這幾條邊就有一個唯一的標識度了,我們可以再這幾條邊中選擇若干條,然後剩下的從不是以這個節點作祖先的邊中選着,就可以;這樣就不會選擇重複了;這樣的選法就是上面說的C(a,k)-C(a-b,k);這個可以類似於現在有黑球n個,紅球m個讓你選擇出來k個球,不能全是紅球的組合公式,用總數減去不合法的數量
現在主要的思路有了,那麼如果求一個節點被經過了幾次呢?
我們可以用差分的思想了求,從樹的下面開始向上疊加就行;
比如說又一條 x 到 y 的邊我們在記錄的時候只需要
設 f=lca(x,y), fa爲 f 的父親節點
c[x]++,c[y]++,c[f] --,c[fa] --;
這條路上的祖先節點就直接讓 lca[f]++就可以了;

代碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mx=300300;
const ll mod=1e9+7;
int ans;
int T;
int n,m,k;
vector<int>ve[mx];//存圖 
ll fac[mx],inv[mx];//用於求組合函數 
ll c[mx],lca[mx];
//c 表示這個點被經過幾次,lca表示這個點作爲祖先別經過的次數 
int de[mx],fa[mx][30];//標準 Lca的數字 
int bit[30];//預處理2的n次方用的 

ll poow(ll a,ll b)//快速冪求 逆元 
{
   ll ans=1;
   while(b)
   {
   	if(b&1) ans=(ans*a)%mod;
   	a=(a*a)%mod;
   	b>>=1;
   }
   return ans%mod;
}
//對這棵樹分層 
void dfs(int x,int f)
{
   de[x]=de[f]+1;
   fa[x][0]=f;
   for(int i=1;bit[i]<=de[x];i++)
   fa[x][i]=fa[fa[x][i-1]][i-1];
   for(int i:ve[x])
   {
   	if(i!=f) dfs(i,x);
   }
   return ;
}
//求組合數的結果 
ll C(ll a,ll b)
{
   if(a<b||b==0) return 0;
   if(a==b) return 1;
   return fac[a]*inv[b]%mod*inv[a-b]%mod;
}
//標準的 倍增Lca求法 
int Lca(int x,int y)
{
   if(de[y]>de[x]) swap(x,y);
   for(int i=20;i>=0;i--)
   {
   	if(de[x]-de[y]>=bit[i])
   	{
   		x=fa[x][i];
   	}
   }
   if(x==y) return x;
   for(int i=20;i>=0;i--)
   {
   	if(fa[x][i]!=fa[y][i])
   	{
   		x=fa[x][i];y=fa[y][i];
   	}
   }
   return fa[x][0];
}
//從樹的最下面開始向上疊加
//記錄每個點被經過的次數 
void DFS(int x,int f)
{
   for(int i:ve[x])
   {
   	if(i!=f)
   	{
   		DFS(i,x);
   		c[x]+=c[i];//差分求和 
   	}
   }
//加上這個節點的貢獻值	
//因爲可能取模後減完是負值所以要 +mod 再取模 
   ans=(ans%mod+(C(c[x],k)%mod-C(c[x]-lca[x],k)%mod+mod)%mod)%mod;
   return ;
}
int main()
{
   ios::sync_with_stdio(0);
   bit[0]=fac[0]=inv[0]=1;
   for(int i=1;i<22;i++) bit[i]=bit[i-1]<<1;
   //預處理 i 的階乘及其逆元 
   for(int i=1;i<=300002;i++)
   {
   	fac[i]=fac[i-1]*i%mod;
   	inv[i]=poow(fac[i],mod-2);
   }
   cin>>T;
   while(T--)
   {
   	cin>>n>>m>>k;
   	ans=0;//初始化數據 
   	memset(c,0,sizeof c);
   	memset(lca,0,sizeof lca);
   	memset(fa,0,sizeof fa);
   	for(int i=0;i<=n;i++) ve[i].clear();
   	for(int i=1,x,y;i<n;i++)
   	{
   		cin>>x>>y;
   		ve[x].push_back(y);
   		ve[y].push_back(x);
   	}
   	dfs(1,0);//先進行分層 
   	for(int i=0,x,y;i<m;i++)
   	{
   		cin>>x>>y;
   		int f=Lca(x,y);
   		c[x]++,c[y]++,c[f]--,c[fa[f][0]]--;
   		//樹上的差分 
   		lca[f]++;//記錄這條路的 lca 被經過的次數 
   	}
   	DFS(1,0);//枚舉這棵樹上的節點 
   	cout<<ans<<"\n";
   }
   return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章