UVA12235 - Help Bubu

链接

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3387

题解

不知道是不是以为修仙太久,这道普通DP我竟然做了一个下午
f[i][j][s][k] f [ i ] [ j ] [ s ] [ k ] 表示的是考虑前 i i 个数字,拿走了j个,剩下的数的集合是 s s (只考虑权值),剩余序列的最后一个数字是k
考虑我以 f[i][j][s][k] f [ i ] [ j ] [ s ] [ k ] 为起点转移其它状态
考虑加入第 i+1 i + 1 个数
如果 k=h[i+1] k = h [ i + 1 ] ,那么取走第 i+1 i + 1 个数并不会使答案更优,还浪费了一次机会,因此我不取
f[i][j][s][k]f[i+1][j][s][k] f [ i ] [ j ] [ s ] [ k ] → f [ i + 1 ] [ j ] [ s ] [ k ] (箭头表示更新)
如果 kh[i+1] k ≠ h [ i + 1 ]
若我不取第 i+1 i + 1 个数,会使混乱程度增加 1 1 ,并且剩下的数字中会加入h[i]这个数字
f[i][j][s][k]+1f[i+1][j][s|(1<<h[i+1])][h[i+1]] f [ i ] [ j ] [ s ] [ k ] + 1 → f [ i + 1 ] [ j ] [ s | ( 1 << h [ i + 1 ] ) ] [ h [ i + 1 ] ]
若我取第 i+1 i + 1 个数
f[i][j][s][k]f[i+1][j+1][s][k] f [ i ] [ j ] [ s ] [ k ] → f [ i + 1 ] [ j + 1 ] [ s ] [ k ]
统计答案的时候,看一下有几个数都被选走了,假设一个状态中都被选走的数字的个数是 cnt[s] c n t [ s ]
那么 ans=min{f[N][j][s][k]+cnt[s]} a n s = m i n { f [ N ] [ j ] [ s ] [ k ] + c n t [ s ] }

经验

DP D P 这种东西,到底是先想状态还是先想转移说不好
一般是两条路同时走,想想我要干啥,设计一个初步状态,然后看看转移的时候有什么影响因素,再把这个因素加到状态里,然后接着想怎么转移;
如果状态的数字太大,而表示的值比较小,可以考虑调换状态和表示的值;
状态不一定表示实际意义,有时候表示的是根据数学推导得到的某些量
先无脑加状态,然后再考虑能不能去掉某些东西,或者使用某种优化
如果思路遇到死胡同,可以换一种思路从头再想,舍弃的起点可以或早或晚,可以从某个思路开始重新想,也可以直接把最初的思路都给舍弃
状压的 S S 可以看作一个集合,逻辑运算看作集合的交并补
所以这道题,可以先想f[i][j]表示前 i i 个数选走j个的时候,最小混乱程度是多少,然后发现当我把某种数字全都选走的时候, f f <script type="math/tex" id="MathJax-Element-206">f</script>的值和答案有偏差,偏差就是有多少种数字被选光了,为了表示有多少种数字被选光了,我可以加一维集合维,表示某种数字是不是存在
然后就接着想转移,这个时候问题就很明显了

代码

//DP
#include <cstdio>
#include <algorithm>
#include <cstring>
#define iinf 0x3f3f3f3f
#define cl(x,y) memset(x,y,sizeof(x))
#define maxn 110
#define upd(x,y) y=min(x,y);
using namespace std;
int f[maxn][maxn][(1<<8)][9], N, K, h[maxn], cnt[1<<8], vis[1000];
void init()
{
    int i, j;
    cl(f,0x3f), cl(cnt,0), cl(vis,0);
    for(i=1;i<=N;i++)scanf("%d",h+i), h[i]-=25, vis[1<<h[i]]++;
    for(i=0;i<1<<8;i++)for(j=1;j<(1<<8);j<<=1)if((i&j)==0 and vis[j])cnt[i]++;
}
void dp()
{
    int i, j, s, k, ans=iinf;
    f[1][1][0][8]=0;
    f[1][0][1<<h[1]][h[1]]=1;
    for(i=1;i<=N;i++)for(j=0;j<=K;j++)for(s=0;s<(1<<8);s++)for(k=0;k<=8;k++)
    {
        if(k==h[i+1])
        {
            upd(f[i][j][s][k],f[i+1][j][s][k]); //不选第i+1个 
        }
        else
        {
            upd(f[i][j][s][k],f[i+1][j+1][s][k]);   //选第i+1个 
            upd(f[i][j][s][k]+1,f[i+1][j][s|(1<<h[i+1])][h[i+1]]);  //不选第i+1个
        }
    }
    for(j=0;j<=K;j++)for(s=0;s<(1<<8);s++)for(k=0;k<=8;k++)upd(cnt[s]+f[N][j][s][k],ans);
    printf("%d\n\n",ans);
}
int main()
{
    int c=1;
    for(scanf("%d%d",&N,&K);N;scanf("%d%d",&N,&K))init(), printf("Case %d: ",c++), dp();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值