BZOJ 1831 逆序对

Description

小可可和小卡卡想到Y岛上旅游,但是他们不知道Y岛有多远。好在,他们找到一本古老的书,上面是这样说的: 下面是N个正整数,每个都在\(1 \sim 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 \sim K\)之间的数。

Output

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

Sample Input

5 4
4 2 -1 -1 3

Sample Output

4

HINT

\(4\;2\;4\;4\;3\)中有\(4\)个逆序对。当然,也存在其它方案得到\(4\)个逆序对。

数据范围:
\(100\%\)的数据中,\(N\le10000\)\(K\le100\)
\(60\%\)的数据中,\(N\le100\)
\(40\%\)的数据中,\(-1\)出现不超过两次。

一道很好的dp题。(难得这题自己能够做出来)
\(f_{i,j,k}\)表示考虑前\(i\)\(-1\),前面\(i\)个数中大于\(j\)的有\(k\)个数字的最优解。
\(pre_{i,j}\)表示前\(i\)\(-1\)中,已知的数字部分小于等于\(j\)的有几个;\(sum_{i}\)表示整个序列小于等于\(j\)的数字有几个。
dp方程比较复杂,我在代码里写注释。

inline void dp()
{
    memset(f[0],0x7,sizeof(f[0]));
    int i,j,k,p,q;
    for (i = 1;i <= K;++i) f[0][i][0] = 0;
    for (i = 1;i <= cnt;++i)
    {
        p = i&1,q = p^1;
        for (j = 1;j <= K;++j)
            for (k = 0;k <= i;++k) f[p][j][k] = inf;
        for (j = 1;j <= K;++j)
            for (k = 0;k < i;++k)
                f[p][j][k] = f[q][j][k] + k + pre[i][K] - pre[i][j] + sum[j-1] - pre[i][j-1];   //第$i$位中填$j$之后的贡献
        for (j = 1;j <= K;++j)
            for (k = i-1;k >= 0;--k)
            {
                if (j + 1 <= K) f[p][j][k+1] = min(f[p][j][k+1],f[p][j+1][k]);  //递推更新$f$值,枚举顺序并不能更改
                if (j > 1)f[p][j][k] = min(f[p][j][k],f[p][j-1][k]);
            }
        for (j = K - 1;j >= 1;--j)
            for (k = i;k >= 0;--k) f[p][j][k] = min(f[p][j][k],f[p][j+1][k]);  //递推再次更新$f$值
    }
}

贴一份完整的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;

#define inf (1<<29)
#define maxn 10010
#define maxk 110
int n,K,ans,seq[maxn],f[2][maxk][maxn],tree[maxk];
int cnt,sum[maxk],pre[maxn][maxk];

inline int lowbit(int x) { return x & -x; }

inline void modify(int x) { for (;x <= K;x += lowbit(x)) tree[x]++; }

inline int calc(int x) { int ret = 0; for (;x;x -= lowbit(x)) ret += tree[x]; return ret; }

inline void dp()
{
    memset(f[0],0x7,sizeof(f[0]));
    int i,j,k,p,q;
    for (i = 1;i <= K;++i) f[0][i][0] = 0;
    for (i = 1;i <= cnt;++i)
    {
        p = i&1,q = p^1;
        for (j = 1;j <= K;++j)
            for (k = 0;k <= i;++k) f[p][j][k] = inf;
        for (j = 1;j <= K;++j)
            for (k = 0;k < i;++k)
                f[p][j][k] = f[q][j][k] + k + pre[i][K] - pre[i][j] + sum[j-1] - pre[i][j-1];
        for (j = 1;j <= K;++j)
            for (k = i-1;k >= 0;--k)
            {
                if (j + 1 <= K) f[p][j][k+1] = min(f[p][j][k+1],f[p][j+1][k]);
                if (j > 1)f[p][j][k] = min(f[p][j][k],f[p][j-1][k]);
            }
        for (j = K - 1;j >= 1;--j)
            for (k = i;k >= 0;--k) f[p][j][k] = min(f[p][j][k],f[p][j+1][k]);
    }
}

int main()
{
    freopen("1831.in","r",stdin);
    freopen("1831.out","w",stdout);
    scanf("%d %d",&n,&K);
    for (int i = 1;i <= n;++i)
    {
        scanf("%d",seq+i);
        if (seq[i] == -1) cnt ++, memcpy(pre[cnt],sum,sizeof(sum));
        else
        {
            sum[seq[i]]++;
            ans += calc(K + 1-seq[i]-1);
            modify(K + 1 -seq[i]);
        }
    }
    for (int i = 1;i <= cnt;++i)
        for (int j = 1;j <= K;++j) pre[i][j] += pre[i][j-1];
    for (int i = 1;i <= K;++i) sum[i] += sum[i-1];
    int ret = 0;
    if (cnt)
    {
        dp(); ret = inf;
        for (int i = 1;i <= K;i++)
            for (int j = 0;j <= cnt;++j)
                ret = min(ret,f[cnt&1][i][j]);
    }
    printf("%d",ret + ans);
    fclose(stdin); fclose(stdout);
    return 0;
}

转载于:https://www.cnblogs.com/mmlz/p/4330107.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值