标签:sum printf 元素 cstring 前缀和 def 题解 简单的 其他
将n个0,m个1进行圆周排列,定义一个排列的权值为圆上所有相邻且相同的数字组成一段的段长的乘积,询问断环成链所有方案的权值之和,\(n,m\leq 5000\)。
因为问题与区间划分有关,直接计数不好做,对序列上考虑,不妨设\(f[i][j]\)为前i个元素(相同的)划分j段的权值和,显然有
\[f[i][j]=\sum_{k=0}^{i-1}f[k][j-1](i-k)\]
这个方程是\(O(n^3)\),于是考虑优化,两种方法
\[f[i][j]=i\sum_{k=0}^{i-1}f[k][j-1]-\sum_{k=0}^{i-1}kf[k][j-1]\]
于是我们只要维护\(f[k][j-1]\)和\(kf[k][j-1]\)的前缀和即可,对于式子的优化,通常要分开互不关联的项,或者单独考虑一项的出现次数。
\[f[i][j]=\sum_{k=0}^{i-1}f[k][j-1](i-k)=\]
\[\sum_{k=0}^{i-1}f[k][j-1](i-k-1)+\sum_{k=0}^{i-1}f[k][j-1]=\]
\[f[i-1][j]+f[i-1][j-1]+\sum_{k=0}^{i-1}f[k][j-1]\]
因此我们只要维护\(f[k][j]\)关于k的前缀和即可,个人更喜欢法二。
显然0的段和1的段数是相同的,这是01序列的一个性质,因此枚举段数,对于\(\sum_{i=1}^{min(n,m)}f[n][i]\times f[m][i]\),实际含义即所有以0的段开头的环对应的0的权值和,而链可以以任意开头,根据其他的题解,容易知道应该乘上一个系数\((n+m)/i\),这个系数我无法给出简单的证明。
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
#define Size 5050
#define yyb 1000000007
using namespace std;
int dp[Size][Size],s[Size],iv[Size];
int main(){
int n,m;iv[1]=1;
for(int i(2);i<=5000;++i)iv[i]=-(ll)yyb/i*iv[yyb%i]%yyb;
for(int i(1);i<=5000;++i)dp[i][1]=i;
for(int i,j(2);j<=5000;++j){
for(i=1;i<j;++i)s[i]=s[i-1]+dp[i][j-1];
for(i=j;i<=5000;++i)
dp[i][j]=(dp[i-1][j]+s[i-1])%yyb,
s[i]=(s[i-1]+dp[i][j-1])%yyb;
}
while(scanf("%d%d",&n,&m)!=EOF){
int ans(0);
for(int i(1);i<=min(n,m);++i)
ans=(ans+(ll)dp[n][i]*dp[m][i]%yyb*(n+m)%yyb*iv[i])%yyb;
printf("%d\n",(ans+yyb)%yyb);
}
return 0;
}
标签:sum printf 元素 cstring 前缀和 def 题解 简单的 其他
原文地址:https://www.cnblogs.com/a1b3c7d9/p/11408935.html