题目链接 点击这里
题目描述:视频清单里面有不同类型的视频,每次播放可以看多个连续的同类型视频,问至少要点播放键多少次
题目分析:
一开始直觉告诉我是
d
p
dp
dp ,当时一直在往区间dp方向想,后来发现
k
k
k 是小于
20
20
20 的可以状态压缩暴力搞
令
d
p
[
i
]
dp[\ i\ ]
dp[ i ] 表示第
i
i
i 中状态下二进制为1的的视频全被看掉的最小次数,其题目所求即为
d
p
[
(
1
<
<
k
)
−
1
]
dp[(1<<k)-1]
dp[(1<<k)−1]
在转移之前遍历一遍原序列,计算看完每种的视频需要的操作次数,即计算当前视频被分成了几块
状态转移:对于之前二进制已经为
1
1
1 的位置不需要管,对于二进制位新变为
1
1
1 没有看的视频,直接由
i
i
i 状态转移加上贡献,对所有结果取
m
i
n
min
min 即可
复杂度为
O
(
(
n
+
k
)
∗
2
k
)
O((n+k)*2^k)
O((n+k)∗2k) 约为
4
e
8
4e8
4e8 不会超时
具体细节见代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int dp[(1<<20)+55],id[405],cnt,num[405];
char s[405];
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
int main()
{
int n = read(),k = read();
scanf("%s",s);
for(int i = 0;i < n;i++)
{
int tmp = s[i]-'a';
if(!id[tmp])
id[tmp] = ++cnt;
}
memset(dp,0x3f,sizeof(dp));
dp[0] = 0;
for(int i = 0;i < (1<<k);i++)
{
memset(num,0,sizeof(num));
int pre = -1;
for(int j = 0;j < n;j++)
{
if(((i>>(id[s[j]-'a']-1))&1) == 0 && pre != id[s[j]-'a'])
{
pre = id[s[j]-'a'];
num[pre]++;
}
}
for(int j = 1;j <= cnt;j++)
dp[(1<<(j-1))|i] = min(dp[(1<<(j-1))|i],dp[i]+num[j]);
}
printf("%d\n",dp[(1<<k)-1]);
return 0;
}