poj 1608 dp(Banal Ticket)

题意:给定一个长度为2n的串,每个字符或者是数字,或者是?。?的位置也应该是一个字符表示相应的数字,但是看不清了。问把?还原成数字,使得前n个数字的乘积等于后n个数字的乘积的还原方法有多少种,不等于的又有多少种?

输入:

2

2??3

输出:

4

96

样例解释:两个问号填数字能符合的四种是:2003、2323、2643、2963。

思路:做过的最难的一道dp,同学讲给我的思路。

1.首先处理乘积为0的情况。这个相对比较简单,只需用分“有已知的0”和“没有已知的0”两种情况讨论,

用组合数学的知识就可以很快计算出来,略过。

2.接下来考虑乘积不为0的情况。(注意此情况下一定不会有已知的0。)

考虑到最终乘积的质因数只有2,3,5,7四种,因此,

用F[i][a][b][c][d]表示:用i个"?"组成2^a*3^b*5^c*7^d的方案数。

在转移方程中从1到9枚举最后一个"?"的取值x,假设x=2^(a_x)*3^(b_x)*5^(c_x)*7^(d_x),有

F[i][a][b][c][d]=sum{F[i-1][a-a_x][b-a_x][c-c_x][d-d_x]}

初值F[0][0][0][0][0]=1,其他都是0。

假设:

前半部分有ml个问号,且所有已知数字的乘积是al,bl,cl,dl;

后半部分有mr个问号,且所有已知数字的乘积是ar,br,cr,dr;

则最终答案为sum{F[ml][a-al][b-bl][c-cl][d-dl]*F[mr][a-ar][b-br][c-cr][d-dr]}+(乘积为0的方案),其中a,b,c,d遍历了所有可能的取值。

时间复杂度:

乘积为0的部分可在O(n)内解决

动态规划部分由于a<=3n,b<=2n,c<=n,d<=n,所以可在O(n^5)内解决

实现难点:

1. 最终答案需要高精度,不过F用long long就可以了。

2. F的空间复杂度较高,可以使用滚动数组或者类似背包问题的那种办法减少空间开销。

写的时候WA和TLE了几次,对着官网的测试数据debug了45个小时才过,够酸爽~~

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 18
#define A N*3+2
#define B N*2+2
#define C N+2
#define D N+2
char str[(N<<1)+5];
int id,n,nl,nr;
long long ml,mr;
int res[(N<<1)+10],len;
long long dp[2][A][B][C][D],dpt[A][B][C][D];
int fac[13][4] = {//fac[1...9]表示前9个数字,fac[11]和fac[12]表示左边(右边)已经填好数字的积
    {0,0,0,0},
    {0,0,0,0},
    {1,0,0,0},
    {0,1,0,0},
    {2,0,0,0},
    {0,0,1,0},
    {1,1,0,0},
    {0,0,0,1},
    {3,0,0,0},
    {0,2,0,0}};
int base[4] = {2,3,5,7};
void jinwei(){//处理进位
    int i,j;
    for(i = 0;i<=nl+nr;i++){
        j = res[i]/10;
        res[i] %= 10;
        res[i+1] += j;
    }
    len = nl+nr;
    while(len >0 && !res[len])
        len--;
}
void add(long long a,long long b){//将a*b累加到输出数组,因为a*b可能溢出long long
    int i,j;
    if(!a || !b)
        return;
    for(i = 0;a;i++){
        long long num = a%10;
        long long x = b*num;
        for(j = i;x;j++){
            res[j] += x%10;
            x /= 10;
        }
        a /= 10;
    }
}
long long pow(int x,int y){//实现幂函数,直接用math库的math会丢失精度
    long long sum = 1;
    while(y--)
        sum *= x;
    return sum;
}
int zero_case(){
    int i,j;
    long long t;
    if(!ml && !mr){             //如果两边都出现了0,最简单的情形
        res[len = nl+nr] = 1;
        return 1;
    }
    else if(ml && mr){          //如果两边都没出现
        add(pow(10, nl)-pow(9, nl),pow(10, nr)-pow(9, nr));//这是两边乘积为0的数量
        return 3;
    }
    else{                       //只有一边出现0
        if(!ml){
            t = pow(10, nr) - pow(9, nr);
            i = nl;
        }else{
            t = pow(10, nl) - pow(9, nl);
            i = nr;
        }
        for(j = i;t;j++){
            res[j] = t%10;
            t /= 10;
        }
        len = j-1;
        return 2;
    }
    return 0;
}
void change(){                  //算出左边(右边)已有的积的幂表示
    int i;
    for(i = 0;i<4;i++){
        while(ml && ml%base[i] == 0){
            fac[11][i]++;
            ml /= base[i];
        }
        while(mr && mr%base[i] == 0){
            fac[12][i]++;
            mr /= base[i];
        }
    }
}
int check(int a,int b,int c,int d,int i){
    return (a-fac[i][0]>=0)&&(b-fac[i][1]>=0)&&(c-fac[i][2]>=0)&&(d-fac[i][3]>=0);
}
int solve(int n){
    int i,j,p,q=0,a,b,c,d,need;
    memset(dp, 0, sizeof(dp));
    dp[0][0][0][0][0] = 1;
    memcpy(dpt, dp[0], sizeof(dp[0]));
    for(i = 1;i<=n;i++){
        q = i&1;            //滚动数组
        p = !q;
        for(d = 0;d<=i;d++)
            for(c = 0;c <= i-d;c++)
                for(b = 0;(need=(b+1)/2+d+c)<=i;b++)//注意判断条件,算作剪枝,不写则TLE
                    for(a = 0;(((b&1)?(a-1):a)+2)/3<=i-need;a++)
                        for(j = 1;j<=9;j++)
                            if(check(a,b,c,d,j))
                                dp[q][a][b][c][d] += dp[p][a-fac[j][0]][b-fac[j][1]][c-fac[j][2]][d-fac[j][3]];
        if(i == min(nl,nr))     //记录一下
            memcpy(dpt, dp[q], sizeof(dp[q]));
        memset(dp[p], 0, sizeof(dp[p]));//注意memset的写法
    }
    return q;
}
void updateres(int id){
    int a,b,c,d,need;
    for(d = 0;d<=n;d++)
        for(c = 0;c <= n-d;c++)
            for(b = 0;(need=(b+1)/2+d+c)<=n;b++)
                for(a = 0;(((b&1)?(a-1):a)+2)/3<=n-need;a++)
                    if(check(a, b, c, d, 11) && check(a, b, c, d, 12)){
                        if(nl <= nr)
                            add(dpt[a-fac[11][0]][b-fac[11][1]][c-fac[11][2]][d-fac[11][3]],dp[id][a-fac[12][0]][b-fac[12][1]][c-fac[12][2]][d-fac[12][3]]);
                        else
                            add(dp[id][a-fac[11][0]][b-fac[11][1]][c-fac[11][2]][d-fac[11][3]],dpt[a-fac[12][0]][b-fac[12][1]][c-fac[12][2]][d-fac[12][3]]);
                    }
    jinwei();
}
int main(){
    int i,j;
    nl = nr = len = 0;
    ml = mr = 1;
    memset(res, 0, sizeof(res));
    scanf("%d",&n);
    scanf("%s",str);
    for(i = 0;i<n;i++)
        if(str[i] == '?')
            nl++;
        else
            ml *= str[i]-'0';
    for(i = n;i<n*2;i++)
        if(str[i] == '?')
            nr++;
        else
            mr *= str[i]-'0';
    if(nl+nr == 0){             //没有问号的情况
        if(ml == mr)
            printf("1\n0");
        else
            printf("0\n1");
        return 0;
    }
    j = zero_case();
    if(j == 1){                 //两边都出现了数字0
        for(i = len;i>=0;i--)
            printf("%d",res[i]);
        printf("\n0");
        return 0;
    }
    else if(j == 3){            //两边都没出现数字0
        change();
        id = solve(max(nl,nr));
        updateres(id);
    }
    for(i = len;i>=0;i--)
        printf("%d",res[i]);
    putchar('\n');
    for(i = nl+nr-1;i>=0;i--)   //有所有的情况减去符合的情况就是不符合的情况
        res[i] = 9-res[i];
    res[nl+nr] = 0;
    add(1,1);
    jinwei();
    for(i = len;i>=0;i--)
        printf("%d",res[i]);
    return 0;
}
时间: 2024-11-01 11:23:38

poj 1608 dp(Banal Ticket)的相关文章

HDU 1087 &amp;&amp; POJ 2533(DP,最长上升子序列).

~~~~ 两道题的意思差不多,HDU上是求最长上升子序列的和,而POJ上就的是其长度. 貌似还有用二分写的nlogn的算法,不过这俩题n^2就可以过嘛.. ~~~~ 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1087 http://poj.org/problem?id=2533 ~~~~ HDU1087: #include<cstdio> #include<cstring> #include<algorithm> #

POJ 3670 &amp;&amp; POJ 3671 (dp)

最长不下降子序列的应用嘛.两题都是一样的. POJ 3670:求给定序列按递增或递减排列时,所需改变的最小的数字的数目. POJ 3671:求给定序列按递增排列时,所需改变的最小的数字的数目. 思路就是求最长不下降子序列,然后剩下的就是需要改变的字母. 最长不下降子序列:(我之前有写过,不懂请戳)http://blog.csdn.net/darwin_/article/details/38360997 POJ 3670: #include<cstdio> #include<cstring

poj 3783 DP 2个鸡蛋扔100层楼的加强版

http://poj.org/problem?id=3783 估计23号之后的排位赛之后我就要退役了,这之前最后再做5天ACM 今天的排位很惨,上次排位也很惨......这道题原来算法课老师讲过,模模糊糊记得方程,但是边界处理有问题, dp[i][j]=min(1+max(dp[k-1][j-1],dp[i-k][j]))   k=1 to 楼数 dp[i][j]:i层楼扔,手里有j个ball 的次数 边界两个:1.dp[1][i]=1,第一层无论手里有几个鸡蛋都是1次,2.dp[i][1]=i

POJ 3034 DP

打地鼠游戏中,你有一个锤子,每一秒钟你可以拿着锤子移动d个单位的距离,必须是直线,掠过的鼠洞中露出的地鼠都会被锤打至,而事先知道从开始时各时间段内出现在老鼠的数量和位置,问题是从游戏开始至结束时,你最多能打到多少只地鼠,开始时锤子可以在任何位置. 题目有个陷阱, 只是说Moles出现的坐标为正,但没说hammer移动的位置要为正,可以为"any position" 所以锤子可以移出矩阵,再从矩阵外一点移进来 例: 20 5 4 1 0 1 0 1 1 0 5 2 1 6 2 0 0 0

POJ 4968 DP||记忆化搜索

给出N个人的平局分X 根据GPA规则计算可能的最高平均GPA和最低平均GPA 可以DP预处理出来所有结果  或者记忆化搜索 DP: #include "stdio.h" #include "string.h" int inf=100000000; double a[11][1100],b[11][1100]; double Max(double a,double b) { if (a<b) return b; else return a; } double M

POJ 2029 DP || 暴力

在大矩形中找一个小矩形 使小矩形包含的*最多 暴力或者DP  水题 暴力: #include "stdio.h" #include "string.h" int main() { int n,m,w,i,s,t,j,k,l,ans,sum,x,y; int map[101][101]; while (scanf("%d",&w)!=EOF) { if(w==0) break; scanf("%d%d",&n,&

POJ 1160 DP

用数轴描述一条高速公路,有V个村庄,每一个村庄坐落在数轴的某个点上,需要选择P个村庄在其中建立邮局,要求每个村庄到最近邮局的距离和最小. cost记录每两个村庄之间建一个邮局的最小代价. 转移方程: dp[j][i]=Min(dp[j][i],dp[k][i-1]+cost[k+1][j]) 前j个村庄建i个邮局=前k个村庄建I-1个邮局+第k+1村庄到第j村庄建一个邮局的代价 #include "stdio.h" #include "string.h" int i

POJ 4939 DP

给你一段长为n的路,每一个单位长度可以放一种塔,这里有三种塔. 红1)对正在经过这座塔的敌人进行 x 每秒伤害的攻击 绿2)对于已经经过这塔的敌人进行y每秒的伤害攻击 蓝3)对已经经过这个塔的敌人放慢速度,使得原先为 经过一个单位时间为  t的速度变为  t+z 对于红塔一定是放在最后面,然后对于前面的绿塔和蓝塔DP dp[i][j]=Max(dp[i-1][j-1]+y*(i-j)*(t+(j-1)*z),dp[i-1][j]+y*(i-j-1)*(t+j*z)); 前i个放j个蓝塔=Max(

poj 2559 DP

两种解法. 我想到的是最大的矩形,中间一定有个最矮的某个单位矩形,所以用两个数组记录histogram[i]左右两边第一个比它小的单位矩形的序号leftLowerId[i]和rightLowerId[i],那么对于histogram[i],它自己的最大矩形面积就是(rightLowerId[i] - leftLowerId[i] - 1) *  histogram[i]. 这里找leftLowerId和rightLowerId的时候用DP加速.以rightLowerId为例,找到右边比histo