[USACO09JAN] 气象测量/气象牛The Baric Bovine 解题报告(DP)

题目链接:https://www.luogu.org/problemnew/show/P2933

Description
  为了研究农场的气候,Betsy帮助农夫John做了N(1 <= N <= 100)次气压测量并按顺序记录了结果M_1...M_N(1 <= M_i <= 1,000,000).
  Betsy想找出一部分测量结果来总结整天的气压分布. 她想用K(1 <= K <= N)个数s_j
(1 <= s_1 < s_2 < ... < s_K <= N)来概括所有测量结果. 她想限制如下的误差:
  对于任何测量结果子集,每一个非此子集中的结果都会产生误差.总误差是所有测量结果的误差之和.更明确第说, 对于每一个和所有s_j都不同的i:
  * 如果 i 小于 s_1, 误差是:2 * | M_i - M_(s_1) | 
  * 如果i在s_j和s_(j+1)之间,误差是:| 2 * M_i - Sum(s_j, s_(j+1)) | 
  注:Sum(x, y) = M_x + M_y; (M_x 和 M_y 之和)
  * 如果i大于s_K,误差为:2 * | M_i - M_(s_K) |
  Besty给了最大允许的误差E (1 <= E <= 1,000,000),找出最小的一部分结果使得误
差最多为E.
 
Input
  第一行: 两个空格分离的数: N 和 E
  第2..N+1行: 第i+1行包含一次测量记录:M_i
Output
  第一行: 两个空格分开的数: 最少能达到误差小于等于E的测量数目和使用那个测量数目能达到的最小误差.
 
Sample Input
4 20
10
3
20
40
Sample Output
2 17

题目大意就是给你一个集合,告诉你如何判定它的子集是否合法并让你找到一个最优子集

解法:

首先我们预处理出一个数组pre,pre[i][j]保存在i到j之间元素对误差的贡献,即我们枚举z(j-1>=z>=i+1),计算abs(2*m[z]-m[i]-m[j])

特殊的是,我们还需要处理出pre[i][0]和pre[i][n+1],分别表示在i之间和在i之后的元素对误差的贡献(感觉贡献这个词怪怪的)

预处理时间复杂度O(n3)

考虑如何DP

定义DP[i][j]表示前j个元素,必选第j个元素,总共选择了i个产生的最小误差。为什么把i放在前,j放在后呢?因为我们首先最小化的是子集的大小。状态转移方程就是:

dp[i][j]=min(dp[i][j],dp[i-1][q]+sum)(i-1<=q<=j)

我们有sum=-pre[q][n+1]+pre[q][j]+pre[j][n+1](之前我们是把q当成是子集的结尾并加上了它之后对误差的贡献,于是此时我们减去这个值改为用j来作为最后一个元素)

DP时间复杂度O(n3)

注意i=1的情况我们提前处理出来就是了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<math.h>
#define ri register int 
#define ll long long
using namespace std;

const int maxn=100+15;
const int inf=0x3f3f3f3f;
int n,e,k;
ll ans;
int m[maxn];
ll dp[maxn][maxn],pre[maxn][maxn];
int main()
{    
    scanf("%d%d",&n,&e);
    for (ri i=1;i<=n;i++)
    scanf("%d",&m[i]);
    for (ri i=1;i<=n;i++)
    {
        for (ri j=i+1;j<=n;j++)
            for (ri k=i+1;k<=j-1;k++)
                pre[i][j]+=abs(2*m[k]-m[i]-m[j]);
        for (int j=1;j<i;j++) pre[i][0]+=2*abs(m[j]-m[i]);
        for (int j=i+1;j<=n;j++) pre[i][n+1]+=2*abs(m[j]-m[i]);
    }
    k=n;ans=inf;
    for (ri i=1;i<=n;i++)
    {
        dp[1][i]=pre[i][0]+pre[i][n+1];
        if (dp[1][i]<=e) 
        {
            k=1;
            if (dp[1][i]<ans) ans=dp[1][i];
        }
    }
    for (ri i=2;i<=n;i++)
    {
        for (ri j=i;j<=n;j++)
        {    
            dp[i][j]=inf;
            for (ri q=i-1;q<j;q++)
            {
                int sum=-pre[q][n+1]+pre[q][j]+pre[j][n+1];
                dp[i][j]=min(dp[i][j],dp[i-1][q]+sum);
            }
            if (dp[i][j]<=e) 
            {
                if (i<k) {k=i;ans=dp[i][j];}
                if (i>k) continue;
                if (i==k) ans=min(ans,dp[i][j]);
            }
        }
    }
    printf("%d %lld\n",k,ans);
    return 0;
}

 

转载于:https://www.cnblogs.com/xxzh/p/9295156.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值