题目链接
BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=5305
洛谷:https://www.luogu.org/problemnew/show/P4492
LOJ:https://loj.ac/problem/2526
Solution
神仙计数题...
对于一棵固定的树,对于每一条边的贡献很容易算,如果这条边较深的点为(v),那么这条边对答案的贡献就是(2sz_v(n-sz_v))。
那么我们反过来考虑,我们(O(n^2))的枚举这条边的情况,即枚举深度较小的点的编号和较大的点的(size),然后统计一条这样的边在所有情况中出现了多少次。
首先我们硬点加点的时候是按编号顺序从小到达的加的,这对答案无影响。
设当前枚举到的点为(i),深度较大的点(size)为(j),我们分成三部分统计。
- (1sim i)的点没有限制,有(i!)种情况。
- 然后剩下了(n-i)个点,我们要选出(j)个放在(i)下面,这(j)个点连的时候也没有限制,那么就是(inom{n-i}{j}j!)种情况。
- 除开这些点剩下还有(n-i-j)个点,第一次有(i)种选法,第二次有(i+1)种...最后一次有(n-i-j+1)种选法,一共就是(prod_{k=i}^{n-i-j+1}k) 种。
那么总的贡献就是:
[ans=sum_{i=1}^{n}sum_{j=1}^{n-i}2j(n-j)cdot i!cdot j!inom{n-i}{j}cdot prod_{k=i}^{n-j-1}k
]
化简一下:
[egin{align}
ans=&sum_{i=1}^{n}i!cdot 2sum_{j=1}^{n-i}j(n-j)cdot j!inom{n-i}{j}prod_{k=i}^{n-j-1}k\
=&sum_{i=1}^{n}i!cdot 2sum_{j=1}^{n-i}j(n-j)cdot frac{(n-i)!}{(n-i-j)!}prod_{k=i}^{n-j-1}k\
=&sum_{i=1}^{n}i!cdot 2sum_{j=1}^{n-i}j(n-j)cdot left(prod_{k=n-i-j+1}^{n-i}k
ight)left(prod_{k=i}^{n-j-1}k
ight)
end{align}
]
由于没有逆元,可以预处理出(s[i][j]=prod_{k=i}^{j}k),时间复杂度(O(n^2))。
#include<bits/stdc++.h>
using namespace std;
void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('
');}
#define lf double
#define ll long long
const int maxn = 2e3+10;
const int inf = 1e9;
const lf eps = 1e-8;
int s[maxn][maxn],n,mod,ans;
int main() {
read(n),read(mod);
for(int i=1;i<=n;i++) {
s[i][i]=i;
for(int j=i+1;j<=n;j++) s[i][j]=1ll*s[i][j-1]*j%mod;
for(int j=0;j<i;j++) s[i][j]=1;
}
for(int i=1;i<=n;i++) {
int res=0;
for(int j=1;j<=n-i;j++) res=(res+1ll*j*(n-j)%mod*s[n-i-j+1][n-i]%mod*s[i][n-j-1]%mod)%mod;
ans=(ans+2ll*s[1][i]%mod*res%mod)%mod;
}write(ans);
return 0;
}