Polygon
题意:
“多边形游戏” 是一款单人益智游戏。在游戏开始时,系统给定玩家一个N边形,该N边形由N个顶点和N条边构成,每条边连接两个相邻的顶点。在每个顶点上写有一个整数,可正可负。在每条边上标有一个运算符“+”(加号)或“*” (乘号)。
第一步,玩家需要选择一条边,将它删除。接下来再进行N-1步,在每一步中玩家选择一条边, 把这条边以及该边连接的两个顶点用一个新的顶点代替,新顶点上的整数值等于删去的两个顶点上的数按照删去的边上标有的符号进行计算得到的结果。最终剩下一个顶点,顶点上的数值就是玩家的得分。
请计算对于给定的N边形,玩家最高能获得多少分,以及第一步有哪些策略可以使玩家获得最高得分。
1≤N≤50,保证玩家无论如何操作,顶点上的数值均在[- 32768,32767]之内。
题解:
首先容易想到枚举删除每一条边,dp[l][r]表示把第l到r的顶点合成之后最大的数值。但是把顶点上最大的数值作为子问题的解对于这一题是不行的,因为最大的数值也可以是2个最小的负数相乘得到,但是如果把最大和最小值同时作为子问题的信息,就可以满足最优子结构了。
最大值只能是2个最大值相加或者相乘,或者2个最小值相乘。
最小值只能是2个最小值相加或者相乘,或者一个最大值和一个最小值相乘,或者2个最大值相乘(当2个子区间的最大最小值全为负数时)。
dp[l][r][0] 为区间[l,r] 的最大值, dp[l][r][1] 为区间[l,r] 的最小值;
dp[l,r,0]=maxl≤k<r{dp[l,k,0] op dp[k+1,r,0]op∈{+,*} dp[l,k,1] ∗ dp[k+1,r,1]
dp[l,r,0] = \max\limits_{ l \le k<r } \left\{ \begin{array}{ll}
dp[l,k,0 ]\,op \,dp[k+1,r,0] & \textrm{op$\in $\{+,*\} }\\
dp[l,k,1 ]\,* \,dp[k+1,r,1]
\end{array} \right.
dp[l,r,0]=l≤k<rmax{dp[l,k,0]opdp[k+1,r,0]dp[l,k,1]∗dp[k+1,r,1]op∈{+,*}
dp[l,r,1]=minl≤k<r{dp[l,k,1] op dp[k+1,r,1]op∈{+,*} dp[l,k,0] ∗ dp[k+1,r,1]dp[l,k,1] ∗ dp[k+1,r,0]dp[l,k,0] ∗ dp[k+1,r,0]
dp[l,r,1] = \min\limits_{ l \le k<r } \left\{ \begin{array}{ll}
dp[l,k,1 ]\,op \,dp[k+1,r,1] & \textrm{op$\in $\{+,*\} }\\
dp[l,k,0 ]\,* \,dp[k+1,r,1] \\
dp[l,k,1 ]\,* \,dp[k+1,r,0] \\
dp[l,k,0 ]\,* \,dp[k+1,r,0] \\
\end{array} \right.
dp[l,r,1]=l≤k<rmin⎩⎪⎪⎨⎪⎪⎧dp[l,k,1]opdp[k+1,r,1]dp[l,k,0]∗dp[k+1,r,1]dp[l,k,1]∗dp[k+1,r,0]dp[l,k,0]∗dp[k+1,r,0]op∈{+,*}
上述算法的时间复杂度为O(N4N^4N4)。实际上我们还可以进一步优化掉枚举第一步删除哪条边耗费的
时间。在游戏最初,我们任意选择一条边删除, 然后把剩下的“链”复制一倍接在末尾(以被删除的边逆时针方向的第一个顶点为开头),在这个长度为2N的 “链”上,Vi∈[1,N],把长度为N的区间[i,i+N-1]合并成一个顶点,就等价于原游戏的第一步删除第 i个顶点逆时针一侧的边,然后把剩余的部分合并成一个顶点。 因为区间长度是DP的阶段,我们只需要对前N个阶段进行DP,每个阶段只有不超过2N个状态,总时间复杂度降低为0(N3)。 最后的答案是 max1≤i≤Nf[i,i+N−1]\max\limits_{1\le i\le N} f[i,i+N-1]1≤i≤Nmaxf[i,i+N−1];
这种 “任意选择一个位置断开, 复制形成2倍长度的链”的方法,是解决DP中环形结构的常用手段之一。
AC代码:
#include <iostream>
using namespace std;
#define ll long long
const int maxn = 2e2;
int dp[maxn][maxn][2];
char op[maxn];
int num[maxn];
int main()
{
// ios::sync_with_stdio(false);
// freopen("in.txt", "r", stdin);
int n;
cin >> n;
for(int i = 1; i <= n; i++)
cin >> op[i - 1] >> num[i];
for(int i = n + 1; i <= 2 * n; i++)
{
op[i - 1] = op[i - n - 1];
num[i] = num[i - n];
}
// for(int i = 1;i<=2*n;i++)cout<<num[i]<<" "<<op[i]<<" ";
for(int i = 0; i <= 2 * n; i++)
{
for(int j = 0; j <= 2 * n; j++)
{
dp[i][j][0] = -0x3f3f3f3f;
dp[i][j][1] = 0x3f3f3f3f;
}
}
for(int i = 1; i <= 2 * n; i++) dp[i][i][0] = dp[i][i][1] = num[i];//初始化边界
for(int len = 2; len <= n; len++)//阶段
{
for(int l = 1; l <= 2 * n - len + 1; l++)//状态:左端点
{
int r = l + len - 1;//右端点
for(int k = l; k < r; k++)//决策
{
int maxx = -0x3f3f3f3f;
// cout<< dp[l][k][0] <<" "<< dp[k + 1][r][0]<<endl;
if(op[k] == 't')
maxx = max(maxx, dp[l][k][0] + dp[k + 1][r][0]);
else maxx = max(maxx, dp[l][k][0] * dp[k + 1][r][0]);
if(op[k] == 'x')
maxx = max(maxx, dp[l][k][1] * dp[k + 1][r][1]);
maxx = max(maxx, dp[l][r][0]);
dp[l][r][0] = maxx;
int minn = 0x3f3f3f3f;
if(op[k] == 't')
minn = min(minn, dp[l][k][1] + dp[k + 1][r][1]);
else minn = min(minn, dp[l][k][1] * dp[k + 1][r][1]);
if(op[k] == 'x')
{
minn = min(minn, dp[l][k][1] * dp[k + 1][r][0]);
minn = min(minn, dp[l][k][0] * dp[k + 1][r][1]);
minn = min(minn, dp[l][k][0] * dp[k + 1][r][0]);
}
minn = min(minn, dp[l][r][1]);
dp[l][r][1] = minn;
}
}
}
int ans = -0x3f3f3f3f;
for(int i = 1; i <= n; i++)
{
ans = max(dp[i][i + n - 1][0], ans);
ans = max(dp[i][i + n - 1][1], ans);
}
cout << ans << endl;
int i;
for(i = 1; i <= n; i++)
if(dp[i][i + n - 1][0] == ans || dp[i][i + n - 1][1] == ans)
{
cout << i;
break;
}
i++;
for(; i <= n; i++)
if(dp[i][i + n - 1][0] == ans || dp[i][i + n - 1][1] == ans)
cout << " " << i;
cout << endl;
return 0;
}