比赛的时候想直接爆搜加剪枝,无限TLE。赛后学了这个状态压缩的姿势。因为b最多选58(否则选1就好了),而58内只有16个素数,可以把它们压成一个状态,先预处理出1~58内所有数对应的状态。这样记忆化搜索的时候,记录当前在选第几个数,和已经使用过的素数的状态,再加上路径还原,就可以得到答案了。
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#include<iostream>
#include<queue>
#include<cmath>
#include<string>
#include<set>
#include<map>
using namespace std;
const int maxn = 100 + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-7;
const int INF = 1000000000;
typedef long long LL;
typedef pair<int, int> P;
int prime[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int Hash[maxn];
int a[maxn];
int Min, n;
int ans[maxn];
int dp[maxn][1<<16];
P trace[maxn][1<<16];
int dfs(int pos, int use){
if(dp[pos][use] != -1)
return dp[pos][use];
if(pos == n)
return dp[pos][use] = 0;
int Min = INF, chose;
for(int i = 1;i < 59;i++){
if((Hash[i]&use) == 0){
int tem = dfs(pos+1, use|Hash[i])+abs(a[pos]-i);
if(tem < Min){
Min = tem;
chose = i;
}
}
}
trace[pos][use] = P(use|Hash[chose], chose);
return dp[pos][use] = Min;
}
int main(){
for(int i = 1;i < 59;i++){
Hash[i] = 0;
for(int j = 0;j < 16;j++){
if(i%prime[j]){
Hash[i] = Hash[i]*2;
}
else{
Hash[i] = Hash[i]*2+1;
}
}
}
while(cin >> n){
for(int i = 0;i < n;i++){
cin >> a[i];
}
memset(dp, -1, sizeof dp);
dfs(0, 0);
int pos = 0, use = 0;
while(1){
if(pos == n)
break;
ans[pos] = trace[pos][use].second;
use = trace[pos][use].first;
pos++;
}
for(int i = 0;i < n;i++)
cout << ans[i] << ' ';
cout << endl;
}
return 0;
}