20171018校内训练

方法1:

考虑贪心,尽量把所有刀数全部割在一边是最优的,如果不行,把一边割成全都是1,剩下的刀数都割在另一边。

方法2:

我们有另外一个可以保证正确性的方法。

我们定义这几个量:i:在行上割的刀数,j:在列上割的刀数,len:行上每一段的距离

显然,我们把i刀尽量平分在行上,j刀尽量平分在列上最优。i+j=k。

所以,我们只要知道i,len中的其中任意一个值,就可以求出其它两个值,从而计算出答案。

我们发现,i*len<=n

∴i<=√n,len<=√n

这样,我们只要分别把i从0枚举到√n,把len从1枚举到√n,即可计算出答案。

注意,由于我们是枚举i,所以i一定会小于n,但i不能大于k,此时计算出的j一定小于k,但j要<=m-1,否则在列上根本割不出j刀

又由于我们是枚举len,所以i有可能大于k,但是此时的i不能舍去,而要看做在列上一刀也不割,因为此时如果len++后i就有可能小于k,那么在列上还要割几刀,不一定有在行上割超过k刀(其实可以看做就割上面的k刀,剩下的刀不割),列上不割更优。但j要<=m-1,否则在列上根本割不出j刀

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
//    freopen("cut.in","r",stdin);freopen("cut.out","w",stdout);
    long long n,m,k,ans=-1ll;cin>>n>>m>>k;
    if(k>n+m-2)return 0*puts("-1");
    for(long long i=0;i*i<=n&&i<=k;i++)//横着割的刀数,不能超过k 
    {
        long long j=k-i;
        if(j>=m)continue; 
        ans=max(ans,(n/(i+1))*(m/(j+1)));
    } 
    for(long long len=1;len*len<=n;len++)//当每段长为len时,横着最多可以割几刀 
    {
        long long i=n/len-1;long long j=max(0ll,k-i);
        if(j>=m)continue;
        ans=max(ans,(n/(i+1))*(m/(j+1)));
    }
    cout<<ans;
    return 0;
 } 
View Code

 

状压DP。

首先,我们可以发现a[i]<=30,这样你取的数的值不会大于58(因为大于58的数都可以用1代替)

<=58的质数一共有16个。这时,我们想到了什么?状压DP!

用f[i][S]表示前i个数(S的二进制第i位上为1表示选第i+1个质数(这些质数必须分别是前面选的i个数的因数))

然后我们就需要一些奇技淫巧。

首先预处理一个数组shu[i]表示数(i+2)的质因子,然后把它转成二进制存起来(二进制第i位上为1表示第i+1个质数是该数的因数)

为什么这么存呢?因为一个数可以分解成几个质数的乘积,即使一个质数的指数>1,只要其它数中没有出现过该质数,那么它们也一定互质,所以只要记每个质数是否出现过即可。

举个例子:50=2*5*5,则它的因数里有2和5这两个质数,而2是第1个质数,5是第3个质数,则shu[50]=1*2^0+0*2^1+1*2^2=5=(101)(二进制下)

求出这个有什么用呢?我们知道,若m个数两两互质,则它们的shu[i] & 起来一定为0(不算1的shu),否则就有相同的因数

这样,我们的状态转移方程就出来了:

f[i][j]=f[i-1][j]+a[i]-1(第i位取1)

f[i][j]=max(f[i-1][j^(异或)shu[k-2](shu[i]表示数(i+2))]+abs(a[i]-k)) (2<=k<=58&&shu[k-2]&j==0)

j&shu[k-2]的意思是k的因数中的质数必须在当前状态选择的质数中出现过(否则根本就不能选k)

j^shu[k-2]的意思是k的因数中的质数在上一个状态选择的质数中必须都没有出现过(否则就不互质)

最后在f[n][0~2^16-1]中取一个最大值(因为f[i][S]的定义是这些质数一定都要出现过,而实际上有的时候不全部选更优)

等等,还没说怎么输出方案呢?

我们用pre[i][S]表示f[i][S]这个状态是由f[i-1][pre[i][S]]转移来的,sum[i][S]表示在f[i][S]最优的情况下,第i位选的数。

这些都是可以在转移时记录下来的。

输出答案时,只要倒序输出sum[i][pre[i][S]]即可(从后往前遍历i,S为最优答案时的选的质数,然后让S=pre[i][S])

求shu[i]:

#include<iostream>
#include<cstdio>
using namespace std;
int pri[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
int main()
{
    for(int i=2;i<=58;i++)
    {
        int S=0,k=i,x=1;
        for(int j=0;j<16;j++)
        {
            if(k%pri[j]==0)S=S+x;
            while(k%pri[j]==0)
            {
                k=k/pri[j];
            }
            x=x*2;
        }
        cout<<S<<",";
    }
}
View Code

状压DP:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int pre[101][70000],sum[101][70000];
int f[110][70000],a[110];
int shu[57]={1,2,1,4,3,8,1,2,5,16,3,32,9,6,1,64,3,128,5,10,17,256,3,4,33,2,9,512,7,1024,1,18,65,12,3,2048,129,34,5,4096,11,8192,17,6,257,16384,3,8,5,66,33,32768,3,20,9,130,513};
int ans[110],Min=999999999;
int Abs(int x){return x>0?x:-x;}
int main()
{
//    freopen("array.in","r",stdin);freopen("array.out","w",stdout);
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=(1<<16)-1;j++)
    {
        sum[i][j]=1;pre[i][j]=j;f[i][j]=f[i-1][j]+a[i]-1;
        for(int k=2;k<=58;k++)
        {
            if(((j&shu[k-2])==shu[k-2])&&(f[i-1][j^shu[k-2]]+Abs(k-a[i])<f[i][j]))
            {
                f[i][j]=f[i-1][j^shu[k-2]]+Abs(k-a[i]);sum[i][j]=k;pre[i][j]=j^shu[k-2];
            }
        }
    }
    for(int j=0;j<=(1<<16)-1;j++)
        if(f[n][j]<Min)
        {
            int k=j;Min=f[n][j];
            for(int i=n;i>=1;i--){ans[i]=sum[i][k];k=pre[i][k];}
        }
    for(int i=1;i<=n;i++)printf("%d ",ans[i]);
    return 0;
}
View Code

 

posted @ 2017-10-19 22:44  lher  阅读(162)  评论(0编辑  收藏  举报