HDU-5273 Dylans loves sequence(区间DP)

题意:给定一个长度为n的数列ai,有q个询问(x,y),表示区间[x,y]中有几个逆序对,回答这些询问。(n<=1000,q<=100000,ai∈{int})

题目短小精悍。首先,q的值非常大,很明显,需要把每个区间的逆序对数预处理出来。

而预处理求逆序对的方法,我想到的是“离散化+计数”。首先ai非常大,必须先离散化后才能计数。开一个cnt[i][j]表示前i个数中大于j的数的个数,dp[i][j]表示区间[i,j]逆序对的对数。

方程:dp[L][R]=dp[L][R-1]+cnt[R-1][a[R]]-cnt[L-1][a[R]]

方法一:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 1000
using namespace std;
struct node
{
	int num,id;
	bool operator<(const node &_)const
	{
		return num<_.num;
	}
}s[N+3];
int a[N+3];
int cnt[N+3][N+3];   //cnt[i][j]表示前i个数中,大于j的数的个数 
int dp[N+3][N+3];    //dp[L][R]表示[L,R]中逆序对的个数 
int n,q;

int main()
{
    scanf("%d%d",&n,&q);
    FOR(i,1,n)
    {
    	scanf("%d",&s[i].num);
    	s[i].id=i;
	}
	sort(s+1,s+1+n);
	int t=0;
	FOR(i,1,n)    //离散化 
	{
		if(s[i].num!=s[i-1].num)t++;
		a[s[i].id]=t;
	}
	
	FOR(i,1,n)FOR(j,1,n)cnt[i][j]=cnt[i-1][j]+(j<a[i]);
	FOR(L,1,n)
		FOR(R,L+1,n)
			dp[L][R]=dp[L][R-1]+cnt[R-1][a[R]]-cnt[L-1][a[R]];
	
	int x,y;
	while(q--)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",dp[x][y]);
	}
    return 0;
}

这种转移方法固然是可行的,但是过于复杂,其实,对于这一类问题,有一种更简便的转移方法:

dp[L][R]=dp[L+1][R]+dp[L][R-1]-dp[L+1][R-1]+(a[L]>a[R])

是不是有点像二维前缀和?事实上,把dp数组画成表格,确实就是一个二维前缀和,不过累计的是逆序对的数量。这种区间的计数问题,这种方法是可以套用的。

方法二:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 1000
using namespace std;
int a[N+3];
int dp[N+3][N+3];    //dp[L][R]表示[L,R]中逆序对的个数 
int n,q;

int main()
{
    scanf("%d%d",&n,&q);
    FOR(i,1,n)scanf("%d",&a[i]);
	FOR(l,1,n)
		FOR(L,1,n-l+1)
		{
			int R=L+l-1;
		    dp[L][R]=dp[L+1][R]+dp[L][R-1]-dp[L+1][R-1]+(a[L]>a[R]);
        }
	int x,y;
	while(q--)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",dp[x][y]);
	}
    return 0;
}

猜你喜欢

转载自blog.csdn.net/paulliant/article/details/80230718