链接
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
个数字,拿走了个,剩下的数的集合是
s
s
(只考虑权值),剩余序列的最后一个数字是。
考虑我以
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
]
(箭头表示更新)
如果
k≠h[i+1]
k
≠
h
[
i
+
1
]
若我不取第
i+1
i
+
1
个数,会使混乱程度增加
1
1
,并且剩下的数字中会加入这个数字
f[i][j][s][k]+1→f[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
可以看作一个集合,逻辑运算看作集合的交并补
所以这道题,可以先想表示前
i
i
个数选走个的时候,最小混乱程度是多少,然后发现当我把某种数字全都选走的时候,
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;
}