总体思路
(1)单维数组
单维数组的思路的构建在第一次作业已经完成了,简单来说就是根据贪心策略从头到尾累加,一旦遇到当前累加和变为负数,就将累加和清零,继续遍历数组中的元素,算法的时间复杂度是O(n)的。这次作业又加了一个条件,就是存在结果溢出的情况。由于笔者不知道结果到底会有多大,所以没敢开个64位整型敷衍了事,就写了高精度处理数据溢出的情况,也就是把每个数都用一个数组表示,数组中每一位代表数字的每一位,运算根据加减法的原理进行数组操作。
测试样例:结果:
代码:
void solve1() { int i,j,s[100],a[100],ans[100]; memset(s,0,sizeof(s)); memset(ans,0,sizeof(ans)); ans[0]=1; s[0]=1; int flag; for (i=1;i<=m;i++) { if (s[s[0]]<0) { memset(s,0,sizeof(s)); s[0]=1; } flag=0; memset(a,0,sizeof(a)); a[0]=1;a[1]=data[1][i]; if (a[1]<0) { a[1]=-a[1]; flag=1; } while (a[a[0]]>9) { a[a[0]+1]=a[a[0]]/10; a[a[0]]=a[a[0]]%10; a[0]++; } int l=s[0]; if (a[0]>l) l=a[0]; if (flag) { for (j=1;j<=l;j++) { s[j]-=a[j]; if (s[j]<0 && j<l){ s[j]+=10; s[j+1]-=1; } } while (s[l]==0 && l>1) l--; s[0]=l; }else { for (j=1;j<=l;j++) { s[j]+=a[j]; if (s[j]>9) { s[j+1]+=s[j]/10; s[j]=s[j]%10; } } if (s[l+1]>0) l++; s[0]=l; } int max=0; if (s[s[0]]>0){ if (s[0]>ans[0]){ max=1; }else if (s[0]<ans[0]) { max=0; }else { for (j=s[0];j>0;j--) if (s[j]>ans[j]) { max=1; break; } else if (s[j]<ans[j]) { max=0; break; } } } if (max==1){ for (j=0;j<=s[0];j++) ans[j]=s[j]; } } for (i=ans[0];i>0;i--) cout<<ans[i]; cout<<endl; }
(2)二维数组和最大子矩形
思路其实是单维思路的扩展,由于矩形的各列它们的行数是相等的,我们可以在预处理的时候将每一列的所有前i行的和计算出来(i<n),这样我们就可以在枚举矩形下边的时候在枚举行数,将这么多行的和当作是一维数组的各个元素,在通过一维的算法求和取最大就可以了。
测试样例:结果:
代码:
void solve2() { int i,j,k; int ans=0; memset(sum,0,sizeof(sum)); for (i=1;i<=n;i++) { for (j=1;j<=m;j++) sum[j][i]=sum[j][i-1]+data[i][j]; } for (i=1;i<=n;i++) for (k=1;k<=i;k++) { int temps=0; for (j=1;j<=m;j++) { int a=sum[j][i]-sum[j][k-1]; if (temps<0) temps=0; temps+=a; if (temps>ans) ans=temps; } } cout<<ans<<endl; }
(3)二维数组最大和联通块
这题不评价,太难了,当初有想过联通性状态压缩的动态规划,不过由于笔者能力有限,实现起来难度巨大,于是放弃,不过即使这么做,也不能在理想的时间内解决题目限定规模的问题。于是,既然怎么做都会运行很久,那笔者就干脆来了个破罐子破摔,写了个暴力枚举(至少能输出正确结果。。。)。主要的思路就是对于n*m矩阵中每个元素枚举它们的选取状态,然后判断这种取法是否联通。一开始枚举状态笔者采用的深度优先搜索,从第一行第一个元素开始枚举其状态,接着递归一个一个的枚举,直到枚举完这n*m个元素的状态,然后再判断是否联通。机智的读者可能发现,这里是可以剪枝优化的,就是某种状态是否联通,我们并不一定要在最后一个元素枚举完后才能判断出来,其实在每一个枚举完后我们就能判断当前的状态是否有可能最终联通,判定的条件就通过遍历之前的矩阵来判断是否有选取的元素无法到达当前枚举元素所在的行。这样做可以剪掉很多不必要的枚举。而枚举完所有元素状态的判断是否连通的方法是用染色,随便从当前状态中选取一个选中的初始状态,然后进行搜索遍历,由于笔者之前枚举是用深度优先搜索,这里觉得递归里面再套个递归不太好,于是染色的步骤就通过广度优先搜索来实现。当染完色后,检查每个元素,如果存在选中的元素未被染色那么这个结果就是不联通的,反之即是联通,就可以算出和然后更新当前答案。这题单独说点感想吧:题目太恶心了,都有退课的冲动了。
测试数据:结果:
代码:
void dfs(int x,int y) { if (x>n) { if (mode==3) solve3(); else solve7(); return; } int xx=x,yy=y+1; if (yy>m) { yy=1; xx++; } pass[x][y]=1; dfs(xx,yy); pass[x][y]=0; dfs(xx,yy); }
void solve3() { memset(qq,0,sizeof(qq)); memset(pb,0,sizeof(pb)); int h=1,t=1; int i,j,k; for (i=1;i<=n;i++) { for (j=1;j<=m;j++) if (pass[i][j]==1) break; if (j<=m) break; } pb[i][j]=1; qq[1]=(i-1)*m+j; while (h<=t) { int x,y; x=(qq[h]-1)/m+1; y=qq[h]%m; if (y==0) y=m; for (k=0;k<4;k++) { int xx=x+change[k][0],yy=y+change[k][1]; int temp; if (xx<1 || xx>n || yy<1 || yy>m) continue; temp=(xx-1)*m+yy; if (pass[xx][yy]==1 && pb[xx][yy]==0) { qq[++t]=temp; pb[xx][yy]=1; } } h++; } int flag=0,temps=0; for (i=1;i<=n;i++) for (j=1;j<=m;j++) if (pass[i][j]==1) { temps+=data[i][j]; if (pb[i][j]==0) flag=1; } temps=temps; if (flag==0 && temps>aans) aans=temps; }
(4)水平相连
虽说题目描写说是在(3)的基础上,但考虑到后面有个/v /h /a 这种东西的存在,我就姑且认为这个/h模式选取的是矩阵吧。其实水平相连的话和原始的并没有什么区别,我代码里写的思路是按照(2)的顺序来求最大值的同时,通过类似的方法求出最小值(遍历顺序一样不过和大于零就清零,取最小),然后用整行的和减掉这个最小值,和求出的最大值进行比较。这么做的原理其实就是考虑到横跨的情况其实就是一行中头取一段,尾取一段,又由于一行的和是固定的,这样我们用一行的和减去求出的最小连续块的和就能得到横跨的选取的最大值。在实现了这个算法后,笔者又想到了一种方法,其实就是把列压缩改成行压缩的形式,然后将数组的列数扩大一倍(后半段与前半段相等),然后按照(2)的方法(这里一开始枚举列),只要在枚举的时候限定下长度不大于m,也能得出答案,这样做代码量少了,但空间复杂度大了。
测试数据:结果:
代码:
void solve4() { int i,j,k; int ans=0; memset(sum,0,sizeof(sum)); for (i=1;i<=n;i++) { for (j=1;j<=m;j++){ sum[j][i]=sum[j][i-1]+data[i][j]; sum[j+m][i]=sum[j][i]; } } for (i=1;i<=n;i++) for (k=1;k<=i;k++) { int temps=0,templ=0,asum=0,min=9999999; for (j=1;j<=m;j++) { int a=sum[j][i]-sum[j][k-1]; if (temps<0) temps=0; temps+=a; if (temps>ans) ans=temps; if (templ>0) templ=0; templ+=a; if (templ<min) min=templ; asum+=a; } if (asum-min>ans) ans=asum-min; } cout<<ans<<endl; }
垂直相连也是一样的(我会说之间将行列对换就能解决吗?)
(5)水平垂直相连
思路就是上面我的两种解法的结合,首先将行数扩大一倍,然后用(4)的方法对于水平方向进行处理,当前枚举矩形的行数保证<=n。
测试数据:结果:
代码:
void solve6() { int i,j,k; int ans=0; memset(sum,0,sizeof(sum)); for (i=1;i<=2*n;i++) { int ii=i; if (ii>n) ii-=n; for (j=1;j<=m;j++) sum[j][i]=sum[j][i-1]+data[ii][j]; } for (i=1;i<=2*n;i++) for (k=1;k<=i;k++) { if (i-k>=n) continue; int temps=0,templ=0,asum=0,min=9999999; for (j=1;j<=m;j++) { int a=sum[j][i]-sum[j][k-1]; if (temps<0) temps=0; temps+=a; if (temps>ans) ans=temps; if (templ>0) templ=0; templ+=a; if (templ<min) min=templ; asum+=a; } if (asum-min>ans) ans=asum-min; } cout<<ans<<endl; }
(7)/v /h /a
思路和(3)类似,不同之处只是在判断是否连通时染色的规则稍加改变。之前染色是的规则是如果相邻元素在矩阵之外就跳过,现在由于水平垂直都相连了,于是我们就矩阵之外的元素变为对应矩阵内的元素。
测试数据:结果:
代码:与(3)类似。
设计过程
设计还是采用面向过程的方法(我知道很挫)。首先开个公共变量mode,根据命令行参数改变mode值来选取模式。各种模式对应一个void函数,solve1(), solve2()...。这样做的好处就是代码比较好组织,思路体现得比较直观,缺点就是代码重复度比较大,过于冗长。这两天试着用面向对象的方法来写,但由于之前没有这方面的经验,又临近截至日期了,所以没有完成。
单元测试
可能是用面向过程的方法设计,单元测试一直失败,没能完成这项的测试。
开发评估
Personal Software Process Stages | 时间百分比(%) | 实际花费的时间 (分钟) | 原来估计的时间 (分钟) | |
Planning | 计划 | |||
· Estimate | · 估计这个任务需要多少时间,把工作细化并大致排序 | 4.7 | 30 | 30 |
Development | 开发 | |||
· Analysis | · 需求分析 (包括学习新技术) | 23.8 | 150 | 30 |
· Design Spec | · 生成设计文档 | 4.7 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 4.7 | 30 | 30 |
· Coding Standard | · 代码规范 (制定合适的规范) | 4.7 | 30 | 30 |
· Design | · 具体设计 | 9.5 | 60 | 60 |
· Coding | · 具体编码 | 19 | 120 | 120 |
· Code Review | · 代码复审 | 4.7 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 4.7 | 30 | 30 |
Reporting | 总结报告 | |||
| 4.7 | 30 | 30 | |
| 9.5 | 60 | 60 | |
Total | 总计 | 100% | 总用时 630 | 总估计的用时 510 |
感想
其实总体来说这次作业还是不难的,不过有bug般的/a 模式的存在着就让学习时间增加了非常多。很遗憾单元测试没有做出来,早知道一开始把研究/a 情况的时间去学习C#面向对象编程方面的东西了。下次一定这样做,努力做出老师的所有要求来。