EOJ:Ugly Numbers
——给一串数字。要求在数字中(第一个数字前不能添加)添加“+”或者“-”使得最后算出来的结果能够被2,3,5,7其中的一个整除。求这样的添加方案共有多少种。
——DP
——http://202.120.106.94/onlinejudge/problemshow.php?pro_id=426
——————————————————————————————————————————————————————————
拿到题首先的想法是搜索……一看这个复杂度果断就放弃了。
在做DP之前有个很重要的想法就是对结果取余,直接保存余数即可。
对几取余?
直观的想法是对2,3,5,7以及两两,三三,四个的公倍数分别取余,然后用容斥原理来做
但是仔细一想,只需要对210取余即可。最后的结果即为余数是0,2,4,6,8,……和3,6,9,12……5,10,15……7,14,21……这些相加
这样是不会有重复计算的,因为被210除余2和被210除余6的数肯定不是一个数。
——————————————————————————————————————————————————————————
首先因为添加的是加减号而没有乘除,所以不需要考虑优先级的问题,因此可以从左往右直接DP。
——————————————————————————————————————————————————————
之前想到的一个错误的DP方程:
DP[i][j]=Segma{DP[i-1][(j-str[i])%210]+DP[i-1][(j+str[i])%210]+DP[i-1][(j*10+str[i])%210]}
简单说就是一位一位的做,每一位可以添加加号减号或者不添加符号。
问题出在不添加符号,如果第I位之前的结果是31的话,在第I-1位与第I位之间不添加符号,按上面方程就是31*10+STR[I],但实际上31可能是由20+11得来的。那么不填符号应该是20+11*10+STR[I]才对。
——————————————————————————————————————————————————————————————————
正确的转移方程:
DP[I][J]表示字符串从0到I-1为止,这么多个数字添加了符号以后被210除余J的方案数。
X为从K到I-1字符串中的数字的值。
DP[I][J]=Segma{DP[K][J-X]+DP[K][J+X]} 0<=K<I
J-X就是添加了负号,J+X添加的是正号
#include<stdio.h>
#include<string.h>
#define SIZE 45
long long dp[SIZE][210];
bool flag[210];
long long ans;
int len, n;
char str[SIZE];
void solve()
{
int i, j;
long long k, tmp;
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (i = 1; i <= len; i++)
for (j = 0; j < i; j++)
{
tmp = 0;
for (k = j; k < i; k++)
tmp = (tmp * 10 + str[k] - '0') % 210;
for (k = 0; k < 210; k++)
{
dp[i][(k + tmp) % 210] += dp[j][k];
if (j)
dp[i][(k - tmp + 210) % 210] += dp[j][k];
}
}
memset(flag, 0, sizeof(flag));
for (i = 0; i < 210; i += 2)
flag[i] = true;
for (i = 0; i < 210; i += 3)
flag[i] = true;
for (i = 0; i < 210; i += 5)
flag[i] = true;
for (i = 0; i < 210; i += 7)
flag[i] = true;
ans = 0;
for (i = 0; i < 210; i++)
if (flag[i])
ans += dp[len][i];
}
int main()
{
int i;
scanf("%d", &n);
getchar();
for (i = 0; i < n; i++)
{
scanf("%s", str);
len = strlen(str);
solve();
printf("Case #%d: %lld\n", i + 1, ans);
}
return 0;
}
浙公网安备 33010602011771号