BZOJ1831 || 洛谷P4280 [AHOI2008]逆序对【DP】

Time Limit: 10 Sec
Memory Limit: 64 MB

Description

小可可和小卡卡想到Y岛上旅游,但是他们不知道Y岛有多远。好在,他们找到一本古老的书,上面是这样说的: 下面是N个正整数,每个都在1~K之间。如果有两个数A和B,A在B左边且A大于B,我们就称这两个数为一个“逆序对”。你数一数下面的数字里有多少个逆序对,你就知道Y岛离这里的距离是多少千米了。 比如说,4 2 1 3 3里面包含了5个逆序对:(4, 2), (4, 1), (4, 3), (4, 3), (2, 1)。 可惜的是,由于年代久远,这些数字里有一部分已经模糊不清了,为了方便记录,小可可用“-1”表示它们。比如说,4 2 -1 -1 3 可能原来是4 2 1 3 3,也可能是4 2 4 4 3,也可能是别的样子。 小可可希望知道,根据他们看清楚的这部分数字,能不能推断出这些数字里最少能有多少个逆序对。

Input

第一行两个正整数N和K。第二行N个整数,每个都是-1或是一个在1~K之间的数。

Output

一个正整数,即这些数字里最少的逆序对个数。

HINT

100%的数据中,N<=10000,K<=100。
60%的数据中,N<=100。
40%的数据中,-1出现不超过两次。


题目分析

首先可以想到从左往右每个-1位置填写的数字一定单调不下降
若有 a j &gt; a i , j &lt; i a_j&gt;a_i,j&lt;i aj>ai,j<i,交换 a i , a j a_i,a_j ai,aj,逆序对数不可能增加
因为交换后 a i , a j a_i,a_j ai,aj对区间 [ i , n ] [i,n] [i,n]逆序对贡献不变,但减少了 a j , a i a_j,a_i aj,ai这一逆序对
且因为交换后 a j a_j aj变小,原来 a j a_j aj对区间 [ j , i ] [j,i] [j,i]的逆序对贡献可能消失

d p [ i ] [ j ] dp[i][j] dp[i][j]表示从右往左第 i i i个-1位置填 j j j增加的最小逆序对数
d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ k ] ) + dp[i][j]=min(dp[i-1][k])+ dp[i][j]=min(dp[i1][k])+ i i i个-1位置填 j j j增加的逆序对数,其中 k ≥ j k\geq j kj
开一个数组 m i [ i ] [ j ] mi[i][j] mi[i][j]保存 d p [ i ] [ j ] dp[i][j] dp[i][j]~ d p [ i ] [ k ] dp[i][k] dp[i][k]的最小值就可以 O ( K ) O(K) O(K)转移了

总复杂度 O ( n k ) O(nk) O(nk)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))
 
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}
 
const int inf=1e9;
const int maxn=10010;
int n,K;
int a[maxn],sum[2][maxn],tot;
int rem[maxn];
lt dp[maxn][110],mi[maxn][110];
 
void add(int x,int v,int d){ for(int i=x;i<=K;i+=lowbit(i))sum[d][i]+=v;}
int qsum(int x,int d){ int res=0; for(int i=x;i>0;i-=lowbit(i))res+=sum[d][i]; return res;}
 
int main()
{
    n=read();K=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        if(a[i]!=-1) add(a[i],1,0);
        else tot++;
    }
     
    lt ans=0; int cnt=0;
    for(int i=n;i>=1;--i)
    {
        if(a[i]!=-1){
            ans+=qsum(a[i]-1,1);
            add(a[i],1,1); add(a[i],-1,0);
        }
        else{
            ++cnt;
            for(int j=1;j<=K;++j) dp[cnt][j]=inf;
             
            int tt=qsum(K,0);
            for(int j=1;j<=K;++j) 
            rem[j]=tt-qsum(j,0)+qsum(j-1,1);
            //qsum(j-1,1)求(i,n]内小于j的个数,qsum(K,0)-qsum(j,0)求[1,i)内大于j的个数
             
            for(int j=1;j<=K;++j)
            dp[cnt][j]=rem[j]+mi[cnt-1][j];
             
            mi[cnt][K+1]=inf;
            for(int j=K;j>=1;--j)
            mi[cnt][j]=min(mi[cnt][j+1],dp[cnt][j]);
        }
    }
     
    lt tt=inf;
    for(int i=1;i<=K;++i) tt=min(tt,dp[tot][i]);
    printf("%lld",ans+tt);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值