混了一天就做了几个水题,果然我真是又狗又懒。
题意:
给定正整数n,要求用最少的操作次数把序列1, 2, 3, 4, ..., n中的所有数都变为0。每次操作可以从序列中选择一个或多个整数,同时减去一个相同的正整数。
Input:
多组数据输入,每组仅包含一个正整数n(n<=10^9)。输入结束标志为EOF。
Output:
对于每组数据,输出最少操作次数。
一开始有点dp的感觉,然后写了一个应该是正确的递推式,但是时间复杂度是O(N^2)级别的,试了几组数据感觉二分来看比较合算,没有反例就写了,然后过了。
虽说我不能证出来二分是对的,但是可以写意地理解一下。
首先,把数组分成两部分,先处理其中大的一部分使得下一次操作可以让原来大的那部分带着后面小的那一部分,这种思路很吸引人,因为这样就相当于前面那部分不用处理(因为它们在你之后递归处理原来大的那部分的时候可以一起被处理)。你要最大限度扩大前面不用处理的部分,就必须保证前面那部分属于处理后的后面那部分,所以平均分最好。
我们再来从略严谨的角度想一下,
如果你采取一种朴素的方法,每次操作的任意两个对象都不相等,那么操作完一次最多消掉一个数(正常人都会这么做),那么实际上你需要n次操作来消掉n个数。
所以减少步骤的方法就是试图一次消掉多个数那么就等价于用不大的代价预处理使得整个数组变成x份,然后对x份等价数组进行一次操作。对于长度为l的一个数组,预处理需要x-1次操作,之后的操作需要f(l/x),所以这个复杂度差不多就是f(l)=x-1+f(l/x),所以f(l)差不多就是(x-1)*logx l?这里x>1,我数学不好你不要骗我。那么x取2肯定是x>1的情况里面最好的。然后x=1的时候这是什么意思?递归都不存在好吗?f(n)不包含递归就是我刚才说的朴素操作方法,复杂度是n,远不如logn。
所以最优策略应该是二分。
以上胡言乱语只是我为了掩饰自己对于算法原理的无知编出来的梦话,很可能错了我还不知道。但是AC了就行了,不爽的话,要不你来打我一顿?
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
int power[34];
int main(){
power[0]=1;
for(int i=1;i<=32;++i) power[i]=power[i-1]*2;
int n;
while(scanf("%d",&n)!=EOF){
int ans;
for(int i=0;i<n;++i)
if(n>=power[i]&&n<power[i+1]){
ans=i;
break;
}
printf("%d\n",ans+1);
}
return 0;
}