bzoj 1049: 数字序列 dp

题目大意:

给定一个长度为n的整数序列.在改变的数最小的和改变的幅度最小的前提下把它变成一个单调严格上升的序列.求改变的最小的数和这个幅度。

题解:

(貌似以前考试考过这道题)
其实这道题就是两道题拼一块的
我们首先考虑第一问
这是一个经典模型,我们有
当有\(i - j \leq a_i - a_j\)\(a_i\)\(a_j\)不用更改\((i > j)\)
所以我们变号得到\(a_j - j \leq a_i - i\)
所以我们将所有序列中的值减去下标再做一遍最长不下降子序列即可
然后我们使用减去了下标的那个数组作为第二问的初始数组
我们设\(f[i]\)为第一问的LCIS的dp数组,\(g[i]\)表示第二问的dp数组
(均表示1~i的答案)
我们有\(g[i] = min{g[j] + calc(j+1,i)}\text{当且仅当}(f[i] == f[j] + 1)\)
由...ydc的题解我们知道...

现在一个结论是,calc(j,i)的方案,一定会以某个k 为分界使得[j,k] 均为\(b_j\)\([k+1,i]\) 均为\(a_i\)

证明已跪...

所以我们利用这个性质统计答案即可
(很抱歉我连怎么用都不会,%了一发hzwer的代码)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(ll &x){
    x=0;char ch;bool flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
inline ll cat_max(const ll &a,const ll &b){return a>b ? a:b;}
inline ll cat_min(const ll &a,const ll &b){return a<b ? a:b;}
const ll maxn = 35010;
const ll inf1 = 1<<30;
const ll inf2 = 1LL<<60;
// inline ll abs(ll x){
//  return x < 0 ? -x : x;
// }
struct Edge{
    ll to,next;
}G[maxn];
ll head[maxn],cnt;
void add(ll u,ll v){
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
}
ll a[maxn],f[maxn],m[maxn],g[maxn];
ll lim;
inline ll find(ll x){
    ll l = 1,r = lim,ret = 0;
    while(l <= r){
        ll mid = (l+r) >> 1;
        if(m[mid] <= x) ret = mid,l = mid+1;
        else r = mid - 1;
    }return ret;
}
ll suma[maxn],sumb[maxn];
int main(){
    memset(m,0x3f,sizeof m);
    ll n;read(n);
    for(ll i=1;i<=n;++i) read(a[i]),a[i] -= i;
    a[++n] = inf1;m[0] = -inf1;
    for(ll i=1;i<=n;++i){
        f[i] = find(a[i]) + 1;
        //printf("f[%d] = %d\n",i,f[i]);
        lim = max(lim,f[i]);
        m[f[i]] = min(a[i],m[f[i]]);
    }
    for(ll i = n;i>=0;--i) add(f[i],i),g[i] = 1LL<<60;
    a[0] = -inf1;g[0] = 0;
#define v G[p].to
    for(ll u = 1;u<=n;++u){
        for(ll p = head[f[u]-1];p;p=G[p].next){
            if(v > u) break;
            if(a[v] > a[u]) continue;
            suma[v-1] = sumb[v-1] = 0;
            for(ll i=v;i<=u;++i){
                suma[i] = suma[i-1] + abs(a[v] - a[i]);
                sumb[i] = sumb[i-1] + abs(a[u] - a[i]);
            }
            for(ll i=v;i<=u;++i){
            //  printf("%d <- %d\n",g[u],g[v] + suma[i] - suma[v] + sumb[u] - sumb[i]);
                g[u] = min(g[u],g[v] + suma[i] - suma[v] + sumb[u] - sumb[i]);
            }
        }
    }
#undef v
    printf("%lld\n%lld\n",n-f[n],g[n]);
    getchar();getchar();
    return 0;
}
  

转载于:https://www.cnblogs.com/Skyminer/p/6421034.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值