给定一个序列,你可以进行一些操作,每个操作是把序列中相邻的k个数和成一个数,那个数就是这k个数的和,操作的费用就是co[k],问把序列合成一个对称的回文序列所需要的最少花费。任意一个元素只能被合成一次,也就是说,他不会用合成出来的结果再去和其他数合成。
首先处理最左边的元素和最右边的元素,可以发现,如果他们两个值不一样,那么较小的元素一定会和他旁边的元素合并。重复处理,直到两个元素相等,再重复处理除去这两个元素以后剩下的序列。
这样我们就可以得到一个新的序列,这个序列是回文的,并且每个元素都是之前的若干个元素组成的。如果这个序列是偶数个,那么我们在这个序列的正中间插入一个0,认为它是由之前的0个元素组成的,将其转化为一个奇数个元素的序列。
然后我们在考虑合并这个新序列,合并的方法即为左边和右边对称的做相同的操作。注意中间的那块要单独处理。
计算出将任意段合并成一段所需要的费用后,就可以用dp计算出整个的最小费用了。
#include <cstdio>
#include <cstring>
const int INF=~0u>>1;
inline void update(int &a,int b) {
if (a>b) a=b;
}
int a[5000];
int co[5001];
int lna[2500];
int rna[2500];
int numa[5001];
int cost[2501][2501];
int dp[2501];
int n,newn,m;
void calNewA() {
int i=0,j=n-1,lnum=0,rnum=0,nn=0;
long long lsum=0,rsum=0;
while (i<=j) {
if (lsum==rsum&&lsum!=0) {
lna[nn]=lnum;
rna[nn]=rnum;
nn++;
lsum=rsum=0;
lnum=rnum=0;
} else if (lsum==rsum&&lsum==0) {
lsum+=a[i++];
lnum++;
rsum+=a[j--];
rnum++;
} else if (lsum<rsum) {
lsum+=a[i++];
lnum++;
} else if (lsum>rsum) {
rsum+=a[j--];
rnum++;
}
}
if (i==j+1) {
if (lsum==rsum) {
lna[nn]=lnum;
rna[nn]=rnum;
nn++;
numa[nn]=0;
} else {
numa[nn]=lnum+rnum;
}
} else if (i==j+2) {
numa[nn]=1;
}
for (i=0;i<nn;i++) {
numa[i]=lna[i];
numa[nn+nn-i]=rna[i];
}
m=nn;
newn=nn+nn+1;
}
void calCost() {
int i,j;
for (i=0;i<m;i++) {
int lnum=lna[i],rnum=rna[i];
cost[i][i]=co[lnum]+co[rnum];
for (j=i+1;j<m;j++) {
lnum+=lna[j];
rnum+=rna[j];
cost[i][j]=co[lnum]+co[rnum];
}
}
int num=numa[m];
cost[m][m]=co[num];
for (i=m-1;i>=0;i--) {
num+=lna[i]+rna[i];
cost[i][m]=co[num];
}
}
int main() {
int i,j;
while (scanf("%d",&n),n) {
for (i=0;i<n;i++) scanf("%d",&a[i]);
for (i=1;i<=n;i++) scanf("%d",&co[i]);
co[0]=co[1]=0;
calNewA();
calCost();
/*
for (i=0;i<m;i++) {
printf("%d %d\n",lna[i],rna[i]);
}
printf("== %d\n",numa[m]);
for (i=0;i<=m;i++) {
for (j=0;j<=m;j++)
printf("%d ",cost[i][j]);
printf("\n");
}
*/
dp[0]=cost[0][0];
for (i=1;i<=m;i++) {
dp[i]=cost[0][i];
for (j=0;j<i;j++) update(dp[i],dp[j]+cost[j+1][i]);
}
printf("%d\n",dp[m]);
}
return 0;
}