P10141 [USACO24JAN] Merging Cells P

题解

正难则反

  • 正着,设 dp(l,r) 为合并出 [l,r]的概率,枚举大区间两端点合并(区间DP)
    • 复杂度 O(n4)
  • 反着,设 dp(l,r) 表示由 [1,n] 分裂出 [l,r] 的概率
    • 设大区间为 [i,j],中间点为 k
    • [i,k]>[k+1,j]dp(i,k)=dp(i,j)ij (一共有 ij 个空隙,选择其中一个的概率是 1ij
    • 反之,dp(k+1,j)=dp(i,j)ij
    • 这里 [i,k]>[k+1,j]​ 的比较规则见题意
  • 如果 i 固定,使得 [k+1,j]<[i,k]j 单点递减
  • 同样,如果 j 固定,使得 [i,k]<[k+1,j]i​ 单调递增
  • 所以可以采用双指针的方法,维护前/后缀和,把复杂度降到 O(n2)
  • 此时 dp(i,k)=p=k+1jdp(i,p)pi

代码

  • 小细节

    • 为什么不是

      • 因为此时 [l,r] 的编号本身就 >[ll[r],l],所以只要值 [ll[r],l] 就可以
      • 而上面 [l,r] 的编号 <[r,rr[l]] ,如果值相等,那就会取后者而不是 [l,r]
    • 前后缀和的 l,r 具体是谁

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int N = (int)5e3 + 10;
int n;
int s[N];
int dp[N][N], pre[N][N], suf[N][N];
int inv[N], ll[N], rr[N];
int Q_pow(int a, int b){
int ans = 1, p = a;
while(b){
if(b & 1){
ans = (ans * p) % MOD;
}
p = (p * p) % MOD;
b >>= 1;
}
return ans;
}
signed main(){
cin >> n;
inv[0] = 1;
for(int i = 1; i <= n; i++){
cin >> s[i];
s[i] += s[i - 1];
inv[i] = Q_pow(i, MOD - 2);
ll[i] = 1, rr[i] = n;
}
dp[1][n] = pre[1][n] = suf[1][n] = inv[n - 1];
for(int i = n - 1; i >= 1; i--){
for(int l = 1, r = i; r <= n; l++, r++){
while(s[r] - s[l - 1] <= s[rr[l]] - s[r]){
rr[l]--;
}
// l, r + 1
(dp[l][r] += (suf[l][r + 1] - suf[l][rr[l] + 1] + MOD) % MOD) %= MOD;
while(s[r] - s[l - 1] < s[l - 1] - s[ll[r] - 1]){ // 为什么不是 <= !
ll[r]++;
}
// l - 1, r
(dp[l][r] += (pre[l - 1][r] - pre[ll[r] - 1][r] + MOD) % MOD) %= MOD;
(dp[l][r] *= inv[r - l]) %= MOD;
suf[l][r] = suf[l][r + 1];
(suf[l][r] += dp[l][r]) %= MOD;
pre[l][r] = pre[l - 1][r];
(pre[l][r] += dp[l][r]) %= MOD;
}
}
for(int i = 1; i <= n; i++){
cout << dp[i][i] << "\n";
}
cout << "\n";
}
posted on   Bubble_e  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话



点击右上角即可分享
微信分享提示