博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
uva 709 Formatting Text
阅读量:5998 次
发布时间:2019-06-20

本文共 3382 字,大约阅读时间需要 11 分钟。

这道题目在poj和zoj上都有,分别是poj 1093   zoj  1147

题意:输入行宽,输入一篇文章,多行,多单词,然后以一个空行来表示文章的结束,但输入的行宽为0的时候表示程序结束。一个单词的长度不会超过行宽。然后通过调整使整片文章看起来最和谐,其实的要求是,一行中,如果不止一个单词,那么行首和行尾都必须放单词,即行首和行尾不能有空格。特殊情况是一行只放一个单词,那么这个单词一定要放在行首,另外如果这个单词的长度小于行宽,那么这一行的badness(一个数值)就为500,如果这个单词刚好等于行宽即完全放满,那么badness为0。另外badness原本是一个数值要计算的,即两个单词之间的空格数减1的平方(c-1)^2,我们的目标是通过调整单词的位置使到整片文章的badness值的和最小

 

先说说注意的问题,输入的问题,我就是WA在这里,当时在UVA一直WA,所有搞出来的数据都是对的,然后放到POJ是PE,这让我有了信心,至少我的程序算法是对的,但是为什么PE我怎么改都改不出来,后来发现ZOJ也有这题,又拿去ZOJ提交,又是WA,然后我就奇怪了,在烦躁的情况下我胡乱输入一些数据,就在这个乱来的过程中发现了BUG,那就是输入是任意的,即单词和单词之间不一定只有一个空格,一篇文章的开头不一定一出来就是单词而是先搞几个空格再出单词,而文章的最后不是立马结束,而是再加一个多余的空格,就是因为忘记处理这些空格只是按照常规的思维也输入数据所以一直找不到BUG(其实处理这个问题注意这种问题应该是ACMer的基本素质,不知道为什么当时没有想到)。所以处理好这些问题之后我们就可以开始DP并开始构建路径了。

保存所有的单词在w[][]数组中,每个单词的单词的长度保存在len[]数组中

DP(用记忆化搜索实现):dp[i][j]表示第i个单词位于一行的第j个位置时的最优解(这里,单词的计数是从1到n,位置是从1到L,L是行宽)。当第i个单词放下去第j个位置之后,就可以去枚举第i+1个单词可以放的位置,显然第i+1的单词能放的第一个位置是 c1=j+len[i]-1+2 (即在第i个单词后面空一个空格再放第i+1个单词), 能放的最后的位置是 c2=L+1-len[i+1] (即第i+1个单词位于行尾),这时候你会发现一个问题,可能根本放不下第i+1个单词,即当c1>c2的时候是放不下第i+1个单词的,那需不需要独立做一个判断呢,不需要的,因为我们是通关过一个循环来枚举第i+1个单词能放的位置,如果第i+1个单词放不下的,这个循环不会执行  for(k=c1; k<=c2; k++)  ,若c1>c2循环不会执行

然后要判断一些特殊情况

1.例如当前的第i个单词已经位于行尾了,那么不用去枚举第i+1个单词的位置,而应该直接递归到第i+1个单词放在下一行的行首的情况

2.另一种情况,第i个单词位于行首,那么还是要去枚举第i+1个单词放在这一行的位置,除此之外,还有枚举一个特殊情况,这行就放第i个单词,第i+1个单词放下一行的行首,注意,一个单词放一行,当且仅当这个单词刚好是为位于行首时才能执行

3.可能你还会想说另一个特殊情况,一个单词位于行首,同时又位于行尾,即这个单词正好在这一行首,而且长度就是行宽,这个情况我们是怎么处理的呢?要不要独立处理呢,不用的,这种情况是归为这个单词位于行尾那种情况来处理的,即第1种特殊情况。为什么,我们在后面说到

 

来看看状态转移方程

dp[i][j]=dp[i+1][1]  (这种情况当且仅当第i个单词位于行尾,那么第i+1个单词不用考虑只能放下一行的行首)

dp[i][1]=dp[i+1][1]+500 (这种情况当且仅当第i个单词位于行首并且长度小于行宽,然后它单独放一行那它产生的badness值是500,然后第i+1个单词放一行行首)

dp[i][j]=dp[i+1][k]+b (k是第i+1个单词放在这行的位置,b是第i个单词和第i+1个单词之间的空格计算出来的badness值)

这是状态转移方程的三种可能,选值最小的,而这三个判断的优先顺序其实是有些讲究的

 

首先应该判断第i个单词是否位于行尾,因为位于行尾的话,接下来的递归只有一种情况,就是第i+1个单词放下一行的行首

即dp[i][j]=dp[i+1][1]  , 递归结束后就应该直接返回上一层了

然后去判断这个单词是否位于行首,如果位于行首,才能进行这个递归

即dp[i][1]=dp[i+1][1]+500 , 第i个单词单独放一行。返回后,不能直接再返回上一层,还要继续去枚举第i+1个单词放在当前这行的情况

即dp[i][j]=dp[i+1][k]+b

所以说,一个单词如果位于行首并且也位于行尾即长度等于行宽的时候,当做位于行尾的情况来处理,我们比较两者

dp[i][j]=dp[i+1][1] 

dp[i][1]=dp[i+1][1]+500

后者还要加上500,而我们是求较小值,显然上面的更优,有了上面的就不要下面的了,当然你颠倒顺序是不影响程序的正确性的,因为还是会被修改回来

然后就是记录路径的问题,path[i][j]表示当第i个单词位于一行的第j个位置时它后面有多少个空格

如果第i个单词位于行尾,那么它后面的空格数是0,即path[i][j]=0

如果第i个单词位于行首并且单独放一行,它后面的空格也是0,因为这是题目说明的,如果一个单词单独一行它后面不需要输出空格,所以也是path[i][j]=0

 

所以当我们遇到path[i][j]=0的时候就直接输出这个单词并且换行,然后下一个状态就是path[i+1][1],如果不为0的话,就要计算出下一个状态

path[][]数组初始化为-1

 

然后就说这么多,看代码吧

 

#include 
#include
#define INF 1000000000#define LEN 90#define MAXN 10010char w[MAXN][LEN];int len[MAXN];int f[MAXN][LEN];bool visit[MAXN][LEN];int path[MAXN][LEN];int L,n;/* //这个函数没用,只是为了检验处理输入后的结果是否正确void print_input(){ int i; printf("单词个数:%d\n",n); for(i=1; i<=n; i++) printf("%s %d\n",w[i],len[i]); return ;}*/int input(){ int i,j,flag; char temp[MAXN]; gets(temp); sscanf(temp,"%d",&L); if(!L) return 0; n=1; while(1) { gets(temp); if(temp[0]=='\0') break; for(flag=0,j=0,i=0; i
n) //递归边界 { if(j==1) //说明第n个单词是在行尾的 return f[i][j]=0; else //说明第n个单词并不是在行尾而是在行中,这是不允许的 return f[i][j]=INF; } if( j+len[i]-1 == L) //若单前的单词位于行尾,那么直接递归下一个单词在行首 {
//一个单词位于行尾它可能同时是位于行首的,即一个单词完整占据一行,但这样并不符合赋值badness=500的条件 //是当单词单独位于一行的行首,且单词长度小于行宽时才赋值badness=500 //所以从逻辑上的优先性来讲,要找最小值,应该先判断是否位于行尾这样就可以不用再判断是否位于行首了 ans=dp(i+1,1); if(ans

 

转载地址:http://oxwmx.baihongyu.com/

你可能感兴趣的文章
C# 使用SqlBulkCopy类批量复制大数据
查看>>
LoadRunner 技巧之协议分析
查看>>
sublime text 3 3017 系统默认快捷键定义文件Default (Windows).sublime-keymap
查看>>
设计模式(六)—原型模式Prototype(创建型)
查看>>
Linux基本操作 2-----Linux文件系统基本结构
查看>>
【转】編譯Ogre1.9 IOS Dependencies及Ogre Source步驟及相關注意事項…
查看>>
Assembly类
查看>>
[算法][雅可比迭代解方程组]
查看>>
分享二
查看>>
对CAB文件进行数字签名
查看>>
基于内容的音频和音乐分析领域的研究工作
查看>>
Android TabActivity之感叹
查看>>
程序员从成熟到优秀的瓶颈
查看>>
java printf long
查看>>
sql 数据库修复
查看>>
linux系统调用和库函数调用的区别
查看>>
黄聪:C#操作xml SelectNodes,SelectSingleNode通过 xPath 定位class包含Contains的DIV
查看>>
Android 广播机制(两种注册方法)与中断广播
查看>>
Git_分支管理策略
查看>>
敏捷开发(三)- 估算故事
查看>>