NOI 2009A 诗人小G
NOI 2009A 诗人小G
诗人小G
【问题描述】
小G是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中, 注意一行中可以放的句子数目是没有限制的。小G给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小G不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小G对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的P次方,而一个排版的不协调度为所有行不协调度的总和。
小G最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。
【输入文件】
输入文件poet.in包含多组数据。
第一行包含一个整数T,表示诗的数量,接下来是T首诗,这里一首诗即为一组数据。每组数据的第一行包含三个由空格分隔的正整数N、L、P,其中N表示这首诗句子的数目,L表示这首诗的行标准长度,P的含义见问题描述。从第2行开始,每行为一个句子,句子由英文字母、数字、标点符号等符号组成(ASCII码33~127, 但不包含 ‘-’)。
【输出文件】
输出文件为poet.out。
对于每组数据,若最小的不协调度不超过1018,则第一行一个数表示不协调度,接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。若最小的不协调度超过1018,则输出"Too hard to arrange"(不包含引号)。每组数据结束后输出"--------------------"(不包括引号),共20个"-","-"的ASCII码为45,请勿输出多余的空行或者空格。
【输入样例】
4
4 9 3
brysj,
hhrhl.
yqqlm,
gsycl.
4 9 2
brysj,
hhrhl.
yqqlm,
gsycl.
1 1005 6
poet
1 1004 6
poet
【输出样例】
108
brysj,
hhrhl.
yqqlm,
gsycl.
--------------------
32
brysj, hhrhl.
yqqlm, gsycl.
--------------------
Too hard to arrange
--------------------
1000000000000000000
poet
--------------------
【样例说明】
前两组输入数据中每行的实际长度均为6,后两组输入数据每行的实际长度均为4。一个排版方案中每行相邻两个句子之间的空格也算在这行的长度中(可参见样例中第二组数据)。每行末尾没有空格。
【评分方法】
本题设有部分分,当你的程序对于该测试点内每组数据计算得出的不协调度最小值都正确时,能得到本测试点70%的分数。在此情况下,若每组数据的排版方案都合法并且得出的不协调度都与输出的相等,则能得到本测试点剩下30%的分数。注意,输出格式错误可能会导致本测试点不得分。
【数据规模和约定】
总共10个测试点,数据范围满足:
测试点 |
T |
N |
L |
P |
1 |
≤10 |
≤18 |
≤100 |
≤5 |
2 |
≤10 |
≤2000 |
≤60000 |
≤10 |
3 |
≤10 |
≤2000 |
≤60000 |
≤10 |
4 |
≤5 |
≤100000 |
≤200 |
≤10 |
5 |
≤5 |
≤100000 |
≤200 |
≤10 |
6 |
≤5 |
≤100000 |
≤3000000 |
2 |
7 |
≤5 |
≤100000 |
≤3000000 |
2 |
8 |
≤5 |
≤100000 |
≤3000000 |
≤10 |
9 |
≤5 |
≤100000 |
≤3000000 |
≤10 |
10 |
≤5 |
≤100000 |
≤3000000 |
≤10 |
所有测试点中均满足句子长度不超过30。
直接dp,30分做法详细分析:
状态转移方程:
f[i] = min(f[j] + |s[i] - s[j] + i - j - 1 - L| ^ p) (0<=j<i)
f(i)表示将前i行诗放入整行的最小不协调度
s表示前缀和
可以使用1D1D动态规划,100分
1 //不要去洛谷上面提交,洛谷的这题数据有问题,去codeVs 2 #include<cstdio> 3 #include<cstring> 4 #define ll long double 5 //node[i]代表i,里面的l代表决策的作用起点,r代表决策的作用终点,p是决策的值 6 struct node{int l,r,p;}q[100100]; 7 #define MAX 1000000000000000000LL 8 #define N 100100 9 ll sum[N],f[N]; 10 int n,l,p,T; 11 char ch[35]; 12 //求|y|^p的函数 13 ll pow(ll y){ 14 if(y<0)y=-y; 15 ll ans=1; 16 for (int i=1;i<=p;i++) ans*=y; 17 return ans; 18 } 19 //计算不协调度 20 ll calc(int x,int y){ 21 return f[x]+pow(sum[y]-sum[x]+(y-x-1)-l); 22 } 23 //二分查找找决策转折点 24 //node表示老决策点,x表示新决策点的值 25 //这个函数就是在老的决策点的范围中寻找新的决策点的值 26 int find(node t,int x){ 27 int l=t.l,r=t.r; 28 while(l<=r){//当头小于等于尾,也就是还有数可以查找的时候 29 //mid=(头+尾)/2 30 int mid=(l+r)>>1; 31 //小于的情况,也就是新的决策点更有,我们就要一直往前继续扩展新的决策点的起点, 32 //直到老决策点比较好的时候 33 if (calc(x,mid)<=calc(t.p,mid)) r=mid-1; 34 //大于的情况,也就老决策点比较好的时候,我们往后搜索新决策点的作用域的起点 35 else l=mid+1; 36 } 37 //返回头 38 return l; 39 } 40 41 void dp(){ 42 //用数组模拟栈,头置为1,尾置为0 43 int head=1,tail=0; 44 //将第一个决策入栈,决策1的起点为0,终点为n,决策的初始值置为0,因为初始决策就是0 45 q[++tail]=(node){0,n,0}; 46 //求取f[i] 47 for (int i=1;i<=n;i++){ 48 //如果栈头结点上的决策作用终点小于i,说明这个决策已经无法对i进行作用,所以我们要换新的决策 49 //head<=tail栈的头小于等于尾,说明栈里面还有新的决策,那就换上新的决策 50 //因为head++,所以此时头节点是指向新的决策 51 if(q[head].r<i&&head<=tail) head++; 52 //用新的可以作用i位置的决策来计算f[i]的值,这样计算出来的f[i]的值就是最优值 53 f[i]=calc(q[head].p,i); 54 //calc(i,n)表示通过值为i的决策去计算f[n]的值 55 //calc(q[tail].p,n)表示通过旧决策q[tail].p去计算f[n]的值 56 //calc(i,n)<=calc(q[tail].p,n)表示值为i的决策优于老决策,我们才进行下一步 57 //不然,如果老决策更好,我们根部用不着换决策 58 59 //每一个被计算出来的f[i],都会使i成为新的决策,因为我有了f[i]的值, 60 //所以我可以用f[i]的值帮助计算别的f[k](k>i),所以f[i]就是新的决策 61 62 /* 63 比如说我们的样例1中,当i==3时,我们通过 q[head].p=2这个决策把f[3]算出来了 64 比如说这里的n是4,如果calc(3,4)<=calc(2,4),那说明值为3的这个新决策更好, 65 我们就要在决策2的作用区间里面找出决策3的作用区间 66 */ 67 68 69 //如果新决策更好,找出新决策的作用范围 70 //确定新决策点的终点 71 if (calc(i,n)<=calc(q[tail].p,n)){ 72 //如果栈的头小于等于尾,head<=tail,说明栈里有元素 73 //calc(q[tail].p,q[tail].l)表示用旧决策点决策值来决策旧决策点的起点 74 //calc(i,q[tail].l)用新决策点来决策旧决策点的起点 75 //如果后者小于前者,说明新决更好,那就要在更广阔的范围里面去寻找新决策点的决策范围 76 //换句话说,就是当新决策点的作用范围覆盖了老决策点,我们要到比旧决策点更老点决策点里面寻找新决策点的作用方位 77 while(head<=tail&&calc(q[tail].p,q[tail].l)>=calc(i,q[tail].l)) tail--; 78 //head>tail说明栈已经为空了,就是说现在新决策点最好,所以把新决策点入栈 79 if(head>tail)q[++tail]=(node){i,n,i}; 80 else{ 81 //否则,我们就要去寻找新决策点的作用范围 82 //x返回新决策点范围的起点 83 int x=find(q[tail],i); 84 //旧决策点的尾部置为x-1 85 q[tail].r=x-1; 86 //将新决策点入栈 87 q[++tail]=(node){x,n,i}; 88 } 89 } 90 } 91 } 92 93 int main(){ 94 scanf("%d",&T); 95 while(T--){ 96 scanf("%d%d%d",&n,&l,&p); 97 for (int i=1;i<=n;i++) scanf("%s",ch),sum[i]=sum[i-1]+strlen(ch); 98 dp(); 99 if(f[n]>MAX) 100 puts("Too hard to arrange"); 101 else 102 printf("%lld\n",(long long)f[n]); 103 puts("--------------------"); 104 } 105 return 0; 106 }
代码对应的分析