bzoj1049: [HAOI2006]数字序列

链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1049

题意:中文题。。

分析:这题有两问。

第一问:求最少需要修改多少个数使序列严格递增,我们反向求最多可以保留多少,类似最长上升一样的求法有dp[i]=max(dp[j]+1,a[i]-a[j]>=i-j)稍微变一下就是a[i]-i>=a[j]-j,所以我们在将原数组a[i]-=i之后求最长不下降序列即可。

第二问:在改变的最少的情况下,修改的值绝对值之和最小。首先我考虑的是如何在最小的情况下还要保证改变的值最少,其实这里给了我们解题的一些思路,意思是我们要在之前的基础之上再做文章。但是一个问题出现了,可能有多个最长不下降,怎么办呢?我是在前面加一个-INF,在后面加一个INF处理的,这样就保证我们是处理唯一的最长不下降了。然后如何在之前的基础之上做第二问呢?设b[i]=a[i]-i,对于b[j]和b[i]若有b[j]<=b[i]&&dp[j]+1==dp[i]那么我们只需要将j+1~i-1这些变成合法即可,同时有一个很显然的性质即j<k<i&&(b[k]>b[i]||b[k]<b[j]),我们称b[k]>b[i]为"大数",b[k]<b[j]为"小数"。我们可以猜想j+1~i-1这些数必然是修改成a[j]或a[i]的,我们用归纳法来证明,首先当j+1==i时显然成立,假设中间有k个数的时候成立,我们只需要证明当中间有k+1个数x的时候是成立的,(1)当x正好在之前的分界点处||x在a[j]这边并且比x<a[j]||x在a[i]这边并且比x>a[i],这时候这个数要改变的最优值必然是与x最近的a[j]或a[i],(2)当x在a[j]这边并且x>a[i]||x在a[i]这边并且x<a[j],我们分析当x在a[j]这边的情况,若我们之间将x改变为a[j]那么代价为x-a[j],若我们将原来最优解的一段a[j]全部变大来配合x变小那么代价比如>=0而收益=1,最后的最极端的情况也是将x于x之后的所有的值全部变为a[i],这样依然是变为a[j],a[i]。那么怎么找这个分界点呢?首先我们将中间的数全部变为a[i]总代价为tot(即分界线为j~j+1),然后当分界线向右移动时,即将一些数右变为a[j],那么如果这个数原来是"大数"我们还要花费代价a[i]-a[j],如果这个数是原来的"小数"我们就可以将直接多花费在tot里面的a[i]-a[j]的花费减去,设最终分界线左边"大数"有x个"小数"有y个,那么tot+x*(a[i]-a[j])-y*(a[i]-a[j])即tot-(y-x)*(a[i]-a[j]),所以我们找到最大化的y-x处就可以了,时间复杂度为O(n^2),出题人说的是随机数据,所以达不到最差情况。

PS:在写完这题后,我看了榜上最快时间是48ms,而我的是6000+ms,不能接受。。然后在我a了1367之后我以为他们都是用那种nlogn的可并堆的做法,但是我在想着能不能满足题目要求的保留最长不下降的长度的前提,然后发现好像证明不了。后来将两份代码和在一起对拍几组数据发现答案完全不一样。所以他们优化的方法显然不是这种nlogn的做法。然后我仔细看了hzwer和iwtwiioi的代码后发现他们在嵌套的寻找j点的那层循环中是直接用前向星记录对应长度的点,这样可以快速找到点,但是接下来暴力处理j~i直接所有的点,其实这样是在一定程度上可以优化但是又在一定程度上会退化,但是!这是随机数据。。。。我服。

代码:

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<math.h>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=50010;
const int MAX=151;
const int MOD=1000000007;
const int MOD1=100000007;
const int MOD2=100000009;
const int INF=2100000000;
const double EPS=0.00000001;
typedef long long ll;
typedef unsigned long long uI64;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,a[N],d[N],f[N];
int getmid(int k,int i) {
    int l=0,r=k,mid=k/2;
    while (l+1<r)
    if (a[d[mid]]<=a[i]) { l=mid;mid=(l+r)/2; }
    else { r=mid;mid=(l+r)/2; }
    d[r]=i;return r;
}
int get_lis() {
    int i,j,k=0;
    memset(f,0,sizeof(f));
    d[k]=0;a[0]=-INF;
    for (i=1;i<=n;i++)
    if (a[i]>=a[d[k]]) { k++;f[i]=k;d[k]=i; }
    else f[i]=getmid(k,i);
    return k;
}
int sum[N];
ll dp[N];
ll getsum() {
    int i,j,mi;
    ll tot;dp[1]=0;
    for (i=2;i<=n;i++) {
        dp[i]=1LL<<50;
        sum[i]=tot=mi=0;
        for (j=i-1;j>0;j--)
        if (a[j]<=a[i]&&f[j]+1==f[i]) {
            dp[i]=min(dp[i],dp[j]+tot-(ll)(sum[j+1]-mi)*(a[i]-a[j]));
            if (a[j]>a[i]) { sum[j]=sum[j+1]-1;tot+=(ll)a[j]-a[i]; }
            else { sum[j]=sum[j+1]+1;tot+=(ll)a[i]-a[j]; }
            mi=min(mi,sum[j]);
        } else {
            if (a[j]>a[i]) { sum[j]=sum[j+1]-1;tot+=(ll)a[j]-a[i]; }
            else { sum[j]=sum[j+1]+1;tot+=(ll)a[i]-a[j]; }
            mi=min(mi,sum[j]);
        }
    }
    //for (i=1;i<=n;i++) printf("%d  %I64d  xxx\n", i, dp[i]);
    return dp[n];
}
int main()
{
    int i,ans1;
    ll ans2;
    scanf("%d", &n);n+=2;
    for (i=2;i<n;i++) {
        scanf("%d", &a[i]);a[i]-=i;
    }
    a[1]=-INF;a[n]=INF;
    ans1=n-get_lis();
    ans2=getsum();
    printf("%d\n%lld\n", ans1, ans2);
    return 0;
}

/*
4
5 2 3 5
5
1 2 3 4 5
5
5 4 3 2 1
10
67 65 95 44 87 76 75 67 47 60
10
771574 717665 7618546 981465 683554 75751 4381389 356714 9587165 8886515
*/


你可能感兴趣的:(bzoj1049: [HAOI2006]数字序列)