http://www.codeforces.com/problemset/problem/283/D
Cows and Cool Sequences
这个题挺好的,纠结了好久,看了几个人的报告,终于理解了,但还是wa了4次。梳理一下思路。
给一个字符串,a1 a1 a3 a4 ......
要是an 可以用a(n+1)连续个数的和表示,问最少需要改变多少个数字。
这个题的收获是:
1:遇到这种问要改变多少个能达到某种状态的,应该多数就是DP了,看看某一位能由哪些状态转换过来
2:两个变量之间的关系不好表达的时候,要用数学手段转换成有限中情况。
针对这个题,如何DP? 我们设pd[i]表示从第0个数字到第i个数字这一串是一个cool的串,并且第i个数字没有变化,所需要的最小变化量。我们的串从0开始到n-1。在原串的最后加一个1,即第n-1个数字可以由1个连续的数字的和表示,显然就是他自己。那么dp[n]就是第n个数字之前的串已经很cool了,而第n个数字没有变化需要的最少变化。
状态如何转移?
假设现在要求dp[i],那么它的值可以由dp[0],dp[1],.......dp[i-2],dp[i-1]得来。假设是从dp[j]转化来的,也就是把i和j之间的i-j-1个数变了,使这一串都是cool的,那么dp[i]=min(dp[i],dp[j]+i-j-1)
但是,并不是所有的dp[j]都能转化到dp[i],我们需要推导他们的关系。
假设x能由y个连续的数字之和表示,则,s是任意数。
当y是奇数的时候,明显x只要是y的任意倍就可以表示成y个连续数的和。
当y是偶数的时候,,2x是y的奇数倍,这个不太好看。我们知道任意的数都可以表示成某些质数的幂的和,那么2x是y的奇数倍,也就是x分解后比y分解后的2正好少一个,并且剩余部分是正整数倍(比如是奇数)。我们把每个数分成两部分,B是它约数2的个数,L存放剩下的部分,则x能由y个连续的数字之和表示可以看作:
当y是奇数的时候,x.L%y.L==0
当y是偶数的时候,x.L%y.L==0&&x.B+1==y.B
以上是两个相邻的数的关系,而从dp[j]转化到dp[i]很简单,依然假设是x,y,但是他们不一定相邻,如果x.L%y.L==0肯定不行,如果x.L%y.L==0,那么看y,如果y是奇数,即y.B==0,那么可以,比如x是14,y是7,那么可以是14 ,14 ,7 ,7,7,7,很随意了。如果y是偶数,即y.B不是0,那么前一项的B一定是y.B-1,如果x,y下标相差和B相差相同,则可以转换,比如2,4,8,16,如果y.B比两者的下标差要小的话(这这里wa了3次。另一次是万恶的long long),那么就无压力转换了,比如10,5,5,5,10,20,从20到5的时候B值降为0,但是这是这个数就是奇数了,前面的数又是它的倍数,随便填都可以了。
问题解决,dp即可,可能没太说明白,要反复琢磨,反复看,我也是看别人的看明白的。
贴代码:
#include "iostream"
#include "cstdio"
#include "cstring"
#include "cmath"
#include "stdlib.h"
#include "algorithm"
using namespace std;
#define MAXN 5010
long long str[MAXN];
int B[MAXN],dp[MAXN];
int n;
bool ok(int j,int i)
{
if(str[j]%str[i]!=0)
return false;
if(B[i]==0)
return true;
else
{
if(i-j==B[i]-B[j])
return true;
if(B[i]<i-j)
return true;
}
return false;
}
int main()
{
scanf("%d",&n);
memset(B,0,sizeof(B));
for(int i=0; i<n; i++)
{
scanf("%I64d",&str[i]);
while(str[i]%2==0)
{
B[i]++;
str[i]/=2;
}
}
str[n]=1;
dp[0]=0;
for(int i=1; i<=n; i++)
{
dp[i]=i;
for(int j=0; j<i; j++)
if(ok(j,i))
dp[i]=min(dp[i],dp[j]+i-j-1);
}
printf("%d\n",dp[n]);
return 0;
}
水平有限,众神轻喷。