最近做题遇到几次逆序数了,今天总结一下,以后遇到了再也不怕了。
首先说明一下什么是逆序数,下面是百度的定义:
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
如2431中,21,43,41,31是逆序,逆序数是4。
①如何求逆序数
方法如下:
考察每一位,判断从这一位往后有多少小于该位的,结果累加,得到最后结果。
至于为什么,我也不知道,反正我感觉挺好理解的。
②如何用算法实现
当然,对于上述简单的过程,我们可以用两个for循环直接暴力,但是,这种方法一般都太low了,复杂度太高。
高效的方法有 : 归并排序、线段树、树状数组。
个人建议使用线段树求解,因为线段树在很多地方都有应用。
对于归并排序和树状数组,我就不解释了,因为归并排序一搞就忘记了而且用处不大,想了解的这里给出一篇博客
https://blog.csdn.net/acm_jl/article/details/51147010
例题是 HDU 1394,然后做完这个可以再写一下POJ 2299
HDU 1394的解析如下
https://www.cnblogs.com/qlky/p/5693747.html
然后我写的是POJ 2299
下面开始解析,当然,大家一定要有一定线段树的基础
算法的思想还是最基础的思想,考察每个点,判断它以后有多少个小于它的,然后累加。
与之前不同的地方在于,求小于某个数的个数时用到了线段树,所以非常快
这里的线段树表示某个范围内的值个数query 1--3,就表示值在1--3的个数
然后对于1--n个数,我们每个数都进行两步操作
查询1--a[i],单点更新a[i],值+1
例如对于 2 4 3 1 序列,我们从右向左判断,首先查询1-1,没有,值为0,然后1点值更新
再查询1--3,值为1,(实质表示的意思是比3小的有1个),然后更新3,
再查询4,更新4
查询2,更新2,得到最后的结果
下面是POJ 2299 的代码,要注意的是要离散化一下,因为值比较大
#include <iostream> #include <cstdio> #include<algorithm> #include <cstring> #include <stack> #define fori(l,r) for( int i = l ; i <= r ; i++ ) #define forj(l,r) for( int j = 1 ; j <= r ; j++ ) #define lef rt<<1 #define rig rt<<1|1 #define mid (l+r)>>1 #define mem(a,val) memset(a,val,sizeof a) typedef long long ll; using namespace std; struct spe { int val,pos; }; const int maxn = 5e5+5; spe p[maxn]; int n; int pos[maxn]; int tree[maxn<<2]; int L,R; bool cmp( spe a,spe b ) { return a.val < b.val; } void change( int rt ) { tree[rt] = tree[lef]+tree[rig]; } void update( int l,int r,int rt,int pos ) //在pos位置值加1 { if( l == r ) { tree[rt]++; return; } int m = mid; if( m >= pos ) update(l,m,lef,pos); else update(m+1,r,rig,pos); change(rt); } ll query( int l,int r,int rt ) { if( l >= L && r <= R ) return tree[rt]; int m = mid; ll ans = 0; if( m >= L ) ans += query(l,m,lef); if( m < R ) ans += query(m+1,r,rig); return ans; } int main() { while( scanf("%d",&n) == 1 && n ) { mem(tree,0); fori(1,n) { scanf("%d",&p[i].val); p[i].pos = i; } sort(p+1,p+1+n,cmp); int cnt = 1; fori(1,n) //进行离散化 { p[i].val = cnt++; pos[ p[i].pos ] = i; } ll ans = 0; for( int i = n ; i >= 1 ; i-- ) { L = 1; R = p[ pos[i] ].val; ans += query(1,n,1); update(1,n,1,p[ pos[i] ].val ); } printf("%lld\n",ans); } return 0; } /* 3 (([()]))) */
其实上面都只是基础,真正的比赛不会出上面的题
如果想更深入的了解,可以看看这篇文章,虽然我是看的是有点晕
https://www.cnblogs.com/saltless/archive/2011/06/01/2065619.html
不过最后还是听我讲解吧
全排列+逆序数
就是给你一个数,问它的全排列有多少个逆序数
例题忘记是在哪了,大家可以自己搜一下
由于它是一个全排列,所以就很有规律性
我们列举一下
n = 2
n = 3
当然,最好的是能够自己去找规律,这样记忆最深刻。
对于n = 3时,其中的每一种排列情况都是 n = 2中的一个变种
也就是说,求出了n,我们就可以求n+1,这是一个递推的过程
请大家再仔细琢磨下,就能发现递推规律。
下面是我自己推的一个公式,如果大家有通项公式,可以互相交流下
其中q = !( i-1 ),q为i-1的阶乘
其实这是一个等差数列。
n = 4时,答案就是 9+15+21+27=72
每一个dp[i]都是等差数列的和,有i项,首项是dp[i-1],公差为i-1的阶乘。
代码实现:
#include <iostream> typedef long long ll; using namespace std; const ll mod = 1e9+7; ll dp[104]; ll factorial[105]; //储存阶乘 const int maxn = 105; ll fastpow( ll base,int x ) //快速幂 { ll ans = 1; while( x ) { if( x&1 ) ans = (ans*base)%mod; base = ( base*base )%mod; x = x>>1; } return ans; } void init() { ll total = 1; for( int i = 1 ; i <= maxn ; i++ ) { total = (total*i)%mod; factorial[i] = total; } dp[1] = 0; dp[2] = 1; ll up; ll ans = 0; for( int i = 3 ; i <= maxn ; i++ ) { ll q = factorial[i-1]; up = ( dp[i-1]*2+( ( i-1 )*q )%mod )%mod; up = (up*i)%mod; ans = ( up*fastpow(2,mod-2) )%mod; dp[i] = ans; } } int main() { init(); int x; while( cin>>x ) { cout<<dp[x]<<endl; } return 0; } /* */
下面是一道更难一点的题,是计蒜客上面的
JSZKC is the captain of the lala team.
Input Format
The input file contains several test cases, each of them as described below.
Output Format
样例输入
3 2 3 3
样例输出
2 1
题目来源
The 2018 ACM-ICPC China JiangSu Provincial Programming Contest
这个题很坑,不能开二维数组,只能先把答案储存下来,然后用滚动数组(当然我不太会用,用两个数组复制的)
很重要的规律就是递推,有一个递推公式
下面的是官方的递推公式
递推公式:用N(n,k)表示1~n排列中逆序数为k者的个数,则有
N(n,k+1)=N(n,k)+N(n-1,k+1)-N(n-1,k+1-n)
我推出来的有些不一样
我用pre记录前缀和,dp[i][j]记录i的全排列时,逆序数为j的个数
dp[i][j] = pre[i-1][j-1]+dp[i-1][j]
当然,由于不能开二维数组,代码看起来比较难懂。
AC代码:
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #include<cmath> #include<algorithm> #include<list> #include<map> #define fori(l,r) for( int i = l ; i <= r ; i++ ) #define forj(l,r) for( int j = 1 ; j <= r ; j++ ) #define mem(a,val) memset(a,val,sizeof a) #define inf 0x3f3f3f3f using namespace std; int n,m; typedef long long ll; const int maxn = 5004; const ll mod = 1000000007; ll dp[maxn]; ll pre[maxn],cur[maxn]; //两个前缀和数组 ll ans[maxn]; struct spe { int first,second; }; int cnt = 1; spe query[maxn]; void init() { mem(dp,0); mem(pre,0); mem(cur,0); fori(2,5000) { int goal = i*(i-1)>>1; int nextgoal = i*(i+1)>>1; goal = min(goal,maxn); nextgoal = min(nextgoal,maxn); pre[0] = dp[0] = cur[0] = 1; forj(1,goal) { dp[j] = ( pre[j-1]+dp[j] )%mod; if( j >= i ) dp[j] = ( dp[j]-pre[j-i]+mod )%mod; cur[j] = ( cur[j-1]+dp[j] )%mod; //前缀和累加 } forj(1,cnt-1) //如果更新的当前行有包含答案 if( query[j].first == i ) { if( query[j].second <= goal ) ans[j] = dp[ query[j].second ]; else ans[j] = 0; } forj( 1,nextgoal ) { if( j > goal ) pre[j] = cur[goal]; else pre[j] = cur[j]; } } } int main() { mem(ans,0); while( scanf("%d %d",&query[cnt].first,&query[cnt].second) == 2 && query[cnt].first ) { if( query[cnt].first == 1 ) { if( query[cnt].second == 0 ) ans[cnt] = 1; else ans[cnt] = 0; } cnt++; } init(); fori(1,cnt-1) printf("%lld\n",ans[i]); return 0; } /* 1 1 1 2 2 1 2 2 3 1 3 2 3 3 3 4 3 5 3 6 4 1 4 2 4 3 4 4 6 5 6 6 */
下面是标程代码:
#include <map> #include <cmath> #include <cstdio> #include <ctime> #include <string> #include <vector> #include <cstring> #include <cstdlib> #include <utility> #include <iostream> #include <algorithm> #define LL long long #define pi 3.1415926535897932384626433 #define sqr(a) ((a)*(a)) using namespace std; typedef pair<int,int> PII; typedef pair<PII,int> PIII; const int N=5002,P=1000000007; int f[2][N]; int ans[N]; vector<PIII> d; void data_maker() { srand(time(0)); freopen("B.in", "w", stdout); printf("1 0\n1 1\n1 5000\n5000 5000\n"); for (int Case=1;Case<=4996;Case++) { printf("%d %d\n",rand()%5000+1,rand()%5001); } fclose(stdout); } int main() { //data_maker(); //freopen("B.in", "r", stdin); //freopen("B.out", "w", stdout); int i,j,k,x,y,Case=0,cur=0; while (scanf("%d%d",&x,&y)!=EOF) d.push_back({{x,y},++Case}); sort(d.begin(),d.end()); f[1][0]=1;f[1][1]=-1;k=1; for (i=1;i<=5000;i++,k^=1) { for (j=0;j<=5000;j++) { if (j>0) f[k][j]=(f[k][j]+f[k][j-1])%P; f[k^1][j]=(f[k^1][j]+f[k][j])%P; if (i+j+1<=5000) f[k^1][i+j+1]=((f[k^1][i+j+1]-f[k][j])%P+P)%P; } while (cur!=Case&&i==d[cur].first.first) ans[d[cur].second]=f[k][d[cur].first.second],++cur; memset(f[k],0,sizeof(f[k])); } for (i=1;i<=Case;i++) printf("%d\n",ans[i]); return 0; }