动态规划 -> 线性dp

1. Flipping Game

题意

给定n个灯的初始状态,每回合对m盏灯进行反转,求在k回合后达到指定状态的方案数。

心理路程

  1. 看到了k个回合,每回合m次反转,求方案数,用屁股想都是dp。
  2. 以为是状压dp,但是数据范围是100,意味着有2^100次方种状态,时间复杂度远远不够。任何企图枚举状态的方法,都会TLE。

教训

取模优先级整错了,wa了半天。
先加减乘除优先,后取模。

思路

  1. 求出需要按奇数次开关的个数为cnt,dp[i][j]表示第i回合,亮j盏灯的方案数,状态转移为dp[i + 1][j] += dp[i][x + num * 2 - m] * C[n - x][num] * C[x][m - num]。num是m盏要按的灯里,原本灭的个数,x是i回合亮灯数。
  2. 初始化dp[0][0] = 1,最后的结果要除以C[n][cnt],取模意义下要乘其逆元。或者初始化dp[0][cnt] = 1,最后的dp[round][0]就是答案。
/*
 * Author:  Chen_zhuozhuo
 * Created Time:  2020/4/29 11:31:14
 * File Name: a.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define per(i,a,b) for(ll i=(a);i>=(b);i--)
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const ll maxll = -1u>>1;
const ll inf = 0x3f3f3f3f;
const ll maxn = 105;
ll n,k,m;
char a[maxn], b[maxn];
ll dp[maxn][maxn];
ll c[maxn][maxn];
ll MOD = 998244353;

///mod优先级低,先乘除后取模

void print(){
    for(int i=0;i<=k;i++){
        cout<<i<<"th: ";
        for(int j=0;j<=n;j++){
            cout<<dp[i][j]<<' ';
        }
        cout<<endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    ll t;cin>>t;
    
    for(int i=0;i<maxn;i++)    //求组合数
        for(int j=0;j<=i;j++) c[i][j]=1;
    for(int i=2;i<maxn;i++)
        for(int j=1;j<i;j++)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;    //要取模!
    
    while(t --){
        //len, round, times
        memset(dp, 0, sizeof(dp));
        cin>>n>>k>>m;
        cin>>a + 1>> b + 1;
        ll cnt = 0;
        
        for(ll i=1;i<=n;i++){
            a[i] = ((a[i] - '0') ^ (b[i] - '0')) + '0';
            if(a[i] == '1'){
                cnt ++;
            }
        }

        dp[0][cnt] = 1;

        for(ll i=0; i<k; i++){                  //回合数
            for(ll x=0; x<=n; x++){             //当前回合亮灯数
                for(ll num=0; num<=m; num++){   //选取的,原本为暗的数量
                    if(x + num * 2 - m <= n && x + num * 2 - m >= 0){
                        dp[i + 1][x + num * 2 - m] = dp[i + 1][x + num * 2 - m] + dp[i][x] * (c[n - x][num] * c[x][m - num] % MOD) % MOD;
                        dp[i + 1][x + num * 2 - m] %= MOD;
                    }
                }
            }
        }
        
        //print();
        
        cout<<dp[k][0]<<endl;
    }
    return 0;
}
/*
Sample Input
3
3 2 1
001
100
3 1 2
001
100
3 3 2
001
100
Sample Output
2
1
7 
*/
### 什么是线性DP线性动态规划(Linear Dynamic Programming,简称线性DP)是动态规划的一种基本形式,其特点是状态的转移呈现线性结构,即状态之间的依赖关系是单向且连续的。通常,这类问题可以通过一维或二维数组进行状态表示,并通过递推关系逐步解。 线性DP常用于处理序列问题,如最大子段和、最长上升子序列、数字三角形、传球游戏等。其核心思想是将原问题拆解为多个子问题,并通过状态转移方程将前面的结果传递给后续状态,从而避免重复计算,提高效率 [^1]。 ### 状态表示与状态转移 在动态规划中,状态表示是解决问题的关键。线性DP中的状态通常具有以下特征: - **状态维度**:状态通常是一维或二维的,例如`dp[i]`表示以第`i`个元素结尾的最优解。 - **状态转移方式**:当前状态`dp[i]`往往依赖于前面的状态`dp[i-1]`或`dp[i-k]`,有时也可能依赖多个前驱状态,如`dp[i] = max(dp[i-1], dp[i-2])` [^4]。 例如,在最大子段和问题中,状态表示为`dp[i]`表示以`a[i]`结尾的最大连续子段和,状态转移方程为: ```cpp dp[i] = max(dp[i-1] + a[i], a[i]) ``` 最终的最大子段和则是所有`dp[i]`中的最大值 [^4]。 ### 应用场景 线性DP广泛应用于以下问题: - **最大子段和**:给定一个整数序列,其连续子序列的最大和。 - **最长上升子序列**:在一个序列中找出最长的严格递增子序列。 - **数字三角形问题**:从三角形顶部到底部,每一步只能走到相邻节点,路径的最大值。 - **传球游戏**:从起点传球`k`次后回到起点的方案数- **乌龟棋**:使用不同类型的卡片前进,能到达的最大分数。 例如,在数字三角形问题中,可以使用二维DP进行解: ```cpp #include <iostream> #include <algorithm> using namespace std; const int N = 510; int n, INF = 1e9; int a[N][N], f[N][N]; int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> a[i][j]; for (int i = 0; i <= n; i++) for (int j = 0; j <= i + 1; j++) f[i][j] = -INF; f[1][1] = a[1][1]; for (int i = 2; i <= n; i++) for (int j = 1; j <= i; j++) f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]); int res = -INF; for (int i = 1; i <= n; i++) res = max(res, f[n][i]); cout << res << endl; return 0; } ``` 该程序使用了线性DP的思想,通过状态转移逐步计算出每一层的最大路径和 [^3]。 ### 优化策略 线性DP在实际应用中可以通过以下方式进行优化: - **滚动数组优化**:当状态只依赖于前一层状态时,可以用一维数组代替二维数组,节省空间。 - **前缀和优化**:对于某些涉及区间和的问题,可以预处理前缀和数组,从而减少重复计算。 - **单调队列/双端队列优化**:在特定条件下,如滑动窗口最值问题中,可以利用单调队列优化状态转移过程 [^1]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值