P10912 [蓝桥杯 2024 国 B] 数星星 题解
Sol
题意转化为给定一棵树,有多少点集 \(G\) 满足 \(L \le |V_G| \le R\) 且它是一颗菊花树。
我们考虑对于每一个节点,算出以其为根的菊花树造成了多少贡献。
最暴力的方法实际上就是先枚举点集内点的个数 \(x\),那么贡献即为 \(\sum\limits_{i=1}^n C_{du_i}^{x-1}\)。
于是我们就有一个 \(O(n^2)\) 的暴力解法,答案为:
\[\sum_{x=L}^{R}\sum\limits_{i=1}^n C_{du_i}^{x-1}
\]
考虑怎么优化,显然你可以把两个求和倒过来:
\[\sum\limits_{i=1}^n\sum_{x=L}^{R} C_{du_i}^{x-1}
\]
然后你发现相同的两个 \(du_i\) 后面的一堆算出来的答案是相同的,于是我们记忆化一下。然后你发现你过了。
因为一棵树不同的度数最多只有 \(\lceil \sqrt{n} \rceil\) 个,然后总时间复杂度是 \(O(n \sqrt{n})\) 的。
Hack
你会发现如果按照上面那样算,在 \(x=2\) 的时候每条边的 \(2\) 个端点会各算一次只有这两个点的集合,这样就重复了。
然后就有了下面这组 Hack:
2
1 2
2 2
Hack 掉了 luogu 里面 \(2\) 篇题解!
所以特判一下 \(L\le 2\) 就好了,数据太水了。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x(0);
short f(1);
char ch(getchar());
while(!isdigit(ch))f=(ch=='-')?-1:1,ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
int qpow(int x,int y,int p)
{
if(y==0)
return 1;
int tmp=qpow(x,y>>1,p);
if(y&1)
return tmp*tmp%p*x%p;
return tmp*tmp%p;
}
const int N=2e5+5,mod=1e9+7;
int n,du[N],num[N];
namespace plzh
{
int fac[N],inv[N];
inline void init()
{
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2,mod);
for(int i=n-1;i>=1;i--)
inv[i]=inv[i+1]*(i+1)%mod;
return ;
}
inline int C(int m,int n)
{
if(m<n)
return 0;
return fac[m]*inv[n]%mod*inv[m-n]%mod;
}}
signed main()
{
n=read();
for(int i=1;i<=n-1;i++)
{
int x(read()),y(read());
du[x]++;
du[y]++;
}
for(int i=1;i<=n;i++)
num[i]=-1;
plzh::init();
int L(read()),R(read()),ans=0;
if(L==1)
ans+=n,L++;
if(L==2&&L<=R)
ans+=n-1,L++;
for(int i=1;i<=n;i++)
{
if(num[du[i]]!=-1)
{
ans=(ans+num[du[i]])%mod;
continue;
}
int now=0;
for(int j=L-1;j<=R-1;j++)
now=(now+plzh::C(du[i],j))%mod;
ans=(ans+now)%mod;
num[du[i]]=now;
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号