1. 程式人生 > >洛谷 P1880 石子合並

洛谷 P1880 石子合並

表示 不同 oid stdio.h 其他 += int 我們 ber

題目描述

在一個園形操場的四周擺放N堆石子,現要將石子有次序地合並成一堆.規定每次只能選相鄰的2堆合並成新的一堆,並將新的一堆的石子數,記為該次合並的得分。

試設計出1個算法,計算出將N堆石子合並成1堆的最小得分和最大得分.

輸入輸出格式

輸入格式:

數據的第1行試正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.

輸出格式:

輸出共2行,第1行為最小得分,第2行為最大得分.

輸入輸出樣例

輸入樣例#1:
4
4 5 9 4
輸出樣例#1:
43
54

解題思路

  由於是環形的,所以我們把石子復制一遍,比如5堆石子,從第4堆環形合並到第3堆(4、5、1、2、3),復制後石子編號就是4 5 6 7 8了,復制一遍就能拆環,直接當做兩倍長的鏈處理。

  在這條鏈上,用動規數組f[j][i](代碼風格太迷,一不小心反了)表示第i堆到第j堆所得積分的極值,它等於i到j的石子總數加已得積分的極值。比如要求2~5的積分,我們可以通過這麽幾種方式合並出2~5—— 2+3~5 2~3+4~5 2~4+5。求出這幾種方式哪種能得到最多或最少的積分,一路求下去直到全部合成一堆為止。

源代碼

#include<stdio.h>
#include<string.h>
int a[210]={0};
int f[210][210]={0};
int s[210]={0};
int n,ans;
inline 
int max(int a,int b) { return a>b?a:b; } inline int min(int a,int b) { return a<b?a:b; } void print() { for(int i=1;i<=n<<1;i++) { for(int j=1;j<=n<<1;j++) printf("%4d",f[i][j]); printf("\n"); } printf("\n"); }
int main() { //freopen("test.in","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",a+i),s[i]=s[i-1]+a[i]; for(int i=n+1;i<=n<<1;i++) s[i]=s[i-1]+a[i-n]; memset(f,0,sizeof(f)); for(int i=2;i<=n;i++) { int c=i-1; for(int j=1;j<=(n<<1)-c;j++) { int minn=999999999; f[j+c][j]=s[j+c]-s[j-1]; if(c==1) minn=0; for(int k=0;k<c;k++) minn=min(minn,f[j+k][j]+f[j+c][k+j+1]); f[j+c][j]+=minn; }//print(); } ans=999999999; for(int i=n;i<=n<<1;i++) ans=min(ans,f[i][i-n+1]); printf("%d\n",ans); memset(f,0,sizeof(f)); for(int i=2;i<=n;i++) { int c=i-1; for(int j=1;j<=(n<<1)-c;j++) { int maxn=-1; f[j+c][j]=s[j+c]-s[j-1]; if(c==1) maxn=0; for(int k=0;k<c;k++) maxn=max(maxn,f[j+k][j]+f[j+c][k+j+1]); f[j+c][j]+=maxn; }//print(); } ans=-1; for(int i=n;i<=n<<1;i++) ans=max(ans,f[i][i-n+1]); printf("%d",ans); return 0; }

Ps:

  這題半年前就想寫了,但由於碼力不足,一直沒動,今晚總算把它a了。

  現在時間:2017年06月03日02:26:43。淩晨做題真有意思,總是想睡覺又想切了這題再睡。

  由於狀態轉移掌握不太熟,一個狀態從哪些情況轉移過來(代碼裏的第三重循環),腦子不清楚,於是那段我是"調參"調出來的,我這種寫法細節要註意的太多了……網上其他題解的狀態轉移寫的真心簡潔,循環方式也不同,代碼量就少了很多,以後還要多多學習呀

洛谷 P1880 石子合並