原题
思路
发现官方题解是枚举子集dp,但是我写的爆搜跑得也不慢,于是写一篇简单好写的搜索题解~
注意到 “如果她今天学习了一个长度为 k 的单词,她坚持要在当天记住词典 中所有长度为 k 的单词。”
于是我们考虑先把相同长度的单词压到一起,方便后边处理
接下来开始写dfs
令 dfs(now,num,ww) 表示目前考虑学习第 now 个单词,目前学了 num 天,第 num + 1 天已经学了 ww 个单词
如果第 num + 1 天还能学单词,显然我们不知道学哪种是最优的,我们就把今天还能学的都搜一遍
特别的,如果第 num + 1 天学习了这个单词后, ww == w ,即第 num + 1 天一个单词也学不了了,需要特殊处理
如果第 num + 1 天已经学不了任何长度的单词了,那么我们无论选择哪个单词放在第 num + 2 天学,都可以搜到最优解,随便选一个即可
现在已经可以跑过了,亲测156ms,大概是正解的两倍,但也挺快的
还有一个小剪枝,即如果 num + 1 >= ans ,那么目前的这种情况肯定不是最优的,直接 return 掉(但是好像也没优化多少)
这种想法,大概是暴力枚举全排列的优化版本,去除了不少重复情况
代码如下
#include <bits/stdc++.h>
#define int long long
#define maxn 100005
#define INF LONG_LONG_MAX
using namespace std;
int n,w;
int a[500005];
int tong[15];//长度i几个单词
int cnt;
bool pd[15];
int rt[15];
int ans;
void dfs(int now,int num,int ww){
if(now == cnt + 1){
ans = min(ans,num + (int)(ww != 0));
} else {
if(num + (int)(ww != 0) >= ans) return ;
bool f = 0;
for(int i = 1;i <= cnt;++ i){
if(pd[i]){
continue;
} else if(ww + rt[i] <= w){
pd[i] = 1;
if(ww + rt[i] == w) dfs(now + 1,num + 1,0);
else dfs(now + 1,num,ww + rt[i]);
pd[i] = 0;
f = 1;
}
}
if(!f){
for(int i = 1;i <= cnt;++ i){
if(pd[i]) continue;
pd[i] = 1;
dfs(now + 1,num + 1,rt[i]);
pd[i] = 0;
return ;
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> w;
for(int i = 1;i <= n;++ i) cin >> a[i],tong[a[i]] ++;
for(int i = 1;i <= 13;++ i){
if(tong[i]){
rt[++ cnt] = tong[i];
}
}
ans = n;
dfs(1,0,0);
cout << ans << "\n";
return 0;
}
1024






