bzoj1831 [AHOI2008]逆序对(dp)

来源:互联网 发布:网络信号传输增强器 编辑:程序博客网 时间:2024/06/05 16:33

设f[i][j]表示第i个空缺填j,前i个空缺产生的逆序对个数的最小值,然而我们发现这样无法进行转移。。因为不知道前面几个空缺都填了什么。然后我们机智的猜想:让空缺填的数单调不降,两两空缺之间均不构成新的逆序对。我们来证明一下:对于两个空位a和b,分别填入数x和y,且x< y。
如果我们交换x和y,会有如下性质:
1.[1,a-1]和[b+1,n]中的数与x y构成的逆序对数不变。
2.[a+1,b-1]中大于y或小于x的数与x y构成的逆序对数不变。
3.[a+1,b-1]中在(x,y)范围内的数与x y构成逆序对。
4.x y构成逆序对。
也就是说我们如果交换x y,逆序对数会增加。
所以填入的这些数一定是单调不降的。哈哈哈,这样我们就可以进行dp了。f[i][j]=min{f[i-1][k]+w[i][j]|1<=k<=j}.其中w[i][j]表示第i个空缺填j,产生的逆序对个数,我们可以提前预处理出来。这样状态是O(nm)的,转移是O(m)的,应该能过,但是考虑到常数很大。。我们可以进行一个简单而有效的优化:前缀最小值优化,即f[i][j]不再表示第i个空缺选j时的最小方案数,而是表示第i个空缺选1~j时的最小方案数。这样转移就是O(1)的,时间复杂度降为O(nm),很快。考虑初值,不太好给。那就把f[i][1]先都做了吧。。最后答案就是f[tot][m]+确定的数的逆序对。

#include <cstdio>#include <cstring>#include <algorithm>#define N 10010#define M 110#define inf 0x3f3f3f3finline 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;}inline int min(int x,int y){return x<y?x:y;}inline int max(int x,int y){return x>y?x:y;}int n,m,a[N],tot=0,num[N],ans=0,w[N][M];//w[i][j]第i个空缺填j,产生的逆序对个数int f[N][M];//f[i][j]第i个空缺填j,前i个空缺产生了逆序对个数的最小值的前缀和最小值优化int main(){//  freopen("a.in","r",stdin);    n=read();m=read();    for(int i=1;i<=n;++i){        a[i]=read();        if(a[i]==-1){            ++tot;            for(int j=m-1;j>=1;--j) w[tot][j]=w[tot][j+1]+num[j+1];//处理i前面会产生的逆序对        }        else{            for(int j=a[i]+1;j<=m;++j) ans+=num[j];//提前把已确定的逆序对加到ans            num[a[i]]++;        }    }int x=tot;memset(num,0,sizeof(num));    for(int i=n;i>=1;--i){        if(a[i]==-1){//处理i后面会产生的逆序对            int sum=0;            for(int j=2;j<=m;++j) sum+=num[j-1],w[x][j]+=sum;            --x;        }else num[a[i]]++;    }    for(int i=1;i<=tot;++i) f[i][1]=f[i-1][1]+w[i][1];    for(int i=1;i<=tot;++i)        for(int j=2;j<=m;++j)            f[i][j]=min(f[i][j-1],f[i-1][j]+w[i][j]);    ans+=f[tot][m];    printf("%d\n",ans);    return 0;}
原创粉丝点击