题目链接:Click here~~
题意:
给一个数列{ai},有两种操作:1、选取数列中的一个数并给它加1。
2、在数列中添加一个数字1。
问如何进行 m 次操作,使数列各项乘积 p 的值最大。
解题思路:
由于数列中有正负数和0,所以导致情况很多,让我们逐个来考虑。
1、负数个数为奇数。(p<=0)
如果数列中不存在0,那么乘积 p 一定为负。不难想到,最好的方法是选绝对值最小的那个负数,把它加到0,使 p 变成 0。
此时进入情况2。
2、数列中含有0。(p=0)
要想把p上升成正数,显然需要把所有的0变成1。
然后进入情况3。
3、负数个数为偶数。(p>0)
首先,肯定不动负数,因为给负数加1,会使其绝对值减小,即 p 会减小。
而且,如果进行操作1,我选的那个数一定是当前数列中最小的正数,显然这样会使 p 变得更大。
如果进行操作2,在不是毫无办法的情况下,我绝对不会傻傻的只添加个1,因为那样对 p 没有贡献。
也就是说,我会一直把它添加到x(x>=2),即操作2一定会和操作1结合起来使用,为了方便,我们把这些结合起来的操作都看做是操作2。
另外,我不会选择进行操作2后再去进行操作1。因为操作2新添加的数,不会超过之前数列中最小的正数。
下面,对于那些正数,比较操作1 和操作2 哪个更划算。
首先,当数列中含有1的时候,一定选操作1。因为把1个1变成2,只需要消耗1步操作就可以达到和消耗2步操作2 相同的效果。
于是我们考虑不含1的数列,比如{a,b,c,d}(按从小到大的顺序排列,a>=2),操作步数为 m。
当m=1 时:操作 1 更佳。
当m=2 时:①a=b。△1 = (2b+1)*cd,△2 = b^2*cd。令△2 < △1,得 b <= 2。又a=b,得唯一解 a = b = 2。
②a<b。△1 = 2b*cd,△2 = a*b*cd。由于a >= 2,所以△2 >= △1。且当 a=2 时,等号成立。
综上,当 a=2 时,操作1 更佳,否则 操作2 更佳。
推论:把所有的2变成3不会使结果更差。
当m>2时:不会证明了。。。囧。。。
最后还剩下的操作就靠下面这个结论了:
——————————————————————————————分割线——————————————————————————————
Q:将一个正整数 x 分解成 k 个正数 ai 相加的形式,问如何才能使得到的各个 ai 的乘积 S 最大(i:1~k , k任意)。
结论1:ai > 1。
如果ai = 1,显然它对乘积 S 没有贡献,不如把它随便加到其他的一个数上,得到的结果一定更大。
结论2:ai < 4。
第一种证明方法:
反证法,假设有某个ai>=4,那么如果把 ai 分成 2+(ai-2) 的形式,得到的乘积 2*ai-4 = ai + (ai-4) >= ai,肯定不会使结果更差。
第二种证明方法:
设任意正整数a、b,考虑什么时候分解会使结果更大,令 a*b >= a+b,即 (a-1)*(b-1) >= 1,可推出 a>=2,b>=2,且当 a=b=2 时等号成立。
所以如果有 ai>=4,那么一定可以找到那样的整数a、b,使得 a*b >= a+b,也不会使结果更差。
结论3:ai 中 2 的个数不超过 2 个。
反证法,假设 2 的个数大于等于3个,那么 3个2 不如 2个3 得到的乘积大。
综上,若 x = 3*k ,ans = 3^k;
若 x = 3*k + 1,ans = 3^(k-1) * 2*2;
若 x = 3*k + 2,ans = 3^k * 2。
——————————————————————————————分割线——————————————————————————————
#include <vector> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; typedef __int64 LL; const int mod = 1000000007; int mypow(int a,LL n) { int ans = 1; for(LL t=a;n;n>>=1) { if(n&1) ans = (ans*t) % mod; t = (t*t) % mod; } return ans; } int main() { // freopen("in.ads","r",stdin); // freopen("ans.ads","w",stdout); int z,n,x,ncase = 0; vector<int> NV,PV; scanf("%d",&z); LL m,cnt[4]; while(z--) { memset(cnt,0,sizeof(cnt)); scanf("%d%I64d",&n,&m); bool neg = false; int minPos = 10001,pid; int maxNeg =-10001,nid; NV.clear(); PV.clear(); while(n--) { scanf("%d",&x); if(x >= 0) { if(x < 4) { ++cnt[x]; continue; } PV.push_back(x); if(x < minPos) minPos = x , pid = PV.size()-1; } else { NV.push_back(x); if(x > maxNeg) maxNeg = x , nid = NV.size()-1; neg ^= 1; } } if(neg) //把最大的负数加成0 { if(NV[nid]+m >= 0) { m += NV[nid]; cnt[0]++; NV.erase(NV.begin()+nid); } else { NV[nid] += m; m = 0; } } if(m >= cnt[0]) //把0加成1 { m -= cnt[0]; cnt[1] += cnt[0]; cnt[0] = 0; if(m <= cnt[1]) //把1加成2 { cnt[2] += m; } else { m -= cnt[1]; cnt[2] += cnt[1]; if(m <= cnt[2]) //把2加成3 { cnt[2] -= m; cnt[3] += m; } else { m -= cnt[2]; cnt[3] += cnt[2]; cnt[2] = 0; if(m == 1) { if(cnt[3]) cnt[3]-- , PV.push_back(4); else if(!PV.empty()) PV[pid]++; } else { if(m%3 == 0) { cnt[3] += m/3; } else if(m%3 == 1) { cnt[3] += m/3-1; cnt[2] += 2; } else { cnt[3] += m/3; cnt[2] += 1; } } } } } LL ans = 1; if(!cnt[0]) { for(int i=2;i<=3;i++) ans = (ans*mypow(i,cnt[i])) % mod; for(int i=0;i<NV.size();i++) ans = ans*NV[i] % mod; for(int i=0;i<PV.size();i++) ans = ans*PV[i] % mod; } printf("Case %d: %I64d\n",++ncase,cnt[0] ? 0 : ans); } return 0; }