此题意思很清楚,通过不停的把2个点通过计算变成一个点。顺序自选,问你得出来最大的值是多少,是最优矩阵链乘的升级版。
需要枚举断开的边,然后下面再和最优矩阵链乘一样的递推公式dp[i][j] = min{dp[i][k]乘或加dp[k+1][j]},但是。怎么写是错的。。
我也是犯了这个错,为什么是错,加法肯定是对的,但是乘法就不一样了,因为最大值可能是由2个最小值乘起来得到的,所以必须要用2个DP来分别保存最大值和最小值。最小值得到有2种方式,如果全是正数或者全是负数,那么就是2个最小的得到,如果有正有负,则是左边最小乘右边最大或者是左边最大乘右边最小。
还要注意的一点是因为断开的地方不确定,而每个点的标号都是确定的,所以就会有从最后一个到第一个这种情况,要用取模,具体怎么做可以看代码,或者自己想。
还有一点。我第一次AC的时候用的是每一次确定一个新的断开的边的时候,重新从头开始推,后来我改成如果dp[i][j]的数字不等于初始值-1了,那么就不推这个了,也AC了。本来我觉得不太对,后来我仔细想了想,因为是顺时针推的,所以假设断开一条边后,边2头的数字顺时针分别是i和j,算出dp[i][j],换一条边后,如果还要求i和j的dp值,那么只可能是dp[j][i]了,故是不会出现最优解遗漏的情况的。而且还可以节约很多中间多余推的时间。
AC代码:
#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<ctime>
using namespace std;
#define NMAX 100005
#define ll long long
int a[100];
int dmax[100][100],record[100],dmin[100][100];
int main()
{
int n,i,j,d,k;
while(~scanf("%d\n",&n))
{
memset(dmax,-1,sizeof(dmax));
for(i = 0; i < n; i++)
{
char temp;
scanf("%c %d ",&temp,&dmax[i][i]);
dmin[i][i] = dmax[i][i];
if(temp == 't') a[i] = 1;
else a[i] = 0;
}
int ans1,ans2,tt=-NMAX,cnt;
for(d = 0; d < n; d++)
{
for(i = 1; i < n; i++)
for(j = d; j+i < d+n; j++)
{
ans1 = -NMAX;ans2 = NMAX;
if(dmax[j%n][(j+i)%n] != -1) continue;
for(k = j; k < j+i; k++)
{
if(a[(k+1)%n])
{
ans1 = max(ans1,dmax[j%n][k%n]+dmax[(k+1)%n][(j+i)%n]);
ans2 = min(ans2,dmin[j%n][k%n]+dmin[(k+1)%n][(j+i)%n]);
}
else
{
ans1 = max(ans1,dmax[j%n][k%n]*dmax[(k+1)%n][(j+i)%n]);
ans1 = max(ans1,dmin[j%n][k%n]*dmin[(k+1)%n][(j+i)%n]);
ans2 = min(ans2,dmin[j%n][k%n]*dmin[(k+1)%n][(j+i)%n]);
ans2 = min(ans2,dmax[j%n][k%n]*dmin[(k+1)%n][(j+i)%n]);
ans2 = min(ans2,dmin[j%n][k%n]*dmax[(k+1)%n][(j+i)%n]);
}
}
dmax[j%n][(j+i)%n] = ans1;
dmin[j%n][(j+i)%n] = ans2;
}
if(ans1>=tt)
{
if(ans1 == tt&&cnt)
record[cnt++] = d+1;
else
{
tt = ans1;
cnt = 0;
record[cnt++] = d+1;
}
}
}
printf("%d\n",tt);
for(i = 0; i < cnt; i++)
printf("%d ",record[i]);
printf("\n");
}
return 0;
}