2022“杭电杯”中国大学生算法设计超级联赛(7)1003 Counting Stickmen

题意简化下可以理解为,给一棵树,让我们找出树中所有如图所示的火柴人

一个节点脑袋,两条两个节点的手臂,一个节点胸,一个节点腰和两条一个节点的脚

最开始没想那么多当树形dp写的,后来发现火柴人可以躺着站着倒立着,全推倒重新写

后来组合数学选手臂的时候写错了,,愣是没找出来,赛后秒出

杭电的std给的是枚举胸腰(图中3 5号点)来拼凑,其实思路都大差不差

我们只需要枚举3号点,在遍历他所有临点,把可以当腿的节点存起来,对于每个节点当腿所以个答案依次加起来就好

这里说下组合,我们假设现在遍历的是x点中,以y做腰时的答案

对于腿脚的选择比较容易,只需要在红色的点选两个点就好,等于\binom{deg(y)-1}{2}

接下来我们说以下毒瘤手的选择,首先我们把所有能当手的链都记下来,也就是遍历x

求一个\sum u\in nerx(deg(u)-1)每个x的临点的出边(非连向x)都可以做手臂

记做handsumx

之后我们要减去y的出边,因为y已经当腿了,就不能当手了,所以handsumx-=deg(y)-1

然后我们计算手的选法时,要减掉一条链中选两个手的情况,也就是图中选左边两个蓝色点做手的情况 也就是遍历x 计算\sum u\in nerx(\binom{deg(u)-1}{2})

记做dishandsumx

别忘了dishandsumx也要减去y的情况 dishandsumx-=\binom{deg(y)-1}{2}

最后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';
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值