[IOI1998] Polygon (区间dp,和石子合并很相似)

题意:

给你一个多边形(可以看作n个顶点,n-1条边的图),每一条边上有一个符号(+号或者*号),这个多边形有n个顶点,每一个顶点有一个值

最初你可以把一条边删除掉,这个时候这就是一个n个顶点,n-2条边的图

如果顶点i和j之间有边,那么你可以把i点和j点合并成一个点,把这个点替换掉i、j两个点,这个新点的值是i+j 或者 i*j (这个是要看连接i和j两点的边上的符号)

 

经过若干次操作之后剩下一个点,这个点的值最大是多少

 

题解:

这道题目和石子合并题目很相似,这里先说一下石子合并

题目:

有N堆石子围成一个圆,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

 

我们来分析一下,给你 4 堆石子:4,5,6,7,按照下图所示合并

 

 

 

这种合并方式的花费为:4+5+9+6+15+7=46

这种合并也就是4->5->6->7按照这个方向进行

 

那我们先合并前3堆石子:4+5+9+6

这个花费就是:第一堆和第二堆合并的花费+第一堆和第二堆合并之后的数量+第三堆的数量

那我们先合并前4堆石子:4+5+9+6+15+7

这个花费就是:第一堆和第二堆和第三堆合并的花费(也就是4+5+9+6)+第一堆和第二堆和第三堆合并之后的数量+第四堆的数量

 

大问题分解成了小问题,而且大问题包含了小问题的解,大问题和小问题同质同解。完完全全满足dp的性质。

剩下的就是从中找到最优解,因为当前是从1->2->3->4的顺序去合并的,还有(1->2)->(3->4)或者1->(2->3->4)的顺序,原理一样,这里面因为第二第三部分是固定的,所以只需要得到小问题的最优解,就可以求得大问题的最优解了。这一点我们可以把原序列复制一份追加到原序列尾部,然后使用区间dp枚举就可以了

 

这里给出石子合并问题求最大花费的代码:dp[i][j]表示:从石子i合并到石子j所能得到的最大花费

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=450;//区间长度为2*n
 4 int dp[maxn][maxn];
 5 int sum[maxn];//前缀和数组
 6 int a[maxn];//每堆石子的个数
 7 int main()
 8 {
 9     int n,x;
10     sum[0]=0;
11     while(scanf("%d",&n)!=EOF){
12         memset(dp,0,sizeof(dp));
13         memset(sum,0,sizeof(sum));
14         for(int i=1;i<=n;i++){
15             scanf("%d",&a[i]);
16             sum[i]=sum[i-1]+a[i];//预处理
17             dp[i][i]=0;
18         }
19  
20         int in=1;//首尾相连之后
21         for(int i=n+1;i<=2*n;i++)
22             sum[i]+=(sum[i-1]+a[in++]);
23  
24         for(int len=2;len<=n;len++)//枚举区间长度
25         {
26             for(int i=1;i<=2*n;i++)//枚举区间起点
27             {
28                 int j=i+len-1;//区间终点
29                 if(j>n*2) break;//越界结束
30                 for(int k=i;k<j;k++)//枚举分割点,构造状态转移方程
31                 {
32                     dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[j]-sum[i-1]));
33                 }
34             }
35         }
36         int MAX=-1;
37         for(int i=1;i<=n;i++)//枚举环状序列的起点,长度为n
38         MAX=max(dp[i][i+n-1],MAX);//求最大值
39         printf("%d\n",MAX);
40     }
41     return 0;
42 }
View Code

 

 

 

 

回归原题,这个题目如果所有边的符号都是+号,那就和石子合并一样了

这里多了一个*号,那么两个很小的负数相乘也可以得到一个很大的值,这样的话,我们可以开一个三维dp,dp[i][j][k]:如果k==1,那就保存的是dp[i][j]的max

如果k==0,那就保存dp[i][j]的最小值

 

其他和正常区间dp一样

 

代码:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define mem(a) memset(a,0,sizeof(a))
typedef long long ll;
const int maxn=55;
const int INF=0x3f3f3f3f;
const double blo=(1.0+sqrt(5.0))/2.0;
const double eps=1e-8;
struct shudui
{
    int a,b;
}que[maxn];
int dp[maxn][maxn][2],num[maxn],str[maxn];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        char s[maxn];
        for(int i=1;i<=2*n;++i)
        {
            if(i%2)
            {
                scanf("%s",s);
                str[i/2]=s[0];
            }
            else 
            {
                scanf("%d",&num[(i-1)/2]);
            }
        }
        str[n]=str[0];
        for(int i=0;i<n;++i)
        {
            for(int j=0;j<n;++j)
            {
                if(i==j)
                    dp[i][j][0]=dp[i][j][1]=num[i];
                else 
                {
                    dp[i][j][0]=INF;
                    dp[i][j][1]=-INF;
                }
            }
        }
        for(int len=1;len<n;++len)
        {
            for(int i=0;i<n;++i)
            {
                //int j=(i+len)%n;
                int j=(i+len);
                for(int k=i;k<j;++k)
                {
                    if(str[(k+1)%n]=='t')
                    {
                        dp[i][j%n][1]=max(dp[i][j%n][1],dp[i][k%n][1]+dp[(k+1)%n][j%n][1]);
                        dp[i][j%n][0]=min(dp[i][j%n][0],dp[i][k%n][0]+dp[(k+1)%n][j%n][0]);
                    }
                    else 
                    {
                        dp[i][j%n][1]=max(dp[i][j%n][1],dp[i][k%n][1]*dp[(k+1)%n][j%n][1]);
                        dp[i][j%n][1]=max(dp[i][j%n][1],dp[i][k%n][0]*dp[(k+1)%n][j%n][0]);
                        dp[i][j%n][0]=min(dp[i][j%n][0],dp[i][k%n][0]*dp[(k+1)%n][j%n][1]);
                        dp[i][j%n][0]=min(dp[i][j%n][0],dp[i][k%n][1]*dp[(k+1)%n][j%n][0]);
                        dp[i][j%n][0]=min(dp[i][j%n][0],dp[i][k%n][0]*dp[(k+1)%n][j%n][0]);
                    }
                }
            }
        }
        //printf("%d %d %d %d\n",dp[1][2][1],dp[2][1][1],dp[2][3][1],dp[3][2][1]);
        int maxx=-INF,index=0;
        for(int i=0;i<n;++i)
        {
            maxx=max(maxx,dp[i][(i+n-1)%n][1]);
        }
        for(int i=0;i<n;++i)
        {
            if(dp[i][(i+n-1)%n][1]==maxx)
            {
                //printf("%d %d\n",i,(i+n-1)%n);
                que[index++].a=i+1;
                //que[index++].b=max((i+n-1)%n,i)+1;
                //que[index++].a=i+1;
            }
        }
        printf("%d\n",maxx);
        for(int i=0;i<index;++i)
        {
            //printf("%d %d\n",que[i].a,que[i].b);
            if(i==index-1)
            printf("%d\n",que[i].a);
            else printf("%d ",que[i].a);
        }
    }
    return 0;
}

 

posted @ 2020-09-12 15:53  kongbursi  阅读(144)  评论(0编辑  收藏  举报