Loading

正睿

DP:

下面均方案的操作顺序若无特殊说明无要求。
例1-1:将一个数 n 划分成 x 个正整数,求方案数.(n,x5000
考虑 dp, fi,j 表示当前和为 i, 用了 j 个数的方案数.
那么平凡的转移是 fik,j1fi,j,时间复杂度 O(n3),不能通过。
可以把其当成是一个数学问题,有一堆柱子,每次可以加一根柱子,要求最后所有柱子的长度之和为 n
我们考虑如果加入一个长度为 k 的柱子,相当于在 k1 次操作前加入一个长度为 1 的柱子,并且每次使其长度 +1,如果是这样,那就只有两种转移了:

  1. 加入一根柱子
  2. 集体 +1
    那么在不同的时刻加入柱子,最后其长度就是定的,所以可以不重不漏的转移,时间复杂度 O(n2).

例1-2:将 n 划分成不同的正整数,求方案数.(n100000)
由于是不同的,加入的数的个数是 O(n) 级别的(1+2+3+...+n=O(n)),直接 dp 即可,时间复杂度 O(nn).

例1-3:同 1-2 但是正整数可以相同
根号分治,>n 的数只能选 n 个,直接 dp 即可,剩下的数像例 1-1 一样跑就行,因为只有 n 种不同的数。时间复杂度 O(nn).

例题:[NOI Online #1 入门组] 跑步

考虑根号分治,n<n 时是完全背包,nn 时是例 1-1,但这时候加入柱子的长度是 n 而不是 1.

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, B = 320;
typedef long long ll;
typedef pair<int, int> pii;
int mod;
void cmax(int &x, int y) { if (x < y) x = y; }
void cmin(int &x, int y) { if (x > y) x = y; } 
void add(int &x, int y) { x += y; if (x >= mod) x -= mod; }
void mul(int &x, int y) { x = 1ll * x * y % mod; }
template<typename T>
void dbg(const T &t) { cerr << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    #ifdef ONLINE_JUDGE
        return ;
    #endif
    cerr << arg << ' ';
    dbg(args...);
}   
int n, m, f[N], g[B][N];
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> mod;
    m = sqrt(n) + 1;
    f[0] = 1;
    for (int i = 1; i < m; i++) {
        for (int j = i; j <= n; j++) {
            add(f[j], f[j - i]);
        }
    }
    g[0][0] = 1;
    for (int i = 1; i < m; i++) {
        for (int j = i; j <= n; j++) {
            g[i][j] = g[i][j - i];
            if (j >= m) add(g[i][j], g[i - 1][j - m]);
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) {
        int sum = 0;
        for (int j = 0; j < m; j++) add(sum, g[j][n - i]);
        add(ans, 1ll * sum * f[i] % mod);
    }
    cout << ans << '\n';
    return 0;
}

trick: 连续段 dp
维护 n 个连续段,每次支持三种转移:

  1. 新建一个段
  2. 将一个段的两边扩张
  3. 合并两个段

例题:[CEOI 2016] kangaroo

注意到题目中跳跃的序列是一个 1n 的排列,考虑以 1n 的 顺序填到某个排列中(先不管 st),设 dpi,j 表示考虑到 i,目前一共有 j 段的方案数,上面的转移中,第一种显然是可行的,因为后面填的数肯定大于 i,第二种不行,因为此时 i 和之前的一个数相邻,比其大,又会比之后填到 i 另一边的数要小,不符合,第三种显然也可行,i 比两边的数都大。我们让连续段可以浮动,即其可以任意平移。

那么考虑转移,isit 时,就是刚刚的做法,然后由于连续段可以浮动,一共有 j1+1=j 个空可以插,但是注意第一种在 i>si>t 时由于 s,t 的位置固定为 1,n,可以放的位置会变少,故转移方程为:
dpi,j=dpi1,j1×(j[i>s][i>t])+dpi1,j+1×j
i=si=t 时,必然是 s,t1,n,所以如果 2n1 被填上了,就是 dpi1,j,否则是 dpi1,j1

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int, int> pii;
void cmax(int &x, int y) { if (x < y) x = y; }
void cmin(int &x, int y) { if (x > y) x = y; } 
void add(int &x, int y) { x += y; if (x >= mod) x -= mod; }
void mul(int &x, int y) { x = 1ll * x * y % mod; }
template<typename T>
void dbg(const T &t) { cerr << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    #ifdef ONLINE_JUDGE
        return ;
    #endif
    cerr << arg << ' ';
    dbg(args...);
}   
int n, s, t, dp[N][N];
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> s >> t;
    dp[1][1] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            if (i != s && i != t) dp[i][j] = (1ll * j * dp[i - 1][j + 1] % mod + 1ll * (j - (i > s) - (i > t)) * dp[i - 1][j - 1] % mod) % mod;
            else dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % mod;
        }
    }
    cout << dp[n][1] << '\n';
    return 0;
}

字符串

iS 的 border |s|is 的周期
周期的单调性:如果 iS+c (c 为字符)的周期,那么 i 也是 S 的周期

posted @   循环一号  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示