CODEFORCES, 454D Little Pony and Harmony Chest

题意:有n个数的数组a 每一个数属于[0,30]

现在要找一个n个数的数组b 使得|ai-bi|的和最小 对于任意i,j,i!=j,bi,bj最大公因数为1

 (1 ≤ n ≤ 100)

策略: 首先这样的b数组肯定存在 我们可以用n个1来实现,而且ai的范围在[0,30]所以 我们在某一个位置上用1和用59的效果是一样的,也就是说b的每一位上我们的选择只要在[1,58]之间就可以了。然后盲目搜索复杂度非常高。所以我们可以通过之前的状态来选择下一位上的数。

假设我们已经在前i位选择了i个数 已经使用了质数prime[k1,k2...kn]的最小和为 sum,那么我们在选择下一位i+1的时候,我们从1-58中找到一个质数没有在之前使用过,即不在prime[k1,k2,..kn]中的数,并把这个数使用的质数加入到prime状态中即可实现转移。

设dp[i][S]为前i位,素数使用状况为S的最小和,那么下一个数bi+1选择后,使用素数状况为S1,且S1&S=0,我们可以更新dp[i+1][S|S1]=min(dp[i+1][S|S1],dp[i][S]+|ai-bi|);

因为最后要输出数组,要用rec记录一下每个状态选择的数。找到第i位状态为S的最小dp值,最后通过S状态反向输出即可。

素数大约有16个,所以S我们可以二进制表示,16个二进制位每一位01表示对应的素数是否用过,状态压缩dp

复杂度在 2^16*n*58


//Author:Wenjun Shi
//on 2016/7/14
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <climits>


#define MAX_N 105
#define P_NUM 16
#define MAX_NUM 58
using namespace std;


int n,m;
int prime[P_NUM]= {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};//需要的素数
int use[60]; //数i包含的素数的状态位情况 二进制表示
int rec[MAX_N][1<<P_NUM];//到第i位,用了哪些素数产生最小值时第i位所选的数
int dp[MAX_N][1<<P_NUM];//到第i位,用了哪些素数产生的最小值


int a[MAX_N];
int ans[MAX_N];
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) scanf("%d",&a[i]);
    for(int i=1; i<=MAX_NUM; i++)
    {
        for(int j=0; j<P_NUM; j++)
        {
            if(i%prime[j]==0) use[i]|=1<<j;
        }
    }
    memset(rec,-1,sizeof(rec));
    memset(dp,0x7f,sizeof(dp));
    const int INF=dp[0][0];
    dp[0][0]=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<1<<P_NUM; j++)
        {
            for(int k=1; k<=MAX_NUM; k++)
            {
                if(dp[i-1][j]!=INF&&(use[k]&j)==0)
                {
                    int d=abs(a[i]-k);
                    if(dp[i][j|use[k]]>dp[i-1][j]+d)
                    {
                        dp[i][j|use[k]]=dp[i-1][j]+d;
                        rec[i][j|use[k]]=k;
                    }
                }
            }
        }
    }
    int Min=INF,s;
    for(int j=0;j<1<<P_NUM;j++)
        if(dp[n][j]<Min)
        {
            Min=dp[n][j];
            s=j;
        }
    for(int i=n;i>0;i--)
    {
        ans[i]=rec[i][s];
        s^=use[ans[i]];
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<' ';
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值