题解 Y
考场上糊了一个 \(n^3\) 的DP,结果不会去重,回归暴力了
正解来自战神和这篇博客
首先发现一个人能给出多少球与他收到了多少球无关,所以可以以此为依据划分状态进行DP
转化一步:\(\prod b_i = \prod\binom{b_i}{1}\),即可以转化为传完球后,每个人再从手里剩下的球中拿出一个的方案数
根据上面的划分,可以想到要根据这个人从给的球还是剩的球中选为划分进行DP
于是令 \(f_{i, 0/1}\) 为第 \(i\) 个人从表示第 \(i\) 个人从自己剩下的球中选,前 \(i-1\) 个人选球的方案数及第 \(i\) 个人从前面的人给的球中选,前 \(i\) 个人选球的方案数
一个是 \(i\) 一个是 \(i-1\) 是因为一个人剩的球和下一个人得到的球相关联,在一次转移中可以一起考虑
然后转移和上面博客基本一样,就偷懒不写了
特别注意系数是 \(a_i+1\) 的那个转移是因为这里定义的「方案数」不是很严谨,其实还包括了选球位置相同但传球数不同的方案数
然后是处理成环转移
- 关于成环转移的处理:
如果一个环形DP满足每个决策点只向下影响下一个决策点一次,且这个影响不会再向下传递(基本就需要满足和Pass to Next一样的特殊限制)
那也许可以在环上选择一个断点,枚举这个点的决策
对于上面那道题,就可以枚举第一个人选的球是哪一种,并将这种状态的初值设为1
至于为什么要设成1,是因为方案数其实是所有系数的乘积,所以初值为1只代表这种状态合法,而与答案(系数的积)无关
然后关于这题的去重:如果所有人传球数的最小值不为零,显然可以让每个人都少传一个得到另一种结果一样的方案
所以可以用不限制每个人传球数量的方案数减去限制每个人传球数量至少为1的方案数得到最终方案数
Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1000010
#define ll long long
#define pb push_back
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n;
ll a[N];
const ll mod=1e9+7;
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
namespace force{
vector<int> v;
vector<vector<int>> ans;
void dfs(int i, int j) {
// cout<<"dfs: "<<i<<' '<<j<<endl;
if (i>n) {
v[0]+=j;
ans.pb(v);
v[0]-=j;
return ;
}
for (int r=1; r<=a[i]+j; ++r) if (a[i]+j-r <= a[i]) {
v.pb(r);
dfs(i+1, a[i]+j-r);
v.pop_back();
}
}
void solve() {
reverse(a+1, a+n+1);
if (n==1) {printf("%lld\n", a[1]); exit(0);}
for (int i=0; i<=a[1]; ++i) {
// cout<<"i: "<<i<<endl;
v.pb(i);
dfs(2, a[1]-i);
v.pop_back();
}
sort(ans.begin(), ans.end());
ans.erase(unique(ans.begin(), ans.end()), ans.end());
// cout<<"siz: "<<ans.size()<<endl;
ll sum=0;
for (auto it:ans) {
ll tem=1;
for (auto i:it) tem=tem*i%mod;
// for (auto i:it) cout<<i<<' '; cout<<endl;
sum=(sum+tem)%mod;
}
printf("%lld\n", sum);
exit(0);
}
}
namespace task1{
ll dp[110][110][110], ans;
void solve() {
reverse(a+1, a+n+1);
for (int i=0; i<=a[1]; ++i) dp[i][1][a[1]-i]=1;
for (int j=2; j<=n; ++j) {
for (int i=0; i<=a[1]; ++i) {
for (int k=0; k<=a[j-1]; ++k) {
for (int r=0; r<=a[j]+k; ++r) {
md(dp[i][j][a[j]+k-r], 1ll*r*dp[i][j-1][k]%mod);
cout<<i<<' '<<j<<' '<<k<<' '<<r<<' '<<dp[i][j-1][k]<<' '<<1ll*r*dp[i][j-1][k]<<endl;
}
}
}
}
for (int i=0; i<=a[1]; ++i) {
for (int k=0; k<=a[n]; ++k) {
dp[i][n][k]=dp[i][n][k]*(i+k)%mod;
md(ans, dp[i][n][k]);
}
}
for (int i=0; i<=a[1]; ++i) {
for (int j=1; j<=n; ++j) {
for (int k=0; k<=a[n]; ++k) {
printf("dp[%d][%d][%d]=%lld\n", i, j, k, dp[i][j][k]);
}
}
}
printf("%lld\n", ans);
exit(0);
}
}
namespace task{
ll dp[N][2], inv[N];
inline ll sum(ll n) {n%=mod; return n*(n+1)%mod*inv[2]%mod;}
inline ll sum2(ll n) {n%=mod; return n*(n+1)%mod*(2*n+1)%mod*inv[6]%mod;}
// pre=1: return dp[1][0]
// pre=0: return dp[1][1]
ll calc(int pre, int beg) {
memset(dp, 0, sizeof(dp));
if (pre) dp[1][0]=1, dp[1][1]=0;
else dp[1][0]=0, dp[1][1]=1;
for (int i=1; i<n; ++i) {
md(dp[i+1][0], dp[i][0]*((a[i]*(a[i]+(beg^1))%mod-sum(a[i]))%mod)%mod);
md(dp[i+1][1], dp[i][0]*((a[i]*sum(a[i])%mod-sum2(a[i]))%mod)%mod);
md(dp[i+1][0], dp[i][1]*(a[i]+(beg^1))%mod);
md(dp[i+1][1], dp[i][1]*sum(a[i])%mod);
}
md(dp[1][0], dp[n][0]*((a[n]*(a[n]+(beg^1))%mod-sum(a[n]))%mod)%mod);
md(dp[1][1], dp[n][0]*((a[n]*sum(a[n])%mod-sum2(a[n]))%mod)%mod);
md(dp[1][0], dp[n][1]*(a[n]+(beg^1))%mod);
md(dp[1][1], dp[n][1]*sum(a[n])%mod);
return (pre?dp[1][0]:dp[1][1])-1;
}
void solve() {
inv[0]=inv[1]=1;
for (int i=2; i<=10; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
ll ans=calc(1, 0)+calc(0, 0)-calc(1, 1)-calc(0, 1);
printf("%lld\n", (ans%mod+mod)%mod);
exit(0);
}
}
signed main()
{
freopen("y.in", "r", stdin);
freopen("y.out", "w", stdout);
n=read();
for (int i=1; i<=n; ++i) a[i]=read();
task::solve();
return 0;
}