zoukankan      html  css  js  c++  java
  • 条形码问题 dp+求某个序列在某种排列中的序号的方法

    题目

    条形码是一种由亮条(Light Bar)和暗条(Dark Bar)交替出现且以暗条为起头的符号,每条都占有若干个单位宽。图33-1给出了一个含有4个条的条形码,它延续了1+2+3+1=7单位的宽。

     一般情况下BC(N,K,M)是一个包含所有由K个条,总宽度正好为N个单位,每个条的宽度至为M个单位性质的条形码组成的集合。例如:图33-1的条形码属于BC(7,4,3)而不属于 BC(7,4,2)。 图33-2显示了集合BC(7,4,3)中的所有16个符号,其中1表示暗,0表示亮。图中所示条形码已按字典顺序排列,冒号左边数字为条形码的编号。图33-1的条形码在BC(7,4,3)的编号为4。

    输入格式:

       输入文件Input4.DAT的第一行为N、K、M的值(1≤N,K,M≤33)。第二行为数字S(0≤S≤100),而后的S行中,每行为一个图33-2那样描述的集合BC(N,K,M)中的一个条形码。

    输出格式:

    你的程序应完成任务:
    A、把输出内容写入文件OUPUT4.DAT。第一行是BC(N,K,M)中条形码的个数。
    B、OUPUT.DAT的第二行起的S行中,每一行是输入文件对应条形码的编号;输入与输出数据中同一行相邻两个数之间用空格区分。
     
    首先是求出条形码个数。可以很容易地得出状态转移方程:

    ans(n,k,m)=sum{ans(n-x,k-1,m)}(1<=x<=min(m,n-k+1),km>=n)(n>k)
    ans(k,k,m)=1
    ans(t,k,m)=0(t<k)

    然后是求每个条形码的序号,这个需要一些技巧。

    ***记一下

    举例:1101000,求序号
    l[1]=2,l[2]=1,l[3]=1,l[4]=3
    比其小的有:
    先是l[1]<2的(ans[7-1][3]=7)
    再是l[1]=2,l[2]>1的(ans[7-2-2][2]+ans[7-2-3][2]=2+1=3)
    还有l[1]=2,l[2]=1,l[3]<1的(0)
    还有l[1]=2,l[2]=1,l[3]=1,l[4]>3的(0)
    因此其序号是7+3+0+0-1+1=10
    其他的以此类推

    ***

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    int ans[40][40];
    int n,m,k,num,temp,temp2;
    int l[40];
    int s;
    string s1;
    int ans2;
    int dfs(int n,int k)
    {
        if(k*m<n)
        {
            ans[n][k]=0;
            return 0;
        }
        if(n>k)
        {
            ans[n][k]=0;
            for(int i=1;i<=min(m,n-k+1);i++)
                if(ans[n-i][k-1]==-1)
                    ans[n][k]+=dfs(n-i,k-1);
                else
                    ans[n][k]+=ans[n-i][k-1];
        }
        else if(n==k)
            ans[n][k]=1;
        else ans[n][k]=0;
        return ans[n][k];
    }
    int main()
    {
        int i,p,j,j1;
        memset(ans,-1,sizeof(ans));
        scanf("%d%d%d",&n,&k,&m);
        printf("%d
    ",dfs(n,k));
        scanf("%d",&s);
        for(i=1;i<=s;i++)
        {
            ans2=0;
            cin>>s1;
            p=0;
            num=0;
            for(j=0;j<s1.length()-1;j++)
            {
                p++;
                if(s1[j]!=s1[j+1])
                {
                    l[++num]=p;
                    p=0;
                }
            }
            l[++num]=p+1;
            temp=n;
            temp2=k;
            for(j=1;j<=num;j++)
            {
                temp2--;
                if(j%2==1)
                    for(j1=1;j1<l[j];j1++)
                    {
                        ans2+=ans[temp-j1][temp2];
                    }
                else
                    for(j1=l[j]+1;temp-j1>=temp2&&j1<=m;j1++)
                    {
                        ans2+=ans[temp-j1][temp2];
                    }
                temp-=l[j];
            }
            printf("%d
    ",ans2);
            //1101110
            //1100
        }
        return 0;
    }

    再贴一段其他人的代码

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,k,m,s,a[100000];
    long long f[1000][1000];
    char c[100];
    int main(){
        scanf("%d%d%d%d",&n,&k,&m,&s);
        f[0][0]=1;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=k;j++)
        for(int l=1;l<=min(m,i);l++)
        f[i][j]=f[i][j]+f[i-l][j-1];
        printf("%d
    ",f[n][k]);
        for(int i=1;i<=s;i++){
            scanf("%s",c);c[n]=(c[n-1]-'0')^1+'0';
            int ans=0,sum1=0,sum2=0,num=0,p=1;
            for(int j=1;j<=n;j++)if(c[j]!=c[j-1])a[++num]=p,p=1;else p++;
            for(int j=1;j<=num;j++){
                sum2++;
                if(j&1)for(int l=a[j]-1;l>=1;l--)ans+=f[n-sum1-l][k-sum2];
                else for(int l=a[j]+1;l<=m;l++)ans+=f[n-sum1-l][k-sum2];
                sum1+=a[j];
            }
            printf("%d
    ",ans);
        }
    } 
    View Code

    再贴一段讲解,并没有什么卵用:

    动态规划和搜索有着千丝万缕的关系,我们先来看一个例子:

    一、问题描述

    “条形码”是一种由亮条(light bar)和暗条(dark bar)交替出现,且以暗条起头的符号。每个“条”(bar)都是若干个单位宽。图1给出了一个含4个“条”的“条形码”,它延续了1+2+3+1=7个单位宽。

    一般情况下,BC(n,k,m)是一个包含所有由:k个“条”,总宽度正好为n个单位,每个“条”的宽度至多为m个单位的性质的“条形码”组成的集合。例如:图1的条形码属于BC(7,4,3),而不属于BC(7,4,2)。

    0: 1000100 | 8: 1100100

    1: 1000110 | 9: 1100110

    2: 1001000 | 10: 1101000

    3: 1001100

    | 11: 1101100

    4: 1001110 | 12: 1101110

    5: 1011000 | 13: 1110010

    6: 1011100 | 14: 1110100

    7: 1100010 | 15: 1110110

    图1 图2

    图2显示了集合BC(7,4,3)中的所有16个符号。1表示暗,0表示亮。图中的条形码已按字典顺序排列。冒号左边的数字为“条形码”的编号。图1中条形码的在BC(7,4,3)中的编号为4。

    输入:输入文件的第一行为数字n,k,m(1<=n,k,m<=30)。第二行为数字s (s<=100)。而后s行中,每行为一个如图1那样描述的集合BC(n,k,m)中的一个“条形码”。

    输出:在输出文件中第一行为BC(n.k,m)中“条形码”的个数。而后s行中每一行为输入文件中对应“条形码”的编号。

    输入输出示例:

    二、分析

    题目有两问。容易看出,计数是求序号的基础,因此我们先解决计数问题。原题只给了一个实例,即条形码。为了能用计算机解决该问题,我们必须先建立一个能够很好描述该问题的数学模型。

    由于条形码是由黑白相间的且以黑色起头的k块组成,每一块最少含有1条,最多含有m条,k块合起来为n条。因此,一个条形码可以由一个k元组(x1,x2,…,xk)表示,且1≤xi≤m,∑(i=1..k)xi=n。相应地,任意一个满足上述条件的k元组唯一表示一个满足条件的条形码。容易证明,所设的k元组与条形码满足一一对应的关系。满足条件的条形码的个数即为所设的k元组的个数。即方程∑(i=1..k)xi=n,1≤xi≤m的整数解的个数。

    最容易想到的是搜索算法1:由于xi的取值范围已经确定,我们可以穷举所有的xi的取值,再检查有多少组解满足∑(i=1..k)xi=n。程序很容易编写,但复杂度却很高,为mk,由于m,k都可能达到30,因此该算法是很低效的。

    搜索算法低效的原因是没有很好的利用∑(i=1..k)xi=n这个约束条件,而只将其作为一个判定条件。最容易想到的改进策略是:如要求方程x1+x2+x3=4,1≤x1,x2,x3≤2的整数解的个数。若x1=1,则方程化为x2+x3=4-x1=4-1=3,若x1=2方程化为x2+x3=2。原方程的整数解的个数,正是方程x2+x3=3,x2+x3=2的整数解的个数的和。这样,我们把含3个未知数的方程的整数解个数的问题化为了若干含2个未知数的方程的整数解个数的问题。以此类推,最终可以化为求含一个未知数的方程的整数解个数的问题。容易得出,方程x=n,1≤x≤m的整数解的个数为:当1≤n≤m时,有1个解,否则,有0个解。

    容易写出搜索算法2:

    Func Count(k,n):LongInt;{∑(i=1..k)xi=n,1≤xi≤m的整数解个数}

    begin

    if k=1 then if 1≤n≤m then Count:=1

    else Count:=0

    else for i:=1 to m do Inc(Count,Count(k-1,n-i)){***}

    end;

    k该程序的时间复杂度仍然为m ,但我们可以通过改进{***}句而将复杂度降低到O(N),

    其中N为Count函数的返回值,即方程的整数解个数。具体方法是通过修改循环语句的起始值和终止值:for i:=MinI to MaxI do Inc(Count,Count(k-1,n-i));其中,MinI=Max{1,n-m*(k-1)},MaxI=Min{m,n-k+1}。而方程的整数解个数可以达到6*10甚至更高。显然,这个程序是不高效的。其原因在于所建立的数学模型的抽象程度不高。

    我们将方程的整数解个数的模型进一步抽象为:k个在1..m之间的数的和为n的方案数。新的模型与方程的整数解个数的模型似乎没有不同,仅仅是将原模型中未知数xi抽象为k个数。但事实上,这个并不大的变化可以很大程度上的优化程序:我们用F(k,n)表示k个在

    1..m之间的数的和为n的方案数,显然有方程式

    F(k,n)=∑(i=1..m)F(k-1,n-i)。

    和初始条件:若i≠0则F(0,i)=0,F(0,0)=1。

    容易写出动态规划程序:

    Proc Count;

    begin

    FillChar (F, Sizeof (F), 0);

    F [0,0]: =1;

    for i:=1 to n do

    for j:=1 to k do

    for p:=1 to m do

    if i>=p then Inc(F[i,j],F[i-p,j-1])

    end;

    动态规划的程序的时间复杂度为O(n*k*m)≤30*30*30=27000。

    动态规划的特点之一是速度快,另一点便是丰富了运算结果。如本题,我们不仅计算出题设条形码的个数,还计算出了所有由i块组成,每一块最少含有1条,最多含有m条,i块一共为j条的条形码的个数(1≤i≤k,1≤j≤n)。而这些信息可以很方便的解决本题的第二问。

    要计算一个条形码的编号,可以先统计在字典顺序中比该条形码小的条形码的个数。这是很容易做到的。具体程序如下:

    Func Index (n, k, p: Integer): LongInt;

    begin

    if k<=1 then Index:=1 else begin

    x:=0;

    if Odd(p) then begin q:=1;Delta:=1 end else begin q:=m;Delta:=-1 end;

    while l[p]<>q do begin

    if n>=q then Inc(x,F[n-q,k-1]);

    Inc (q,Delta)

    end;

    8

    Inc (x, Index (n-q, k-1, p+1));

    Index: =x

    end

    end;

    其中L数组存放的是所读入的条形码的所对应的k元组。如条形码1001110对应的L数组为L[1]=1,L[2]=2,L[3]=3,L[4]=1。该过程的时间复杂度为O(n*k)≤30*30=900

    再得到了完美的解答之后,我们再来看看前面的搜索算法。容易看出,搜索算法2可改为:

    Func Count(k,n):LongInt;{∑(i=1..k)xi=n,1≤xi≤m的整数解个数}

    begin

    if F[k,n]=NULL then

    if k=0 then F[k,n]:=1

    else for i:= Max{1,n-m*(k-1)} to Min{m,n-k+1} do

    Inc (F [k, n], Count (k-1, n-i));

    Count: =F [k, n]

    end;

    改进后的算法即为动态规划的递归式写法。此算法可以看作是动态规划算法的改进。因为对决策变量i的初始值和终止值的修正,使得其计算次数较递推写法的动态规划更少。也就是说,我们通过对搜索的算法的改进,得到了同样的动态规划的算法。

    那么动态规划与搜索的关系究竟是什么呢,我们再来看另外一个问题:

    序关系计数问题 (福建试题)

    一、问题描述

    用关系‘<’和‘=’将3个数A、B和C依次排列有13种不同的关系:

    A<B<C, A<B=C, A<C<B, A=B<C, A=B=C, A=C<B,

    B<A<C, B<A=C, B<C<A, B=C<A,

    C<A<B, C<A=B, C<B<A。

    编程求出N个数依序排列时有多少种关系。

    二、分析

    <1>.枚举出所有的序关系表达式

    我们可以采用回溯法枚举出所有的序关系表达式。N个数的序关系表达式,是通过N个大写字母和连接各字母的N-1个关系符号构成。依次枚举每个位置上的大写字母和关系符号,直到确定一个序关系表达式为止。

    由于类似于‘A=B’和‘B=A’的序关系表达式是等价的,为此,规定等号前面的大写字母在ASCII表中的序号,必须比等号后面的字母序号小。基于这个思想,我们很容易写出解这道题目的回溯算法。

    算法1,计算N个数的序关系数。

    procedure Count(Step,First,Can);

    {Step表示当前确定第Step个大写字母;

    First表示当前大写字母可能取到的最小值;

    Can是一个集合,集合中的元素是还可以使用的大写字母}

    begin

    if Step=N then begin{确定最后一个字母}

    for i:=First to N do if i in Can then Inc(Total);{Total为统计的结果}

    Exit

    end;

    for i:=First to N do{枚举当前的大写字母}

    if i in Can then begin{i可以使用}

    Count(Step+1,i+1,Can-[i]);{添等号}

    Count(Step+1,1,Can-[i]){添小于号}

    end

    end;

    调用Count(1,1,[1..N])后,Total的值就是结果。该算法的时间复杂度是O(N!)

    图4-8 N=3时的解答树

    <2>.粗略利用信息,优化算法1

    算法1中存在大量冗余运算。如图4-8,三个方框内子树的形态是完全一样的。一旦我们知道了其中某一个方框内所产生的序关系数,就可以利用这个信息,直接得到另两个方框内将要产生的序关系数。

    显然,在枚举的过程中,若已经确定了前k个数,并且下一个关系符号是小于号,这时所能产生的序关系数就是剩下的N-k个数所能产生的序关系数。

    设i个数共有F[i]种不同的序关系,那么,由上面的讨论可知,在算法1中,调用一次Count(Step+1,1,Can-[i])之后,Total的增量应该是F[N-Step]。这个值可以在第一次调用Count(Step+1,1,Can-[i])时求出。而一旦知道了F[N-Step]的值,就可以用Total:=Total+F[N-Step] 代替调用Count(Step+1,1,Can-[i])。这样,我们可以得到改进后的算法1-2。

    算法2,计算N个数的序关系数。

    procedure Count(Step,First,Can);

    {Step,First,Can的含义同算法1}

    begin

    if Step=N then begin{确定最后一个字母}

    for i:=First to N do if i in Can then Inc(Total);{Total为统计的结果}

    Exit

    end;

    for i:=First to N do{枚举当前的大写字母

    }

  • 相关阅读:
    蒲公英
    大神-YY
    iOS开发精选知识点讲解 - 视频等 iOSStrongDemo是由@李刚维护,总结一些iOS开发精选知识点。每一个知识点都有相应的测试代码,非常适合iOS初学者。
    iOS开发UI篇—懒加载
    iOS开发UI篇—UITableviewcell的性能优化和缓存机制
    iOS开发UI篇—UITableview控件基本使用
    iOS开发UI篇—UITableview控件简单介绍
    iOS — Autolayout之Masonry解读
    iOS开发UI篇—多控制器和导航控制器简单介绍
    iOS开发网络篇—数据缓存
  • 原文地址:https://www.cnblogs.com/hehe54321/p/7324743.html
Copyright © 2011-2022 走看看