LA4329 Ping pong

一共有\(n\)个人,在一般化的情况下,第\(i\)个人作为裁判时,设在\(i\)的左边存在\(c_i\)个人的技能值小于\(i\),在\(i\)的右边存在\(d_i\)个人的技能值大于\(i\)
则在\(i\)右边大于\(i\)的有\(n-i-d_i\)个人,在\(i\)左边大于\(i\)的有\(i-1-c_i\)个人。
根据乘法原理,一共有\(c_i*(n-i-d_i)+(i-1-c_i)*d_i\)种可能。
遍历\(2\)\(n-1\)号,根据上式可以求出所有可能性。

利用树状数组保存\(c\)\(d\)两个数组,具体来说有如下例子

3 4 2 1 5 6 8

以计算\(c\)数组为例,初始情况下,\(A\)\(C\)(注意,这里是大C,目的是为了与刘汝佳的书一样,方便阅读)
| C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
考察第一个\(3\),在C数组里初始什么都没有,\(sum(3)\)的值为0,但要在A数组中添加一个\(3\)的对应数量为1。此时\(c_1=0\)
| C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |

考察第二个\(4\),首先在\(A\)数组中利用\(add(4)\)给对应位置加上1个数量,然后计算\(sum(4)\),将值赋给\(c_2\),其值为1。
| C | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |

以下以此类推,不过最后会发现,大\(C\)数组和小\(c\)数组中的值不同,因为小\(c\)是在动态添加的过程中,以后如果有比当前考察对象更小的数值加入进来的话,该考察值不会改变,而大\(C\)是会改变的。

#include <string.h>
#include <iostream>
using namespace std;
const int maxn = 100000+10;
int i, n, bit[maxn];
inline void add(int x,int d){while(x<=maxn){bit[x]+=d;x+=x&-x;}}
inline int sum(int x){int ret=0;while(x>0){ret+=bit[x];x-=x&-x;}return ret;}
inline int query(int x,int y){return sum(y)-sum(x-1);}
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
const int maxN = 20000+10;
int main() {
	int T; cin >> T;
	while (T--) {
		int c[maxn], d[maxn], a[maxN];
		memset(c, 0, sizeof(c));
		memset(d, 0, sizeof(d));
		cin >> n;
		for (i = 0; i < n; ++i) read(a[i]);
		memset(bit, 0, sizeof(bit));
		for (i = 0; i < n; ++i) { add(a[i], 1); c[i] = sum(a[i]-1); }
		memset(bit, 0, sizeof(bit));
		for (i = n-1; i >= 0; --i) { add(a[i], 1); d[i] = sum(a[i]-1); }
		long long cnt = 0;
		for (i = 1; i < n-1; ++i) cnt += c[i]*(n-i-d[i]-1) + d[i]*(i-c[i]);
		cout << cnt << endl;
	}
	return 0;
}
posted @ 2017-06-02 23:59  積水成淵  阅读(207)  评论(0编辑  收藏  举报