测绘
测绘
Descrption
-
为了研究农场的气候,
Betsy
帮助农夫John
做了 \(N(1≤N≤100)\) 次气压测量并按顺序记录了结果\(M_1,M_2,…,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_{S1}|\) ;
- 如果 \(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)\) ,找出样本 \(k\) 最小的一部分结果使得误差最多为 \(E\).
Input
- 第一行:两个数 \(N,E\)(含义如题)。
- 接下来 \(N\) 行每行一个数表示气压。
Output
- 输出一行,两个数,第一表示最小的样本数 \(K\),第二个表示这 \(K\) 个样本的最小误差值
Sample Input
4 20
10
3
20
40
Sample Output
2 17
Hint
-
选择第二次和第四次的测量结果能达到最小误差\(17\) 。
-
分析:
-
看到这种题型,我们首先要考虑 \(dp\) ,看能不能找到最优子结构,典型的线性 \(dp\) 的状态定义为 \(dp[i]\) 表示前 \(i\) 个元素作为一个子问题,但选多少个数,这显然是限制条件,很容易定义 \(dp[i][j]\) ,表示前 \(i\) 个数选择 \(j\) 个数的最小误差。那如何转移呢?
-
首先我们要确定一个问题,就是第一维的前 \(i\) 是否包含第 \(i\) 个元素的问题,显然必须包含第 \(i\) 个元素,且为所选子序列的最后一个元素,即为 \(S_k\) ,这样我们才好转移。转移的决策就是从 \(i\) 往后找一个元素加入序列。
-
转移方程:\(dp[i][j]=min(dp[i][j],dp[k][j-1]+?),(j-1\le k<i)\) 。
- 首先我们预处理出一个数组\(pre\),\(pre[i][j]\) 表示原序列中\(M_i,M_j\) 是所选子序列的相邻的两个元素,则 \(i\) 到 \(j\) 之间元素对误差的贡献,枚举 \(k,(i+1\le k\le j-1)\) ,计算\(abs(2*M_k-M_i-M_j)\) ,显然我们可以 \(n^3\) 暴力枚举预处理出此结果。
- \(pre[i][0]\) 记录如果 \(M_i\) 作为序列第一个数,则\(M_1\sim M_{i-1}\) 的误差之和,\(pre[i][n+1]\) 记录如果 \(M_i\) 作为所选子序列的最后一个元素,则 \(M_{i+1}\sim M_n\) 的误差之和。
- 所以,\(?=-pre[k][n+1]+pre[i][n+1]+pre[k][i]\)
- \(dp[k][j-1]\) 是把 \(k\) 当成了序列的最后一位,所以\(+pre[k][n+1]\) ,所以先减去,此时把 \(i\) 当做了最后一位,所以 \(+pre[i][n+1]\) ,然后加上 \(M_k\sim M_i\) 之间的误差 \(pre[k][i]\) 。
-
转移方程为:\(dp[i][j]=min(dp[i][j],dp[k][j-1]-pre[k][n+1]+pre[i][n+1]+pre[k][i] ),(j-1\le k<i)\) 。
-
显然是由 选择 \(i-1\) 个元素的序列推导出选择 \(i\) 个元素的序列,所以选择了多少个元素作为阶段,为了方便推导,对 \(dp[i][j]\) 重新定义为:前 \(j\) 个元素,最后一个元素为 \(j\) 共选了 \(i\) 个元素的最小误差。
-
转移方程:\(dp[i][j]=min(dp[i][j],dp[i-1][k]-pre[k][n+1]+pre[i][n+1]+pre[k][i]),(j-1\le k<i)\) 。
-
当前行状态只与上一行状态相关,可以用滚动数组压维。
-
Code
#include <bits/stdc++.h> typedef long long LL; const int maxn=100+15; const int inf=0x3f3f3f3f; int n,E; LL ans; int a[maxn]; LL dp[maxn][maxn],pre[maxn][maxn]; void Init(){ scanf("%d%d",&n,&E); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++){//预处理pre[i][j] for(int j=i+1;j<=n;j++) for(int k=i+1;k<=j-1;k++) pre[i][j]+=abs(2*a[k]-a[i]-a[j]); for(int j=1;j<i;j++) pre[i][0]+=2*abs(a[j]-a[i]);//i之前 for(int j=i+1;j<=n;j++) pre[i][n+1]+=2*abs(a[j]-a[i]);//i之后 } } void Solve(){ int id=n;//记录选的满足条件的最小的个数 ans=inf; for(int i=1;i<=n;i++){//只选一个数的序列 dp[1][i]=pre[i][0]+pre[i][n+1]; if(dp[1][i]<=E){ id=1;ans=std::min(ans,dp[1][i]); } } if(id==1){//没有比1小的选择了 printf("%d %lld\n",id,ans);return; } for(int i=2;i<=n;i++){//选i个数 for(int j=i;j<=n;j++){//第i个数位j dp[i][j]=inf; for(int k=i-1;k<j;k++){//上一个阶段的最后一个数位k int sum=-pre[k][n+1]+pre[k][j]+pre[j][n+1]; dp[i][j]=std::min(dp[i][j],dp[i-1][k]+sum); } if(dp[i][j]<=E){//在误差范围之内 if(i>id) continue;//选的数比已选的大 if(i<id){id=i;ans=dp[i][j];}//在保证id尽可能小的情况下再考虑值 if(i==id) ans=std::min(ans,dp[i][j]); } } } printf("%d %lld\n",id,ans); } int main(){ Init(); Solve(); return 0; }
-