题意简化下可以理解为,给一棵树,让我们找出树中所有如图所示的火柴人
一个节点脑袋,两条两个节点的手臂,一个节点胸,一个节点腰和两条一个节点的脚
最开始没想那么多当树形dp写的,后来发现火柴人可以躺着站着倒立着,全推倒重新写
后来组合数学选手臂的时候写错了,,愣是没找出来,赛后秒出
杭电的std给的是枚举胸腰(图中3 5号点)来拼凑,其实思路都大差不差
我们只需要枚举3号点,在遍历他所有临点,把可以当腿的节点存起来,对于每个节点当腿所以个答案依次加起来就好
这里说下组合,我们假设现在遍历的是x点中,以y做腰时的答案
对于腿脚的选择比较容易,只需要在红色的点选两个点就好,等于
接下来我们说以下毒瘤手的选择,首先我们把所有能当手的链都记下来,也就是遍历x
求一个每个x的临点的出边(非连向x)都可以做手臂
记做handsumx
之后我们要减去y的出边,因为y已经当腿了,就不能当手了,所以handsumx-=deg(y)-1
然后我们计算手的选法时,要减掉一条链中选两个手的情况,也就是图中选左边两个蓝色点做手的情况 也就是遍历x 计算
记做dishandsumx
别忘了dishandsumx也要减去y的情况 dishandsumx-=
最后handsumx减去dishandsumx就是我们总的手的选法
然后就是头的选择,这个很简单,就等于deg(x)-3(总的临点减去手和脑袋)
他们三个乘在一起就是对于x当胸,y当腰的选法
接下来只需要扫一遍即可
代码如下
#include <bits/stdc++.h>
#define pb push_back
#define int long long
#define fer(i,a,b) for(int i=a;i<=b;++i)
#define der(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
const int mod=998244353;
const int N=5e5+10;
vector<int> v[N];
int qpow(int a,int b,int p){
int res=1%p;
while(b){
if(b&1) res=res*1ll*a%p;
a=a*1ll*a%p;
b>>=1;
}
return res;
}
int jian(int a,int b){ return (a-b+mod)%mod;}
int res;
int db(int a){//n*(n-1)/2
return ((a*jian(a,1))%mod*qpow(2,mod-2,mod))%mod;
}
int noderes(int node){
int totsum=v[node].size();
if(totsum<4) return 0;//如果没有四条或以上出边肯定为0
vector<int> body;
int handsum=0;
int dishandsum=0;
for(auto t:v[node]){
if(v[t].size()>=3){
body.pb(t);//可以当腰的点
}
if(v[t].size()>=2){
handsum+=v[t].size()-1;//手的总链数
dishandsum+=db(v[t].size()-1);//同一临点外选两只手的总数
}
}
int ans=0;
for(auto t:body){
int tmp=v[t].size()-1;
int nowhandsum=handsum-tmp;//减去y的手的链数
int nowdishandsum=dishandsum-db(tmp);//减去y的不合法选手的选法
int choosebody=db(jian(v[t].size(),1));//腿的选法
int choosehand=jian(db(nowhandsum),nowdishandsum);//手的选法
int choosehead=jian(totsum,3);//脑袋的选法
ans+=((choosebody*choosehand)%mod*choosehead)%mod;
ans%=mod;
}
return ans;
}
signed main()
{
int T;
cin>>T;
while(T--){
int n;
cin>>n;
fer(i,1,n) v[i].clear();
res=0;
fer(i,1,n-1){
int a,b;
cin>>a>>b;
v[a].pb(b);
v[b].pb(a);
}
fer(i,1,n){
res+=noderes(i);
res%=mod;
}
cout<<res<<'\n';
}
}