• P5336


    一个挺神仙的区间 DP,细想起来又其实是非常平凡的,毕竟每一步撕烤都有迹可循。听说原来好像是个黑题/jy,0rz〇rz


    先来探讨一下方案长什么样子。取了一个区间后,以后取的区间要么与该区间无交,要么包含它(真正取的元素使它的补)。所以方案呈一个以包含关系为纽带的树形结构,根是 ([1,n]),每个区间取的元素为它减去它的若干直接包含的区间。没有相交,这就很适合转化为子问题。

    考虑在初始的全局局面 ([1,n]) 上做出一步决策。看起来比较难:可决策的只有若干直接包含的区间,以及剩下的元素。枚举最后一个直接包含的区间的左右端点的话,其内部可以归约到子问题,但是外部变成了 ([1,n]-I),变成一个四不像的东西。要按照此思路一直决策下去的话,会得到 (2^n) 个状态,封闭不住直接爆炸。于是只能考虑决策后者。

    但是后者看上去更难决策,这枚举都没法枚举。但是注意到剩下来的元素的代价至于 min 和 max 有关,于是我们可以直接枚举 min 和 max(以下认为值域大小也是 (n),离散化即可)!先考虑确实剩下来至少一个元素的情况,这样转移所需要的信息是 ([1,n]) 中选掉一些直接包含的区间,使得剩下来元素全部 (in[mn,mx]),最小代价(代价其实就是每个直接包含的区间作为子问题的答案之和)为多少。这看起来就是个挺常规的区间 DP 了。以及直接包含的区间作为子问题的答案需要用到所有区间 ([l,r]) 答案,此时我们正式设计状态:(dp_{l,r}) 表示子问题 ([l,r]) 的答案,(f_{l,r,i,j}) 表示 ([l,r]) 中只剩 ([i,j]) 内元素的最小代价。目标 (dp_{1,n}),转移的话他们两个互相转移,相辅相成,缺一不可

    (f) 的转移的话,对 (r) 进行决策,分两种情况:若 (a_rin[i,j]),则 (r) 可以剩下,(r) 往左移一格;(r) 不剩下而作为直接包含的区间的元素的话,寻找左端点 (k),转移到 (f_{l,k-1,i,j}+dp_{k,r})(dp) 的转移,当剩下至少一个元素时,枚举 min 和 max (i,j),转移到 (f_{l,r,i,j}+A+B(i-j)^2)。当一个元素都不剩的时候,此时转移是容易的,一个复杂的想法是另搞一个 DP 专门表示一个元素都不剩的情况,然后枚举 (r) 所在直接包含区间的左端点进行转移,然后再给 (dp_{l,r}) chkmin。但其实可以直接在原 (dp) 上枚举断点 (i),这样虽然会误统计到 ([l,i-1]) 有元素剩下的情况,但无伤大雅,第一该统计的情况一个都没漏,第二多统计的情况并没有超出所有要统计情况的范围,跟据 max 的可叠加性,这不影响答案。

    最后有个小小的环状转移需要考虑一下:(f_{l,r,i,j})​ 直接转移到 (dp_{l,r})​,(dp_{l,r})​ 转移到 (f_{l,r,i,j}+A+B(i-j)^2)​。注意到当 (f_{l,r,i,j})​ 直接 chkmin (dp_{l,r})​ 时,情况是一个都不剩,这种情况并不需要被 (dp_{l,r})​ 此时转移时考虑。之所以 (f) 要进行这一步更新,是因为保证 ([l,r]) 的超区间的 (f) 值无误。所以我们可以先放弃 (f)​ 的这一步更新,把 (dp) 搞完之后再将 (f) 的这一步补上。

    总复杂度 (mathrm O!left(n^5 ight))

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define pb push_back
    const int inf=0x3f3f3f3f3f3f3f3f;
    const int N=60;
    int sq(int x){return x*x;}
    int n;
    int A,B;
    int a[N],b[N];
    vector<int> nums;
    void discrete(){
    	sort(nums.begin(),nums.end());
    	nums.resize(unique(nums.begin(),nums.end())-nums.begin());
    	for(int i=1;i<=n;i++)b[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin()+1;
    }
    int f[N][N][N][N];
    int dp[N][N],mn[N][N],mx[N][N];
    signed main(){
    	cin>>n>>A>>B;
    	for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
    	discrete();
    	for(int l=n;l;l--)for(int r=l;r<=n;r++){
    		mn[l][r]=inf;mx[l][r]=-inf;
    		for(int i=l;i<=r;i++)mn[l][r]=min(mn[l][r],a[i]),mx[l][r]=max(mx[l][r],a[i]);
    	}
    	for(int l=n;l;l--)for(int r=l;r<=n;r++){
    		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++){
    			f[l][r][i][j]=inf;
    			if(i<=b[r]&&b[r]<=j)f[l][r][i][j]=min(f[l][r][i][j],f[l][r-1][i][j]);
    			for(int k=l+1;k<=r;k++)f[l][r][i][j]=min(f[l][r][i][j],f[l][k-1][i][j]+dp[k][r]);
    		}
    		dp[l][r]=inf;
    		for(int i=l;i<=r;i++)dp[l][r]=min(dp[l][r],dp[l][i-1]+A+B*sq(mx[i][r]-mn[i][r]));
    		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)dp[l][r]=min(dp[l][r],f[l][r][i][j]+A+B*sq(nums[i-1]-nums[j-1]));
    		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)f[l][r][i][j]=min(f[l][r][i][j],dp[l][r]);
    	}
    	cout<<dp[1][n];
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    20000字干货笔记,一天搞定Mysql~【转】
    Linux操作系统概述及内核介绍
    如何在装有高版本NBU的主机上安装低版本的NBU?卸载8.0安装7.5记录
    vmware+kvm+vnc安装配置
    NBU异机恢复Oracle数据库,作业报错2850处理
    NetBackup 进程整理
    1、虚拟化实施流程、宿主机如何选型、如何进行性能测试
    灾难恢复的衡量指标RTO和RPO
    国内主要灾备厂商
    单例设计模式
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/Myratingis1064Myratingis1064Myratingis1064Myratingis1064Myratingis1064Myratingis1064Myratingis1064.html
Copyright © 2020-2023  润新知