• [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
    ",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
    ",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
    ",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
    ",maxx);
            for(int i=0;i<index;++i)
            {
                //printf("%d %d
    ",que[i].a,que[i].b);
                if(i==index-1)
                printf("%d
    ",que[i].a);
                else printf("%d ",que[i].a);
            }
        }
        return 0;
    }
  • 相关阅读:
    在awk里引用shell变量(支持正则)
    python模块pyautogui
    一个完整的搜索系统
    信息检索笔记(9)再论文档评分
    信息检索导论学习笔记(8)向量空间模型
    搜索引擎查询扩展
    信息检索笔记(10)Lucene文档评分机制
    Lucene的分析过程
    信息检索导论学习笔记(7)文档评分、词项权重计算
    信息检索导论学习笔记(5)
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/13657321.html
Copyright © 2020-2023  润新知