题目链接:
http://codeforces.com/contest/453/problem/B
题意:
给出一个序列a,求取一个序列b,b序列的数两两互质,问能够导致∑|ai−bi|最小的方案
思路:
http://blog.csdn.net/qq_24451605/article/details/48878237 orz
- 定义状态dp[i][j]表示前i个数达到j状态的最小的结果,j状态表示已经被用过的质数。
- 因为当一个a的数据范围不超过30,所以如果某个数超过60,那么选择1一定比它更优,所以我们能够用到的数的质因子也一定不会超过60,也才17个,所以我们只需要每次枚举数,然后通过之前的状态进行转移即可。
- 具体转移过程见代码。
- 为了得到方案,我们记录了pre[i][j]代表前i个数达到状态j得到最大值的前一个状态,num[i][j]表示前i个数达到状态j得到最大值的最后选取的数字。
state[k] 表示k的所有质因子,用了k,就不能用所有k的质因子了
转移: dp[i+1][x] = min(dp[i+1][x],dp[i][j]+abs(a[i+1]-k)) 【 b[i+1]=k; x=state[k] | j 】
为什么对质数进行状压嘞?首先不同的质数是可以选的,一个质数只能用一次,那么如何分辨非质数是否可选? 其实就是判断这个非质数A的质因子是否已经用过了,假如前面已经选了一个非质数B,那么如果A与B有公因子x,即使x不是质数,也一定可以分解成质数,这样只需判断质数是否用过就好了。
挺有收获的,状压状压!!!
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define MS(a) memset(a,0,sizeof(a)) #define MP make_pair #define PB push_back const int INF = 0x3f3f3f3f; const ll INFLL = 0x3f3f3f3f3f3f3f3fLL; inline ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } ////////////////////////////////////////////////////////////////////////// const int maxn = 1e5+10; int n,a[105]; int dp[105][1<<16],pre[105][1<<16],num[105][1<<16],state[105]; int prime[20] = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; void print(int x,int st){ if(x == 1){ printf("%d ",num[x][st]); return ; } print(x-1,pre[x][st]); printf("%d ",num[x][st]); } int main(){ cin >> n; for(int i=1; i<=n; i++) cin >> a[i]; memset(dp,INF,sizeof(dp)); dp[0][0] = 0; for(int k=2; k<59; k++){ for(int j=0; j<16; j++) if(k%prime[j] == 0) state[k] |= (1<<j); } int tot = 1<<16; for(int i=0; i<n; i++){ for(int j=0; j<tot; j++){ if(dp[i][j]==INF) continue; for(int k=1; k<59; k++){ if(state[k]&j) continue; int x = state[k]|j; if(dp[i+1][x] > dp[i][j]+abs(a[i+1]-k)){ dp[i+1][x] = dp[i][j]+abs(a[i+1]-k); pre[i+1][x] = j; num[i+1][x] = k; } } } } int minn = INF,st; for(int j=0; j<tot; j++){ if(minn > dp[n][j]){ minn = dp[n][j]; st = j; } } print(n,st); puts(""); return 0; }