Hihocoder #1636 : Pangu and Stones(2017 ACM-ICPC北京站J题)

我校太弱并没有争取到去现场赛的机会,所以只是跟风打了一个网络同步赛。根据榜单来看J题应该是一个稳铜牌的题目。
Hihocoder#1636


题目大意

n堆石子摆成一排,每次可以至少选取x个,最多选取y个连续的石子合为一堆,代价为选取的所有石子的个数和。问把所有石子合为一堆的代价最小为多少,若不能完成输出0. (1<=n<=100,组数不超过110,最多5组数据N>=50)


本题DP特征还是非常明显的。但是在打网络赛的时候看见了n<=100,Test case也有100多,总是以为这是一个N^3的题目,没敢往N^4上想。但是赛后补过题,又想了想开局坑爹的猫吃鱼的题,看到了数据量的提示,感觉自己实在是太迷信复杂度计算,这个毛病得治。


思路历程

    拿到这道题第一反应不能想到至少要划分两个状态来表示区间的两个端点。困难的地方在于如何解决至少拿x堆石子,最多拿y堆石子进行合并。

    最初的想法是对于每一个区间(L,R)设置dp[l][r]表示将其合为一堆的最小花费。但是合并(L,R)后,对于包含(L,R)的区间(LL,RR)石子堆数发生了变化,影响了后续的合并操作。但是又局限于复杂度的计算,没敢细分状态,一度还以为题目的正解是一个脑洞巨大的DP套二分???至此思维陷入了僵局,开了计算几何题。

    实际上状态还可以细分,那么肯定是要按照区间DP的经典形式要再加上一维表示表示区间内的堆数这个重要的信息。这时本ZZ想到的第一个的状态转移是,枚举(i,j)区间内的分界线k,分为(i,k)区间和(k+1,j)区间,再枚举堆数p,转移方程为dp[i][j][p]=min(dp[i][k][a]+dp[k+1][j][b]),(a+b==p)。但是这样想来N^3个状态,每次转移复杂度N^2,N^5复杂度是肯定会超时的,并且好像不能考虑到合并堆数的限制,遂设法改进。

    一个区间内dp[i][j][p]涉及到的状态转移有两种:
    1)由子区间的状态dp[i][k][a]+dp[k+1][j][b] (a+b==p)得到。初次想到这里可能还需要枚举a。但转而一想,(i,j)区间内的堆数p总可以看成(i,k)的p-1堆再加上(k+1,j)的1堆组合成。这样可以把方程优化为dp[i][j][p]=dp[i][k][p-1]+dp[k+1][j][1](好吧其实很多大佬上来就是这么想的=.=)
    2)当l<=p<=r时,dp[i][j][p]的状态可以支付价格转移到dp[i][j][1]。
    这两种状态转移一定要先考虑第一种,在考虑第二种.把一个区间内的石子合为一堆支付的代价是不变的(区间内的石子总数不会变),但达到可以合并的条件时价格可以找最小的。

    初始化的时候也需要注意边界条件。具体见Code吧。
    代码写得丑,见谅。。。

代码

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int maxn=120;
const long long inf=1LL<<60;
long long dp[maxn][maxn][maxn];
long long pre[maxn];

long long sum(int l,int r)
{
    return pre[r]-pre[l-1];
}

void print_state(int n)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
            for(int k=1;k<=j-i+1;k++)
                cout<" "<" "<" "<int main()
{
    int n,l,r;
    while(scanf("%d %d %d",&n,&l,&r)!=EOF)
    {
        pre[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&pre[i]);
            pre[i]+=pre[i-1];
        }

        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                for(int k=1;k<=n;k++)
                    dp[i][j][k]=inf;

        for(int len=l;len<=r;len++)
        {
            for(int l=1;l+len-1<=n;l++)
            {
                int r=l+len-1;
                dp[l][r][len]=0;
                dp[l][r][1]=sum(l,r);
            }
        }
        for(int i=1;i<=n;i++)
            dp[i][i][1]=0;

//        print_state(n);
        for(int len=2;len<=n;len++)
        {
            for(int i=1;i+len-1<=n;i++)
            {
                int j=i+len-1;
                for(int k=i;kfor(int p=1;p<=len;p++)
                        dp[i][j][p+1]=min(dp[i][j][p+1],dp[i][k][p]+dp[k+1][j][1]);
                }
                for(int k=l;k<=r;k++)
                    dp[i][j][1]=min(dp[i][j][1],dp[i][j][k]+sum(i,j));
            }
        }
        if(dp[1][n][1]==inf)
            dp[1][n][1]=0LL;
        printf("%lld\n",dp[1][n][1]);
    }
    return 0;
}

你可能感兴趣的:(hiho刷题日记)