uvalive4490(状态压缩 + dp)

题目大意:
从左到右给出n本数的高度和最多可以抽取的书本的数量k,如果书本与相邻的书本的高度不相同的话就是一个段,段越多混乱度就越高。求最低的混乱度。

思路:
dp[i][j][s][end]表示前i本书操作了j次,剩下的书的状态为s,最后一本书是end的最低的混乱度。
那么对于一本书有两种情况:
1. 跟其前面的书籍的高度是一样的
那么dp[i][j][s][end] = min(dp[i][j][s][end],dp[i - 1][j][s][end]);
2. 跟其前面的书籍的高度是不一样
(1)抽取这本书出来
那么dp[i][j + 1][s][end] = min(dp[i][j + 1][s][end],dp[i - 1][j][s][end]);
为什么会等于dp[i - 1][j][s][end]?
因为你将第i本数抽取出来了实行了j + 1次操作相当于你对第i - 1本书实行了j次操作。
(2)不抽取这本书
那么dp[i][j][s | (1 << num)][num] = min(dp[i][j][s|(1 << num)][num],dp[i][j][s][end] + 1);
代码:

#include <iostream>
using namespace std;
#include <cstring>
#include <stdio.h>
const int maxn = 1 << 8;
const int INF = 0x3f3f3f3f;
const int maxm = 110;

int dp[2][maxm][maxn][10],n,k;//由于只有前后操作所以2就足够了 利用滚动数组
int rec[1 << 8];

void init() {
    for(int i = 0; i < (1 << 8); i++) {//记录每个状态的书本最多有多少种段数 主要是用于抽取出来的书籍重新插入 每插入一次就增加一个段 如果留下来的有这种类就不用增加
        rec[i] = 0;
        for(int j = 0; j < 8; j++)
            if(i &(1 << j))
                rec[i]++;
    }
}
int main() {
    int T = 0;
    init();
    while(scanf("%d %d",&n,&k)  == 2 &&n && k) {
        int S = 0;
        int num,now,pre;
        memset(dp[0],INF,sizeof(dp[0]));

        for(int i = 0; i < n; i++) {
            scanf("%d",&num);
            num -= 25;
            pre = i & 1;//滚动数组的巧妙用法 只需要考虑前后的时候可以使用
            now = !pre;
            memset(dp[now],INF,sizeof(dp[now]));
            dp[now][i][(1 << num)][num] = 1;
            for(int j = 0; j <= min(i,k); j++) 
                for(int s = S;s;s = (s - 1) & S) {
                    for(int end = 0;(1 << end) <= s; end ++) {
                        if(dp[pre][j][s][end] == INF)
                            continue;
                        if(end == num)
                            dp[now][j][s][end] = min(dp[now][j][s][end],dp[pre][j][s][end]);
                        else {
                            dp[now][j + 1][s][end] = min(dp[now][j + 1][s][end],dp[pre][j][s][end]);
                            dp[now][j][s |(1 << num)][num] = min(dp[now][j][s |(1 << num)][num],dp[pre][j][s][end] + 1);
                        }
                    }
                }
                    S = S |(1 << num);//记录下总的种类数

        }
            int ans = INF;
            for(int j = 0; j <= k; j++) {
                for(int s = S;s;s = (s - 1)&S) {
                    for(int end = 0;(1 << end) <= s;end++) {
                        if(dp[n & 1][j][s][end] != INF ){
                            ans = min(ans,dp[n & 1][j][s][end] + rec[S^s]);//S^s记录的是被抽出来的在留下的种类中不存在的种类
                            //cout << ans <<endl;
                        }
                    }
                }
            }
            printf("Case %d: %d\n\n",++T,ans);

    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值