整齐打印
考慮在一臺(tái)打印機(jī)上優(yōu)美地打印一段文章的問(wèn)題。輸入的正文是長(zhǎng)度為I1,I2,…,In的n個(gè)單詞構(gòu)成的序列。我們希望將這段文章在幾行上打印出來(lái),每行的最大長(zhǎng)度為m,且“優(yōu)美”度的標(biāo)準(zhǔn)如下:
如果某一行包含從i到j(luò)的單詞,且每?jī)蓚€(gè)單詞間留一空,則在行末多余的空格為
我們希望除最后一行的所有行中,行末多余空格的總和最小。請(qǐng)給出一個(gè)算法來(lái)優(yōu)美地打印出一段有n個(gè)單詞的文章。
由于必須打印完n個(gè)單詞且每行打印的單詞是連續(xù)的,因此,我們從第n個(gè)單詞開始,依次考慮填一個(gè)單詞(單詞n),填兩個(gè)單詞(單詞n-1,單詞n)…,填n個(gè)單詞(單詞1,單詞2,…,單詞n)的打印方案。
r[i]?
設(shè)當(dāng)前行填入單詞i…單詞j(i≤ j),每?jī)蓚€(gè)單詞間留一空,單詞間的空格數(shù)為j一i,從第i個(gè)單詞到第j個(gè)單詞的長(zhǎng)度和為,因此行末多余的空格為。由于單詞填人的方式是按單詞序號(hào)遞減的順序進(jìn)行的,因此填入單詞i…單詞n后的行末空格數(shù)的總和應(yīng)為當(dāng)前行的行末空格數(shù)+前面行的行末空格數(shù)的和。我們的目標(biāo)是使它最小。
設(shè) r[i] -- 填入單詞i…單詞n后,所有被填行的行末空格數(shù)總和的最小值。顯然,r[i]的遞歸式如下:
另外,我們專門設(shè)置了一張記憶表k[i](1≤ i≤ n+1),記下使得r[i]最小的j值,表示填單詞i…單詞n的最佳方案中,最后一行應(yīng)填單詞i…單詞j(=k[i])。
r的遞歸的邊界為
r[n+1]=0;k[n+1]=n+1;
表示不填任何單詞時(shí)的行末空格數(shù)為0。
我們從r[n+1]出發(fā),依次求r[n],r[n-1]…r[1]。由r[i]的遞歸式可以看出,求 r[i]最小值的子問(wèn)題,包含了求r[i+l]…r[n+l]的子問(wèn)題。要使r[i]最小,必須使這些子問(wèn)題的值最小,因此符合動(dòng)態(tài)規(guī)劃程序設(shè)計(jì)要求的“最優(yōu)子結(jié)構(gòu)”和“重疊子問(wèn)題”兩個(gè)要素。我們按自下而上的方式求解,充分利用了重疊子問(wèn)題。最后求出的r[1]為優(yōu)美打印方案中行末空格數(shù)的總和;從單詞1出發(fā),順著記憶表K的指示,可順序打印出文章的各行。
網(wǎng)上的代碼(個(gè)人認(rèn)為有些問(wèn)題,因?yàn)樽詈笠恍幸厥馓幚?#xff09;:
/*優(yōu)美打印 見算法導(dǎo)論 要在每行長(zhǎng)度為m的紙上打印n個(gè)長(zhǎng)為l[i]的單詞,優(yōu)美的定義是:同行單詞空一個(gè),使各行末尾空格之和(最后一行除外)最小。 逆填單詞,設(shè)r[i]表示打印i到第n個(gè)單詞的最小末尾空格數(shù),顯然,r[i]的遞歸式如下: r[i]=min(m-((j-i)+sum[i][j])+r[j+1])(1<=i<=n+1)(i<=j<=n) 我們專門設(shè)置了一張記憶表k[i](1≤i≤n+1),記下使得r[i]最小的j值 邊界條件r[n+1]=0;k[n+1]=n+1; */ #include<iostream> #include<algorithm> #include<string> using namespace std; int n,m; int l[101],c[101][101],sum[101],r[101],k[101]; //第i個(gè)單詞長(zhǎng)l[i],前i個(gè)單詞長(zhǎng)sum[i],k記錄 const int INF=1000000000; int PERFECT_PRINT() {r[n+1]=0,k[n+1]=n+1;for(int i=1;i<=n;i++)r[i]=INF;for(int i=n;i>=1;i--){for(int j=n;j>=i;j--){if(m<j-i+c[i][j])continue;int tmp=m-((j-i+c[i][j]))+r[j+1];if(tmp<r[i]){r[i]=tmp;k[i]=j;}}}return r[1]; } void print() {cout<<PERFECT_PRINT()<<endl;int p=1;while(1){if(p==n+1)break;cout<<k[p]<<" ";p=k[p]+1;}cout<<endl; } int main() {while(cin>>n>>m){for(int i=1;i<=n;i++)cin>>l[i];memset(sum,0,sizeof(sum));for(int i=1;i<=n;i++)sum[i]=sum[i-1]+l[i];for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)c[i][j]=sum[j]-sum[i-1];print();} }我大概寫了一下思路,有些邊界條件沒(méi)有處理
const int N = 7; int a[] = {0,1,4,8,2,4,1}; int b[N]; int c[N][N]; int s[N]; int r[N]; int m = 10;int neatPrint() {memset(s,0,sizeof(s));int i = 0;for (i = 1; i <= N-1; ++i)s[i] += s[i-1] + a[i];for( i = 1; i < N; ++i) {for (int j = 1; j < N; ++j) {if( i<= j )c[i][j] = s[j] - s[i-1];}}for( i = N-1; i >0; --i) {if (c[i][N-1] + N - 1 - i <=m) {b[i] = N-1;r[i] = 0;}}if (i >1) {for (;i >0; --i) {for (int j = N-1;j>=i; --j) {if (m - (j - i + c[i][j]) >=0) {int tmp = m - (j - i + c[i][j]) + r[j+1];if (tmp < r[i]) {r[i] = tmp;b[i] = j;}}}}}}從最后一個(gè)單詞開始放,r[i]表示在以i單詞開頭的文檔,從i到N的單詞所用的最少末尾空格。
總結(jié)