1. 程式人生 > >洛谷 P4280 bzoj1786 [AHOI2008]逆序對(dp)

洛谷 P4280 bzoj1786 [AHOI2008]逆序對(dp)

題面

luogu
bzoj

題目大意:

  • 給你一個長度為\(n\)的序列,元素都在\(1-k\)之間,有些是\(-1\),讓你把\(-1\)也變成\(1-k\)之間的數,使得逆序對最多,求逆序對最少是多少
  • \(n<=10000,k<=100\)

題解

結論:

填的數是不下降的

證明:

假設相鄰的兩個-1的位置是(x,y)(a[x]<=a[y]);
如果交換x,y;
對1-x和y-n中的數顯然沒有影響.
對x-y中大於max(a[x],a[y])和小於min(a[x],a[y])的數顯然也沒有影響.
但是對x-y中a[x]-a[y]的數,逆序對數顯然增加了.
所以交換x,y只會導致逆序對數不降.
所以-1位置的數一定是單調不降的.

\(l[i][j]\)表示前\(i\)個數有多少大於\(j\)的數
\(r[i][j]\)表示後\(i\)個數有多少小於\(j\)的數

我們把-1位置弄出來\(dp\)

\(f[i][j]\)表示第\(i\)位填\(j\),逆序對個數最少是多少
\(b[i]表示第i個空在原序列的位置\)

假設\(i\)位填\(t\)

\(f[i][t] = min(f[i-1][z]+l[b[i]][t]+r[b[i]][t])\)

這樣複雜度是\(O(n*k^2)\)

  • 注意還要加上沒填數時的逆序對

可以發現\(f[i-1][z]\) 可以用一個字首最小來優化

Code

#include<bits/stdc++.h>

#define LL long long
#define RG register

using namespace std;
template<class T> inline void read(T &x) {
    x = 0; RG char c = getchar(); bool f = 0;
    while (c != '-' && (c < '0' || c > '9')) c = getchar(); if (c == '-') c = getchar(), f = 1;
    while (c >= '0' && c <= '9') x = x*10+c-48, c = getchar();
    x = f ? -x : x;
    return ;
}
template<class T> inline void write(T x) {
    if (!x) {putchar(48);return ;}
    if (x < 0) x = -x, putchar('-');
    int len = -1, z[20]; while (x > 0) z[++len] = x%10, x /= 10;
    for (RG int i = len; i >= 0; i--) putchar(z[i]+48);return ;
}
const int N= 10010;
int cnt, l[N][110], r[N][110], a[N], b[N];
LL f[N][110], g[N][110];
int main() {
    //freopen(".in", "r", stdin);
    //freopen(".out", "w", stdout);
    int n, k; read(n); read(k);
    for (int i = 1; i <= n; i++) {
        read(a[i]);
        if (a[i] == -1) b[++cnt] = i;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= k; j++) {
            l[i][j] = l[i-1][j];
            if (a[i] > j) l[i][j]++;
        }
    for (int i = n; i >= 1; i--)
        for (int j = 1; j <= k; j++) {
            r[i][j] = r[i+1][j];
            if (a[i] < j && a[i] != -1) r[i][j]++;
        }
    for (int i = 1; i <= cnt; i++) g[i][0] = 1ll << 60;
    for (int i = 1; i <= cnt; i++)
        for (int j = 1; j <= k; j++) {
            f[i][j] = g[i-1][j]+l[b[i]][j]+r[b[i]][j];
            g[i][j] = min(g[i][j-1], f[i][j]);
        }
    LL ans = 1ll << 60;
    for (int i = 1; i <= k; i++) ans = min(ans, f[cnt][i]);
    for (int i = 1; i <= n; i++) ans += l[i][a[i]];
    printf("%lld\n", ans);
    return 0;
}