题目大意:
把1到N的数按顺序写出来,在每两个数位之间交替地添加+-号,求最后的和。如下:
1,2,3,4,5,6,7,8,9,10,11,12→1-2+3-4+5-6+7-8+9-1+0-1+1-1+2=5
题解
两个dp数组
我们需要两个dp数组来帮助我们解决问题。
dp
dp[i][j]
表示i位数,第i位为j的数,开头为正,写成题目形式的总和。
如 dp[2][2] = (2-0)+(2-1)+(2-2)+(2-3)+(2-4)+(2-5)+(2-6)+(2-7)+(2-8)+(2-9)
dp[3][1] = (1-0+0)+(1-0+1)+(1-0+2)+(1-0+3)+...+(1-9+8)+(1-9+9)
所以,当前状态等于j减去上一位状态(当前为正,上一位就为负),而第i位的j就要加10i−1次。
dp2
我们还需要一个dp2[i]
,表示前i位数所有转换为题目形式的答案。
如dp2[2] = 1-2+3-4+5-6+7-8+9-1+0-1+1-1+2-...-9+6-9+7-9+8-9+9
所以dp2[i]等于dp2[i-1]加上刚好i位的数计算的结果。
如果i为偶数,则i位数的最高位符号一定为-,只需要减去上所有dp[i][k]即可。注意:k必须从1开始,为0不叫真正的“刚好i位”。
如果i为奇数,i位数+-一定是交替的,像-1+0-0+1-0+1-1+0-2+1-0+3…
可以发现,除个位以外的数位,都在偶数次加减后抵消掉,只有个位可以对答案有贡献,没两个i位数可以对答案贡献1。(如102与103,-1+0-2+1-0+3=1)
i位数一共有10i−10i−1个,所以总贡献为10i−10i−12
利用两个dp数组求解
要分两种情况,一种是偶数个数位,一种是奇数个数位。
偶数个数位
因为偶数个数位,每一个数位的符号是固定的,最高位一定是-,然后第二位为+…
从高位到低位枚举数位i,枚举每一个小于当前位数字的数字j,用f表示当前的符号,tmp记录前面已经枚举过的数位计算出的结果。ans+=dp[i][j]×f+tmp×10i−1,即加上i位数开头为j的全部结果,添上f符号,再加上已经枚举过的大于i的数位的结果,计算10i−1次。
如果i为最高位,j为0,因为不能有前导0,则此时ans+=dp2[i−1],即把位数小于i的结果全部加上。
奇数个数位
用同样的方法枚举,如果i为最高位,j为0的情况同上ans+=dp2[i−1],其它情况如下:
我们假设最高位符号为+,然后-…用tmp同样的记录前面的结果,f记录符号。因为数字交替加减,个位以上的都将被抵消,理由与计算dp2时相同。
所以当i>1时,个位的贡献为10i−12,即ans+=10i−12(因为枚举的i,j表示有i位,第i位为j,此时包含10i−1个数)
当i=1时,可直接手动计算,len表示数的位数,如果len=1(mod2)&&(j=1(mod2)),则最高位符号为+,ans+=tmp+j,否则为-,ans+=−tmp−j
(看不懂的看代码)
代码:
#include<cstdio>
#include<cstring>
const int MAXD=17,MAXN=10;
long long dp[MAXD][MAXN],dp2[MAXD],pow10[MAXD]={1,10};
void Init()
{
for(int j=0;j<MAXN;j++)
dp[1][j]=j;
dp2[1]=5;
pow10[0]=1;
for(int i=2;i<MAXD;i++)
{
pow10[i]=pow10[i-1]*10LL;
dp2[i]+=dp2[i-1];
if(i%2==1)
dp2[i]+=(pow10[i]-pow10[i-1])/2;
for(int j=0;j<MAXN;j++)
{
for(int k=0;k<MAXN;k++)
dp[i][j]+=1LL*j*pow10[i-2]-dp[i-1][k];
if(j>0&&i%2==0)
dp2[i]-=dp[i][j];
}
}
}
long long solve(char num[],int len)
{
long long ret=0;
int tmp=0,f;
if(len%2==0)
{
f=-1;
for(int i=len;i>0;i--)
{
for(int j=0;j<num[len-i]-'0';j++)
if(i==len&&j==0)
ret+=dp2[i-1];
else
ret+=dp[i][j]*f+tmp*pow10[i-1];
tmp+=(num[len-i]-'0')*f;
if(f==-1)f=1;
else f=-1;
}
ret+=tmp;
}
else
{
f=1;
for(int i=len;i>0;i--)
{
for(int j=0;j<num[len-i]-'0';j++)
if(i==len&&j==0)
ret+=dp2[i-1];
else
{
if(i>1)
ret+=pow10[i-1]/2;
else
{
if(len%2==1&&j%2==1)
ret+=tmp+j;
else
ret+=-tmp-j;
}
}
tmp+=(num[len-i]-'0')*f;
if(f==-1)f=1;
else f=-1;
}
if(len%2==1&&(num[len-1]-'0')%2==1)
ret+=tmp;
else
ret+=-tmp;
}
return ret;
}
int main()
{
Init();
char N[MAXD]="";
int len=0;
while(1)
{
scanf("%s",N);
len=strlen(N);
if(len==1&&N[0]=='0')
break;
printf("%lld\n",solve(N,len));
}
return 0;
}