homework-02

回答问题

描述在这么多相似的需求面前, 你怎么维护你的设计 (父类/子类/基类, UML, 设计模式,  或者其它方法) 让整个程序的架构不至于崩溃的?

答:处理这种问题显然还是面向过程比较方便,像这种写完了测试通过了就扔的代码没有必要去维护其扩展性,重用性。更别说什么uml父类子类繁琐的东西,用最简单的方法解决问题就好了。如果是web应用或者桌面应用才需要曲考虑这些所谓维护设计吧。

给出你做单元测试/代码覆盖率的最终覆盖率的报告, 用截屏显示你的代码覆盖率

答:不会用VS,在linux下用了lcov来生成覆盖率,并成生了结果html,详见github上的maxsum.c.gcovresu/index.html

阅读 工程师的能力评估和发展 和相关文章, 在完成作业的时候记录自己花费的时间, 并填下表。如果你对有些术语不太清楚,请查看教材和其它资料。如果你认为你不需要做某个步骤, 那就跳过去。 

答:跳过。

你在这个作业中学到了什么?  有什么好的设计值得分享?  感想如何 (太容易 / 太难 / 太无趣)?

答:学了一下Markdown来写文档,表示很不错。重新复习了一下单调队列。对于第三问的感想:太难。

问题重述

这次作业略繁琐。需要在一个程序内支持传递运行参数,支持横向相通,纵向相通,扩展子区域(随意联通)。

问题分析

需要重新构造程序,支持传入参数,有-v-h-a三种,如果是c语言的话,在main()函数内加int argc,char *argv[]就可以引用传入的参数,其中文件名在argv[argc-1]里,程序名在argv[0]里,剩下的就是支持的选项。

只有-v和-h怎么做

-v是上下联通,-h是左右联通。对于环形问题很容易想到的方法就是复制一份接在矩形后边,比如如果需要支持左右联通-h,那么如果原来的矩形是:

5 6 -3 8 -9 2

则可以往右边复制一份自己接上,变成

5 6 -3 8 -9 2 | 5 6 -3 8 -9 2

这样就能够包含所有环形结构,不过有个问题,这样求出来会包含长度大于m的结果,而这是不符合题意的。

为了避免这样的问题,从之前的解法很难再改进。

另外-v只需要限制上下区间的距离就能避免不合题意的情况,所以不用特殊处理。

另一种思路

换一种思路去考虑这个问题就比较容易了,令sum[i]表示sigma{a[j],1<=j<=i}(其中sum[0]=0),我们的目的是最大化sum[a]-sum[b],即:

ans=Max{sum[a]-sum[b],0<=b<=a<=m}

从左向右循环,记录最小的sum[min],对于每个i,以a[i]结尾的最大和的子串就是sum[i]-sum[min],所以只需要不断更新sum[min]并计算最大的sum[i]-sum[min]就行了。时间复杂度也是O(N)

为了限制长度i-min<=m,可以维护一个队列dd[],其中队头是t,队尾是w。其中dd[i].value是其sum值,而dd[i].id是该sum值对应的i

维护队列t-w按dd[].value从小到大排序,每次取出dd[t]便为需要的sum[min]。而每次计算完一个sum值需要插入到队尾。

dd[t].id距离当前i大于m,也就是取得了不符合规定的区间,那么该dd[t]就不能被使用,显然该值同样不能被大于i的sum使用,所以应该让其出队。

新加入的sum[i]dd[w]中需要做一次插入排序,而显然所有大于sum[i]的队列里的元素都应该被去除。

所以在一次循环中,每个点只会进入单调队列一次,而且再被插入的时候就会被淘汰。所以维护单调队列不会带来复杂度的增加,仅会带来常数的增加。所以时间复杂度依然为O(n^2*m)

扩展子区域 -a如何实现

大概想了一下可以用状态压缩动态规划来做,这样可以得到最优解。

最朴素的想法是用1表示选定格子,0表示不选定格子,一行一行进行递推,只有上面的状态和下面的状态有联通才能转移。

具体一点说,用Dp[i][status]来表示第i行,状态为status的方格,其取到的最优子区域和是多少。用Canappend(status1,status2)来表示两个状态是否能转移。则动态规划方程为:

Dp[i][status]=Max{Dp[i-1][status_i],status_i in status_all and Canappend(status,status_i)}+cost[i][status]

其中cost[i][status]为取第i行中status的状态所带来的和。

这种想法很简单,不过可惜是错的。

很明显,状态数是不够的,因为单单有0和1表示的信息不够,它只能表示纵向联通的信息,而不能表示横向联通,这样最直接的结果就是终状态的合理性无法判断,所以要正确进行Dp,还需要额外的信息量。

用0-k表示格子的选取情况,其中0表示不取,1-k表示其处于某种联通状态。比如说

0 1 1 2 0 2 1

表示其中{2,3,7}为一个联通块(不管它到底怎么连的,反正在它上边就是连上了),{4,6}是另一个联通块。这样表示的信息就足够了。

但信息变多带来的是转移的复杂性,一行一行转移所需要处理的状态有点略多。

据说可以用插头Dp来实现简单转移,我看了下一篇论文,发现实现起来还是有点微难的。其复杂度上界大概是O((m/2)^m*n),在n,m<=32的情况下的运行时间依然是天文数字。所以任务很艰难,在下能力有限,还是没能写出正解来。

另外有一种想法是先把所有联通的正值点抽象成一个点,然后再在这些新的"大点"上作最小生成树,不过这显然没法得到最优解。

程序

程序只实现了-h -v两种参数。

  1 #include<stdio.h>
  2 
  3 #define MAXN 1000
  4 #define MAXM 1000
  5 #define MINV (-2147483647)
  6 int n,m,a[2*MAXN+2][2*MAXM+2],dd[2*MAXM+2],id[2*MAXM+2];
  7 
  8 void input(char *cin)
  9 {
 10     int i,j;
 11     freopen(cin,"r",stdin);
 12     scanf("%d,",&n);
 13     scanf("%d,",&m);
 14     for(i=0;i<n;i++)
 15         for(j=0;j<m;j++)
 16             {
 17             scanf("%d",&a[i][j]);
 18             getchar();
 19             }
 20 }
 21 int donocir()
 22 {
 23     int i,j,k,sum,ans;
 24     ans=MINV;
 25     int p[MAXM+2];
 26     for (i=0;i<n;i++)
 27         {
 28         for(k=0;k<m;k++) p[k]=0;
 29         for (j=i;j<n;j++)
 30             {
 31             sum=0;
 32             for (k=0;k<m;k++)
 33                 {
 34                 p[k]+=a[j][k];
 35                 if (sum<0)sum=0;
 36                 sum+=p[k];
 37                 if (sum>ans)
 38                     ans=sum;
 39                 }
 40             }
 41         }
 42     return ans;
 43 }
 44 int docir(int ish,int isv)
 45 {
 46     int i,k,j,l,p[2*MAXM+2],nn,mm,ans=MINV,sum,t,w,ai,aj,ak,at;
 47     nn=n; mm=m;
 48     for(i=0;i<n;i++)
 49         for(j=0;j<m;j++)
 50             {
 51             if(ish)
 52             {
 53                 a[i][j+m]=a[i][j];
 54             }
 55             if(isv)
 56             {
 57                 a[i+n][j]=a[i][j];
 58                 a[i+n][j+m]=a[i][j];
 59             }
 60             }
 61     if(isv)nn=2*n; else nn=n;
 62     if(ish)mm=2*m; else mm=m;
 63     for(i=0;i<n;i++)
 64         {
 65         for(k=0;k<mm;k++) p[k]=0;
 66         for(j=i;j<nn && j<i+n;j++)
 67             {
 68             t=0;
 69             w=0;
 70             dd[t]=0;
 71             id[t]=-1;
 72             sum=0;
 73             for(k=0;k<mm;k++)
 74                 {
 75                 p[k]+=a[j][k];
 76                 sum+=p[k];
 77                 while(k-id[t] > m)t++;
 78                 if (sum-dd[t] > ans)
 79                     {
 80                     ans=sum-dd[t];
 81                     ai=i;
 82                     aj=j;
 83                     ak=k;
 84                     at=id[t];
 85                     }
 86                 while(sum<dd[w] && w>=t) w=w-1;
 87                 w=w+1;
 88                 dd[w]=sum;
 89                 id[w]=k;
 90                 }
 91             }
 92         }
 93     printf("%d\n%d\n%d\n%d\n",ai,aj,at,ak);
 94     return ans;
 95 }
 96 int main(int argc,char *argv[])
 97 {
 98     int i,j,ish=0,isv=0;
 99     if (argc==1)
100         {
101             printf("no input file!\n");
102         }
103     if (argc==2)
104         {
105             input(argv[1]);
106             printf("%d\n",donocir());
107         }
108     else
109         {
110             input(argv[argc-1]);
111             for (i=1;i<argc-1;i++)
112                 for(j=1;argv[i][j]!='\0';j++)
113                 {
114                 if (argv[i][j]=='h') ish=1;
115                 if (argv[i][j]=='v') isv=1;
116                 }
117             printf("%d\n",docir(ish,isv));
118         }
119     return 0;
120 }
View Code

执行方法

运行./maxsum [options] <filename>

支持 -h -v -hv 和 -h -v

posted @ 2013-09-29 21:50  Forwil  阅读(292)  评论(1编辑  收藏  举报