zoukankan      html  css  js  c++  java
  • 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\%)的数据中,(Nle10000)(Kle100)
    (60\%)的数据中,(Nle100)
    (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;
    }
    
  • 相关阅读:
    【计算机网络】复习集(更新中)
    滑雪 (记忆化搜索)
    橱窗布置
    复制书稿 (dp+贪心)
    8786:方格取数 (多线程dp)
    编辑距离
    8782:乘积最大
    合并石子 (区间dp+前缀和)
    6045:开餐馆
    6049:买书 (完全背包)
  • 原文地址:https://www.cnblogs.com/mmlz/p/4330107.html
Copyright © 2011-2022 走看看