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; }
状压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<<","; } }
状压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; }