2021牛客多校5-Double Strings(dp+组合数学)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这道题不要读假题呀,这道题不是让你选连续的一段(要是连续的一段不就成签到题了么)

dp思路

d p [ i ] [ j ] dp[i][j] dp[i][j]表示只考虑 A A A中的前 i i i个字符和 B B B中的前 j j j个字符时的相同的子序列的个数。
状态转移如下:

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] − d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1]

d p [ i ] [ j ] dp[i][j] dp[i][j]等于不考虑本位的i的情况下的情况数+不考虑本位j的情况下的情况数-这两种情况算重的情况(就是同时不考虑i的本位和j的本位)
当然如果 s [ i ] = = t [ j ] s[i]==t[j] s[i]==t[j]
那么就是 d p [ i ] [ j ] + = d p [ i − 1 ] [ j − 1 ] dp[i][j]+=dp[i-1][j-1] dp[i][j]+=dp[i1][j1]
因为如果这种情况的话,那么我们少考虑了一种情况,就是i和j都考虑的情况,那么无论我们前面怎么选,后面加一个 i i i位和 j j j位就可以了,因此要加上一个 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]这样是我们没有运算到的。

初始化问题

这个待会会再说
在这里插入图片描述
右边是不对的初始化,左边是正确的初始化。

求组合数的问题

在正常不卡空间的题里面我们一般会开一个 C [ 5005 ] [ 5005 ] C[5005][5005] C[5005][5005]的数组来记录一下组合数的大小,但是这道题铁定 M L E MLE MLE,因此我觉得那我就用 L u c a s Lucas Lucas定理求组合数,但是 T L E TLE TLE了,这道题应该用阶乘预处理逆元求解组合数。因为模数是素数,因此我们可以用快速幂求解逆元。

递推求组合数

// c[a][b] 表示从a个苹果中选b个的方案数
for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

来源:acwing算法基础课

Lucas定理求组合数

若p是质数,则对于任意整数 1 <= m <= n,有:
    C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

int qmi(int a, int k, int p)  // 快速幂模板
{
    int res = 1 % p;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p)  // 通过定理求组合数C(a, b)
{
    if (a < b) return 0;

    LL x = 1, y = 1;  // x是分子,y是分母
    for (int i = a, j = 1; j <= b; i --, j ++ )
    {
        x = (LL)x * i % p;
        y = (LL) y * j % p;
    }

    return x * (LL)qmi(y, p - 2, p) % p;
}

int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

来源:acwing算法基础课

预处理逆元求组合数

昨天其实还挺麻烦的,我是这么搞的:

ll qmi(ll a,ll k,ll p=N) {
    ll res=1%p;
    while(k){
        if(k&1) res=(ll)res*a%p;
        a=(ll)a*a%p;
        k>>=1;
    }
    return res;
}
ll lsc[2102100],bym[2021000];
ll C(ll m,ll n,ll p=N)  
{
    if(n==0)return 1;
    if(n==m)return 1;
    return lsc[m]*bym[n]%p*bym[m-n]%p;
}

int main(){
	lsc[1]=1;
    bym[1]=1;
    for(int i=2;i<=1000000;i++) 
    {
     lsc[i]=lsc[i-1]*i%N;
     bym[i]=qmi(lsc[i],N-2);
    }
    //C(3,2)=3
    //lsc数组是阶乘数组,bym数组是逆元数组
}

当然acwing上也有板子:

首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int k, int p)    // 快速幂模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
#include<iostream>
#include<cstring>
using namespace std;

typedef long long ll;

ll dp[5002][5002];
char s[5020];
char t[5020];
const ll N=1e9+7;
ll qmi(ll a,ll k,ll p=N) {
    ll res=1%p;
    while(k){
        if(k&1) res=(ll)res*a%p;
        a=(ll)a*a%p;
        k>>=1;
    }
    return res;
}

ll lsc[2102100],bym[2021000];

ll C(ll m,ll n,ll p=N)  
{
    if(n==0)return 1;
    if(n==m)return 1;
    return lsc[m]*bym[n]%p*bym[m-n]%p;
}

int main(){
	cin>>s+1>>t+1;
	lsc[1]=1;
    bym[1]=1;
    for(int i=2;i<=1000000;i++) 
    {
     lsc[i]=lsc[i-1]*i%N;
     bym[i]=qmi(lsc[i],N-2);
    }
	int n=strlen(s+1);
	int m=strlen(t+1);
	ll ans=0;
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		dp[i][0]=1;
		for(int j=1;j<=m;j++){
			dp[0][j]=1;
			dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
			dp[i][j]%=N;
			if(s[i]==t[j]){
				dp[i][j]+=dp[i-1][j-1];
				dp[i][j]%=N;
			}else if(s[i]<t[j]){
				ans+=(ll)(dp[i-1][j-1]*C(n+m-i-j,n-i))%N;
				/*cout<<i<<" "<<j<<"--------";
				cout<< C(n+m-i-j,n-i)<<endl;*/
			}
			if(dp[i][j]<=0)dp[i][j]+=N;
		}
	}
    ans%=N;
	cout<<ans<<endl;
}
### 联合活动 Chocolate 问题解题思路 #### 背景描述 在网的联合活动中,“Chocolate”问题是关于如何合理分配巧克力给朋友。具体来说,目标是通过K次切割将一块由个连续部分组成的巧克力分成K+1份,每一份都包含一些连续的部分。 #### 解决方案概述 为了有效地解决这个问题,可以采用动态规划的方法来寻找最优解法。该方法的核心在于定义状态转移方程以及初始化边界条件[^1]。 #### 动态规划实现细节 - **状态表示**:设`dp[i][j]`代表前i个块中做最j刀所能获得的最大价值。 - **初始条件**:当没有切分时(`j=0`),最大值即为整个区间的总和;对于其他情况,则需遍历所有可能的位置进行尝试。 - **状态转移**:对于每一个新的位置k,在其之前已经完成了一定数量的分割操作(j),此时需要计算从当前位置到起点之间的最小成本,并更新全局最优解。 ```python def max_chocolate_value(chunks, K): n = len(chunks) # 计算区间内的累积和用于快速求子数组之和 prefix_sum = [0] * (n + 1) for i in range(1, n + 1): prefix_sum[i] = prefix_sum[i - 1] + chunks[i - 1] dp = [[float('-inf')] * (K + 1) for _ in range(n)] # 初始化第一列 for i in range(n): dp[i][0] = prefix_sum[i + 1] for j in range(K + 1): # 遍历每一刀数目的可能性 for i in range(n): # 当前考虑到第几个chunk为止 if j == 0 or i < j: continue for p in range(i): # 尝试不同的最后一刀位置p cost = abs(prefix_sum[p + 1] - prefix_sum[i + 1]) dp[i][j] = max(dp[i][j], min(dp[p][j - 1], cost)) return dp[-1][-1] ``` 此算法的时间复杂度主要取决于三重循环结构O(N^3*K),其中N为chunks的数量,K为允许的最大切割次数。虽然看起来效率不高但对于题目规模而言是可以接受的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值