题解:P11655 「FAOI-R5」Lovely 139

P11655 题解

题目传送门

题目描述

给定两个非负整数 n 和 m,计算所有包含 n 个 0 和 m 个 1 的 01 字符串的极长颜色段数之和。

极长颜色段的定义是连续相同字符的最大子串。例如:

0011 的极长颜色段数为 2(两个 0 和两个 1)

0101 的极长颜色段数为 4(每个字符交替)

最终结果对 1e9+7 取模。 //补药忘记取模qwq

解题思路

通过观察不难发现极长颜色段的数量等于相邻字符不同的位置数目加一例如,字符串 0011 中相邻不同的位置数目为1,段数为1+1=2

因此,问题转化为计算所有字符串中相邻字符不同的位置数目之和,再加字符串总数

然后再推一下公式

1.字符串总数:n 个 0 和 m 个 1 排列的总数为组合数 C(n+m,n)

2.相邻不同的位置数目之和:假设某个位置是相邻不同的,这样的位置共有 (n+m-1) 种可能。又因每个位置的贡献为 2 * C(n+m-2,n-1)。所以总贡献为2 * (n+m-1) * C(n+m-2,n-1)

最终公式为:

g(n,m) = C(n+m,n)+2 * (n+m-1) * C(n+m-2,n-1)

特殊情况

当 n=0 且 m=0 时,答案为 1(空字符串)。

当 n=0 或 m=0 时,字符串全为同一字符,段数为 1。

代码 + 注释

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
const int MAXN = 2e6 + 10;
int f[MAXN],dp[MAXN]; //f存储阶乘,dp存储逆元
//快速幂计算逆元
int k(int x,int y)
{
int r = 1;
while(y)
{
if (y & 1) r = r * x % mod;
x = x * x % mod;
y >>= 1;
}
return r;
}
//计算组合数 C(a, b),利用公式 C(a,b) = a!/(b!(a-b)!)。
int c(int a,int b)
{
if(a < 0 || b < 0 || a < b) return 0;
return f[a] * dp[b] % mod * dp[a - b] % mod;
}
//特判:处理 n 或 m 为 0 的情况。
//公式实现:根据推导公式计算总和。
int g(int n,int m)
{
if(n == 0 && m == 0) return 1;
if(n == 0) return 1;
if(m == 0) return 1;
int tot = (n + m - 1) % mod;
tot = tot * 2 % mod;
tot = tot * c(n + m - 2,n - 1) % mod;
int ans = (c(n + m,n) + tot) % mod;
return ans;
}
signed main()
{
//预处理阶乘和逆元数组
f[0] = 1;
for(int i = 1;i < MAXN;i++)
{
f[i] = f[i - 1] * i % mod;
}
dp[MAXN - 1] = k(f[MAXN - 1],mod - 2);
for(int i = MAXN - 2;i >= 0;i--)
{
dp[i] = dp[i + 1] * (i + 1) % mod;
}
//阶乘数组 f:f[i] = i! % mod。
//逆元数组 dp:dp[i] = (i!)^{-1} % mod,通过费马小定理计算。
int T;
cin >> T;
while(T--)
{
int n,m;
cin >> n >> m;
cout << g(n,m) << "\n";
//输出就ok了
}
return 0;
}

dp数组的作用详解

1.dp数组的定义

dp数组用于存储逆阶乘的模逆元。具体来说:

dp[i] 表示 i! 在模 mod 意义下的逆元,即:dp[i] = (i!)^{-1} % mod

例如,当i = 3时,dp[3]表示3!的逆元,即6^{-1} mod 1e9+7。

2.为什么需要dp数组

组合数的计算需要除法,直接计算阶乘会导致数值过大,且模运算中除法不适用。

逆元的作用:
在模运算中,除以一个数等价于乘以它的逆元。

因此,dp 数组将除法转换为乘法,使得组合数的计算可行

3.dp数组的预处理过程

步骤 1:计算最大阶乘的逆元:

dp[MAXN - 1] = k(f[MAXN - 1], mod - 2);

利用费马小定理,当 mod 为质数时:

a^(-1) ≡ a^(mod-2) % mod;

这里 f[MAXN - 1] 是 (MAXN-1)!,通过快速幂计算其逆元。

步骤 2:递推计算所有逆阶乘:

for(int i = MAXN - 2;i >= 0;i--)
{
dp[i] = dp[i + 1] * (i + 1) % mod;
}

4.dp数组在组合数计算中的应用

在函数c(a,b)中:

f[a] * dp[b] % mod * dp[a - b] % mod;

等价于组合数公式

通过 dp 数组将除法转换为乘法:

1 / b! = dp[b],1 / (a-b)! = dp(a-b);

无注释代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
const int MAXN = 2e6 + 10;
int f[MAXN],dp[MAXN];
int k(int x,int y)
{
int r = 1;
while(y)
{
if (y & 1) r = r * x % mod;
x = x * x % mod;
y >>= 1;
}
return r;
}
int c(int a,int b)
{
if(a < 0 || b < 0 || a < b) return 0;
return f[a] * dp[b] % mod * dp[a - b] % mod;
}
int g(int n,int m)
{
if(n == 0 && m == 0) return 1;
if(n == 0) return 1;
if(m == 0) return 1;
int tot = (n + m - 1) % mod;
tot = tot * 2 % mod;
tot = tot * c(n + m - 2,n - 1) % mod;
int ans = (c(n + m,n) + tot) % mod;
return ans;
}
signed main()
{
f[0] = 1;
for(int i = 1;i < MAXN;i++)
{
f[i] = f[i - 1] * i % mod;
}
dp[MAXN - 1] = k(f[MAXN - 1],mod - 2);
for(int i = MAXN - 2;i >= 0;i--)
{
dp[i] = dp[i + 1] * (i + 1) % mod;
}
int T;
cin >> T;
while(T--)
{
int n,m;
cin >> n >> m;
cout << g(n,m) << "\n";
}
return 0;
}

完事

posted @   DomiSun  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
/* 点击爆炸效果*/
点击右上角即可分享
微信分享提示