题意:给定一个长度为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; }