P4280 [AHOI2008]逆序对

传送门

考虑 $dp$ ,发现之前的 $-1$ 可能会产生贡献不好处理

贪心一下发现每个位置填的数必须单调不减,所以就不用考虑之前填的数

设 $f[i][j]$ 表示当前考虑到第 $i$ 个位置,填的数为 $j$ 时的最小代价

那么有 $f[i][j]=f[pre][k]+val[i][j],k<=j$,$pre$ 是上一个 $-1$ 的位置,$val[i][j]$ 是在位置 $i$ 填 $j$ 时的代价,这个可以用树状数组求出

转移只要维护一个前缀最小值即可做到 $O(1)$,记得答案要统计本身的逆序对数

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline 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<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e4+7,M=207;
int n,K,a[N];
ll f[N][M],mi[N][M],ans;
int t[2][N];
inline void add(int x,int v,int p) { while(x<=K) t[p][x]+=v,x+=x&-x; }
inline int ask(int x,int p) { int res=0; while(x) res+=t[p][x],x-=x&-x; return res; }
int main()
{
    n=read(),K=read();
    for(int i=1;i<=n;i++) a[i]=read();
    memset(f,0x3f,sizeof(f)); memset(mi,0x3f,sizeof(mi));
    int pre=0,tot=0;
    for(int i=1;i<=K;i++) mi[0][i]=0;
    for(int i=1;i<=n;i++) if(a[i]!=-1) add(a[i],1,1);
    for(int i=1;i<=n;i++)
    {
        if(a[i]!=-1) { ans+=(tot-ask(a[i],0)); add(a[i],1,0); add(a[i],-1,1); tot++; continue; }
        for(int j=1;j<=K;j++)
            f[i][j]=mi[pre][j] + (tot-ask(j,0)) + ask(j-1,1);
        for(int j=1;j<=K;j++)
            mi[i][j]=min(mi[i][j-1],f[i][j]);
        pre=i;
    }
    printf("%lld\n",ans+mi[pre][K]);
    return 0;
}

 

posted @ 2019-08-24 16:11  LLTYYC  阅读(183)  评论(0编辑  收藏  举报