动态规划题集2

动态规划题集2

1. 互不侵犯King

题目:在 N × N N×N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。 ( N ≤ 9 , K ≤ N ∗ N ) (N\le 9, K \le N*N) (N9,KNN).

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 15, K = 90, M = (1 << 9) + 10;
ll f[N][M][K];
int n, m;
int cnt[M];
vector<int> head[M];
int calculate(int x)
{
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        res += ((x >> i) & 1);
    }
    return res;
}

bool check(int x)
{
    for(int i = 1; i < n; i++)
    {
        if((x >> (i - 1)) & (x >> i)) return false;
    }
    return true;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i < (1 << n); i++)
    {
        cnt[i] = calculate(i);
        for(int j = 0; j < (1 << n); j++)
        {
            if((i & j) == 0 && check(i | j))
            {
                head[i].push_back(j);
            }
        }
    }
    f[0][0][0] = 1;
    for(int i = 1; i <= n + 1; i++)
    {
        for(int j = 0; j < (1 << n); j++)
        {
            for(int k = 0; k <= m; k++)
            {
                for(auto p : head[j])
                {
                    if(k >= cnt[p]) f[i][j][k] += f[i - 1][p][k - cnt[p]];
                }
            }
        }
    }

    printf("%lld\n", f[n + 1][0][m]);
    return 0;
}

2. 集合选数

题目:求集合 { 1 , 2 , … , n } \{1,2,…,n\} {1,2,,n} 的子集个数,且需要满足:若 x x x 在子集中,则 2 x 2x 2x 3 x 3x 3x不在子集中。 ( n ≤ 1 0 5 ) (n\le 10^5) (n105)

写出如下矩阵

1 3 9 27…

2 6 18 54…

4 12 36 108…

发现最多有11列,最多17 行

我们在其中选取一些数,相邻的不能选择.

然后就可以状压求方案数了,但是5没有出现,同样5的倍数也没有出现,7也如此

应该记录哪些数字出现过,没出现过就作为矩阵的第一个元素,最后把若干个矩阵的方案相乘

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod =  1000000001;
const int N = 100010, M = (1 << 17) + 10;
bool st[N], check[M];
ll f[18][M], ans = 1;
int n;
int main()
{
    scanf("%d", &n);

    for(int i = 0; i < (1 << 17); i++)
    {
        check[i] = true;
        for(int j = 1; j < 17 && check[j]; j++)
        {
            if((i >> (j - 1)) & (i >> j)) check[i] = false;
        }
    }

    for(int u = 1; u <= n; u++)
    {
        f[0][0] = 1;
        if(st[u]) continue;
        int i, a, lastm = 0;
        for(i = 1, a = u; a <= n; a <<= 1, i++)
        {
            int p = a, m = 0;
            while(p <= n)
            {
                st[p] = true;
                p *= 3, m++;
            }
            for(int j = 0; j < (1 << m); j++)
            {
                f[i][j] = 0;
                for(int k = 0; k < (1 << lastm); k++)
                {
                    if(check[j] && check[k] && (j & k) == 0) f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
                }
            }
            lastm = m;
        }
        ans = ans * (f[i - 1][0] + f[i - 1][1]) % mod;
    }
    printf("%lld\n", ans);
    return 0;
}

3. POI2004 PRZ

n n n 个人要过桥,桥有承重量 W W W。所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。一个队伍的过桥时时间应该算走得最慢的那一个,我们想知道如何分批过桥能使总时间最少

n < = 16 n<=16 n<=16

首先, d p [ i ] dp[i] dp[i] 为状态为 i i i 下,这些队员过桥最少要用的时间,再维护一下每个状态 i i i 的总重量 W W W 以及总时间 T T T (指一次过桥的重量和时间,不管这个状态能否过桥)。

接着,顺序枚举状态 i i i ,并枚举 j j j ( j ∈ i j \in i ji ),将 i i i 分为状态 j j j 和状态 i ⊕ j i \oplus j ij ,意思就是 状态 j j j 的一次过桥时间 + + + 状态 i i i 的最优过桥时间,注意状态 j j j 的总重量要小于题目给定的 w w w ,更新 d p [ i ] dp[i] dp[i]

d p [ i ] = min ⁡ ( d p [ i ] , T [ j ] + d p [ i ⊕ j ] ) ( W [ j ] ≤ w ) dp[i] = \min (dp[i],T[j]+dp[i \oplus j] )(W[j] \le w) dp[i]=min(dp[i],T[j]+dp[ij])(W[j]w)

#include<bits/stdc++.h>
using namespace std;
const int N = 20, M = (1 << 17) + 10;
int t[N], w[N], f[M], maxt[M], sumw[M];
int n, W;
int main()
{
    scanf("%d%d", &W, &n);

    //小心这个地方要从0开始编号.
    for(int i = 0; i < n; i++) scanf("%d%d", &t[i], &w[i]);
    for(int i = 0; i < (1 << n); i++)
    {
        for(int j = 0; j < n; j++)
        {
            if((i >> j) & 1) sumw[i] += w[j], maxt[i] = max(maxt[i], t[j]);
        }
    }

    memset(f, 0x3f, sizeof f);
    f[0] = 0;

    for(int i = 0; i < (1 << n); i++)
    {
        for(int j = i; ; j = ((j - 1) & i))
        {
            if(sumw[j] <= W) f[i] = min(f[i], f[i ^ j] + maxt[j]);
            if(!j) break;
        }
    }

    printf("%d\n", f[(1 << n) - 1]);
}

4. 奇怪的道路

题目: n n n 个点,要连接 m m m 条无向边,可以有重边,要保证最后每个点度数为偶数,并且连边的两个点 u , v u,v u,v 需要满足 1 < = ∣ u − v ∣ < = K 1<=|u-v|<=K 1<=uv<=K,问方案数。 1 < = n < = 30 , 0 < = m < = 30 , 1 < = K < = 8 1<=n<=30, 0<=m<=30, 1<=K<=8 1<=n<=30,0<=m<=30,1<=K<=8

5. Hero meet devil

给一个长度不超过 15 15 15 的串 S S S ( 仅包含 A C G T ACGT ACGT ),对于所有的 i i i ( 0 ≤ i ≤ ∣ S ∣ 0\le i \le |S| 0iS) ,求有多少长度为 M M M 的串 T T T 满足串 T T T 和串 S S S 的最长公共子序列长度为 i i i ( M ≤ 1000 ) (M\le 1000) (M1000)

6. SCOI2008 奖励关

7. 不要62

[ L , R ] [L,R] [L,R]​ 间,不出现 “ 62 62 62” 的数的个数。

裸的数位dp

#include<bits/stdc++.h>
using namespace std;
const int N = 25;
int f[N][N][2], a[N];
int A, B;

int dp(int pos, int k, int lim)
{
    if(pos == -1) return 1;
    int& v = f[pos][k][lim];
    if(v != -1) return v;
    int ans = 0, up = lim ? a[pos] : 9;
    for(int i = 0; i <= up; i++)
    {
        if(i == 4) continue;
        if(k == 6 && i == 2) continue;
        ans += dp(pos - 1, i, i == up && lim);
    }
    return f[pos][k][lim] = ans;
}

int solve(int x)
{
    int sz = 0;
    while(x)
    {
        a[sz++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dp(sz - 1, 0, 1);
}

int main()
{
    ios::sync_with_stdio(0);
    while(cin >> A >> B, A)
    {
        cout << solve(B) - solve(A - 1) << endl;
    }
}

8. B-number

B数的定义:能被13整除且本身包含字符串"13"的数。例如:130和2613是B数,但是143和2639不是B数。你的任务是计算1到n之间有多少个数是B数。

小心深搜的地方,千万别少了 else 判断

#include<bits/stdc++.h>
using namespace std;
const int N = 35, mod = 13;
int f[N][15][15][2], a[N];

int dp(int pos, int sum, int k, int flag, int lim)
{
    if(pos == -1) return sum == 0 && flag;
    int& v = f[pos][sum][k][flag];
    if(!lim && v != -1) return v;
    int ans = 0, up = lim ? a[pos] : 9;
    for(int i = 0; i <= up; i++)
    {
        if(k == 1 && i == 3) ans += dp(pos - 1, (sum * 10 + i) % mod, i, 1, i == up && lim);
        //千万别少了 else!!!
        else ans += dp(pos - 1, (sum * 10 + i) % mod, i, flag, i == up && lim);
    }
    if(!lim) v = ans;
    return ans;
}

int main()
{
    int x;
    while(cin >> x)
    {
        int sz = 0;
        while(x)
        {
            a[sz++] = x % 10;
            x /= 10;
        }
        memset(f, -1, sizeof f);
        printf("%d\n", dp(sz - 1, 0, 0, 0, 1));
    }
    return 0;
}

9. count 数字计数

给定两个正整数 a a a b b b,求在 [ a , b ] [a,b] [a,b] 中的所有整数中,每个数码 (digit,指 0-9) 各出现了多少次。其中 a ≤ b ≤ 1 0 12 a ≤ b ≤ 10^{12} ab1012

首先,我们单独处理这十个数字会简单一些

我们令 f ( i , c n t ) f(i, cnt) f(i,cnt) 表示处理到了第 i i i 个位置,数字 d d d 从高位到低位出现的次数是 c n t cnt cnt 个. 那么处理到最后一位的时候,只需要返回 c n t cnt cnt 就可以了.

#include<bits/stdc++.h>
using namespace std;
const int N = 20;
typedef long long ll;
ll f[N][N][2][2];
int a[N];
//d表示当前的数字,cnt表示从高位到低位放了多少个d了.
ll dp(int pos, int cnt, int d, int flag, int lim)
{
    if(pos == -1) return cnt;
    ll& v = f[pos][cnt][flag][lim];
    if(v != -1) return v;
    ll ans = 0;
    int up = lim ? a[pos] : 9;
    for(int i = 0; i <= up; i++)
    {
        ans += dp(pos - 1, cnt + (i == d && (d != 0 || !flag)), d, i == 0 && flag, i == up && lim);
    }
    return v = ans;
}

ll solve(ll x, int d)
{
    int sz = 0;
    while(x)
    {
        a[sz++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dp(sz - 1, 0, d, 1, 1);
}

int main()
{
    ll a, b;
    scanf("%lld%lld", &a, &b);
    for(int i = 0; i <= 9; i++) printf("%lld%c", solve(b, i) - solve(a - 1, i), i == 9 ? '\n' : ' ');
    return 0;
}

10.手机号码

[ L , R ] [L,R] [L,R] 间,不同时出现 8 , 4 8,4 8,4,且必须包含三个相邻且相同的数。 1 0 10 ≤ L ≤ R ≤ 1 0 11 10^{10} \le L \le R \le 10^{11} 1010LR1011.

这个题8个参数的状态全部开,答案会不一样.

#include<bits/stdc++.h>
using namespace std;
const int N = 15;
typedef long long ll;
ll f[N][N][N][2][2][2][2][2];
int a[N];

ll dp(int pos, int flag_4, int flag_8, int consecutive, int k, int cnt, int lead_zero, int lim)
{
    if(flag_4 && flag_8) return 0;
    if(pos == -1) return consecutive;
    ll& v = f[pos][k][cnt][flag_4][flag_8][consecutive][lead_zero][lim];
    if(v != -1) return v;
    ll ans = 0;
    int up = lim ? a[pos] : 9;
    for(int i = 0; i <= up; i++)
    {
        int t = !lead_zero || i != 0;
        if(i == k && !lead_zero) t = cnt + 1;
        ans += dp(pos - 1, flag_4 || i == 4, flag_8 || i == 8, consecutive || (t >= 3), i, t, i == 0 && lead_zero, i == up && lim);
    }
    return v = ans;
}

ll solve(ll x)
{
    int sz = 0;
    while(x)
    {
        a[sz++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dp(sz - 1, 0, 0, 0, 0, 0, 1, 1);
}

int main()
{
    ll L, R;
    scanf("%lld%lld", &L, &R);
    //printf("%lld\n", solve(R));
    printf("%lld\n", solve(R) - solve(L - 1));
    return 0;
}

11. D. Beautiful numbers

定义 b e a u t i f u l beautiful beautiful 数:这个数能被它的每一位非零数整除。例如 12 12 12 能被 1 1 1 2 2 2 整除,故 12 12 12 b e a u t i f u l beautiful beautiful 数,求区间 [ l , r ] [l,r] [l,r] 内的 b e a u t i f u l beautiful beautiful 数的个数

被每一位整除,即对这个数的每位数字的lcm取模的值为0 。

于是考虑状态中存下所有数位的lcm,以及对于1~9的lcm(为2520)取模的值。

d p [ i ] [ j ] [ k ] [ 0 / 1 ] dp[i][j][k][0/1] dp[i][j][k][0/1]:长为i,所有数位的lcm为j,对2520取模为k,0/1表示是否压着上界。

注意所有可能的lcm数目很小,应当进行离散化。不离散化会 M L E MLE MLE

这个题有多组询问,但是 f f f 数组不用初始化(每次初始化会 T L E TLE TLE)。只要保存 l i m = 0 lim = 0 lim=0 的状态即可。让 f f f 保存的也是 l i m = 0 lim = 0 lim=0 时的结果。

#include<bits/stdc++.h>
using namespace std;
const int N = 19, M = 2525;
typedef long long ll;
ll f[N][M][50];
int a[N];

int Hash[M];
ll dp(int pos, int sum, int lcm, int lim)
{
    if(pos == -1) return sum % lcm == 0;
    ll& v = f[pos][sum][Hash[lcm]];
    if(!lim && v != -1) return v;

    int up = lim ? a[pos] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; i++)
    {
        ans += dp(pos - 1, (sum * 10 + i) % 2520, i == 0 ? lcm : lcm * i / __gcd(lcm, i), i == up && lim);
    }
    if(!lim) v = ans;
    return ans;
}

ll solve(ll x)
{
    int sz = 0;
    while(x)
    {
        a[sz++] = x % 10;
        x /= 10;
    }
    return dp(sz - 1, 0, 1, 1);
}

int main()
{
    int id = 0;
    for(int i = 1; i <= 2520; i++)
    {
        if(2520 % i == 0)
        {
            Hash[i] = ++id;
        }
    }

    memset(f, -1, sizeof f);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        ll L, R;
        cin >> L >> R;
        cout << solve(R) - solve(L - 1) << endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值