POJ3666 Making the Grade

http://poj.org/problem?id=3666

 

很明显B序列非严格递增和B序列非严格递减处理方式是相同的,分别处理取最小值即可

 

下面以B序列非严格递增为例:

 

一个直观的想法是 设 f[i][j] 为确定了b序列前i个数,第i个数的值为j是所得的最小值

f[i][j]=min\left \{f[i-1][k]+abs(j-a[i]) \right \} (0\leqslant k\leqslant j)

这么一看时间复杂度是O(n^2*max\left \{ a_i\right \})的,需要进行优化

 

首先发现,对于一个i,随着j的增加,可选取的决策点k是只增不减的,即j每次加1时,可选取的k的个数也会加1

由此看出j每次加1时,要是再从头到尾循环k就造成了时间上的浪费

所以对于一个i,记录一个mn,表示循环到j的时候的决策点f[i-1][k] 的前缀最小值,直接利用mn实现转移,而每次更新mn时只需考虑新加入的 f[i-1][j] ,就能做到O(1)更新mn

 

到这里复杂度就降到了O(n*max\left \{ a_i \right \}),但ai的范围是1e9,还需要继续优化

 

这时候就有一个猜想,貌似B的取值在1e9这个范围内会有很多是无用的,再进一步地说,B的取值会不会是A序列出现的数字的集合的子集,也就是B的取值不会出现A中没有出现的数字

 

可以尝试证明一下,这里用数学归纳法:

(1)当N=1时显然成立

(2)当N>1时,假设前Bn-1项取得最优解且满足上述条件,此时Bn>=Bn-1 

1.若Bn-1>=An,则令Bn=Bn-1即可,贪心地想,尽可能地让Bn往小了选,这样之后B序列能选择的数范围更广,与A序列更“接近”

2.反之,又分为两种情况   

1.若选取的Bn>=An(>=Bn-1),按照上面贪心的思想,直接令Bn取An即可

2.否则的话,假设Bn-1=Bn-2=...=Bn-k (0<=k<=n-1),对于A序列和B序列这2k个点来说,就已经转化成了 “ 在数轴上取一个点,让这个点到其他指定的点的距离和最小”   问题,就像这样:

                                            

    

Bn-1=Bn-2=...=Bn-k肯定是取在这些A点的中位数上

然后发现,再往里插入An时,它肯定在Bn-1=Bn-2=...=Bn-k这个点的右侧,这时候无论A点个数是奇是偶,中位数始终不变的

所以接下来取Bn的时候,也直接把它放在中位数的位置上,也就是Bn=Bn-1,这样就保证了最优解

 

综上,Bn的取值一定是A序列出现的数的集合的子集,然后离散化处理A序列即可,这样复杂度就降到了O(n^2)

 

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define db double
#define rep(x,a,b) for(int x=(a);x<=(b);x++)
#define per(x,a,b) for(int x=(a);x>=(b);x--)
#define reP(x,a,b) for(int x=(a);x<(b);x++)
#define Per(x,a,b) for(int x=(a);x>(b);x--)
#define scf(a) scanf("%d",&a)
#define scfll(a) scanf("%lld",&a)
#define scfdb(a) scanf("%lf",&a)
#define ptf(a) printf("%d",a)
#define ptfll(a) printf("%lld",a)
#define ptfdb(x,a) printf("%x.lf",a)
#define ptfsp(a) printf("%d ",a)
#define ptfllsp(a) printf("%lld ",a)
#define ptfdbsp(x,a) printf("%x.lf ",a)
#define pli(a,b) make_pair(a,b)
#define pb push_back
#define el puts("")
#define ls pos<<1
#define rs pos<<1|1
#define pi 3.1415926
//ios::sync_with_stdio(false);
using namespace std;
const ll mod=1e9+7;
const int maxn=2e3+5;
ll f[maxn][maxn];
map<int,int>mp;
int a[maxn],b[maxn];
int main(){
    int n;scf(n);
    rep(i,1,n) scf(a[i]),mp[a[i]]=1;
    int cnt=0;
    for(auto &x:mp) x.second=++cnt,b[cnt]=x.first;
    ll ans=1e18;
    //for(int j=1;j<=cnt;j++) cout<<b[j]<<" ";el;
    rep(i,1,n)
        rep(j,1,cnt)
            f[i][j]=1e18;
    rep(j,1,cnt) f[0][j]=0;
    rep(i,1,n){
        ll mn=1e18;
        rep(j,1,cnt){
            mn=min(mn,f[i-1][j]);
            f[i][j]=min(f[i][j],mn+abs(b[j]-a[i]));
        }
    }
    rep(j,1,cnt) ans=min(ans,f[n][j]);

    rep(i,1,n)
        rep(j,1,cnt)
            f[i][j]=1e18;
    rep(j,1,cnt) f[0][j]=0;
    rep(i,1,n){
        ll mn=1e18;
        per(j,cnt,1){
            mn=min(mn,f[i-1][j]);
            f[i][j]=min(f[i][j],mn+abs(b[j]-a[i]));
        }
    }
    rep(j,1,cnt) ans=min(ans,f[n][j]);

    cout<<ans;
}

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值