逆序对的定义:
对于一个数列
\[ a_1, a_2, a_3, \cdots, a_n \]
逆序对的个数是:
\[ \sum_{i < j, a_i > a_j} 1 \]node
涵涵有两盒火柴,每盒装有n根火柴,每根火柴都有一个高度。 如今将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为:\(∑(a_i−b_i)^2\)ios
其中\(a_i\)表示第一列火柴中第i个火柴的高度,\(b_i\)表示第二列火柴中第i个火柴的高度。数组
每列火柴中相邻两根火柴的位置均可以交换,请你经过交换使得两列火柴之间的距离最小。请问获得这个最小的距离,最少须要交换多少次?若是这个数字太大,请输出这个最小交换次数对 \(99,999,997\)取模的结果。数据结构
能够证实当两个数列都是通过排序以后的数列可使得\(\sum(a_i-b_i)^2\)取到最小,优化
根据上面的结论,就是说若是对于数列\(\{a_n\}, \{b_n\}\):
\[ a_1,~a_2,~a_3,~\cdots,~a_{n-1},~a_n \\ b_1,~b_2,~b_3,~\cdots,~b_{n-1},~b_n \]
对它们的\(id\)排序以后,原数组排序的过程当中至关于消除逆序对,可是原本的\(id\)是正序的,对于这个过程是对\(id\)增长逆序对的数量。能够说就因该是当\(a\)数组对于\(b\)数组想要移动到如出一辙所须要花的时间。可是如何求出这个步骤是一个值得讨论的问题:spa
首先定于\(q[a[i]] = b[i]\),最终的目标是\(a[i]=b[i] = t\),即\(q[t] = t\)。能够发现终极目标就是将\(q\)数组进行排序所须要的次数是多少,即求\(q\)的逆序对的个数。指针
代码:code
#include <iostream> #include <algorithm> using namespace std; const int maxn = 100005; const int P = 99999997; int n; struct node { int id, val; } a[maxn], b[maxn]; int T[maxn], m[maxn]; int lb(int i) { return i & (-i); } bool cmp(node x, node y) { return x.val < y.val; } void add(int i, int delta) { while (i <= n) { T[i] += delta; i += lb(i); } } int sum(int i) { int rtn = 0; while (i > 0) { rtn += T[i]; i -= lb(i); } return rtn; } int main() { cin >> n; for (int i = 1; i <= n; i ++) cin >> a[i].val; for (int i = 1; i <= n; i ++) { cin >> b[i].val; a[i].id = b[i].id = i; } sort(a + 1, a + 1 + n, cmp); sort(b + 1, b + 1 + n, cmp); for (int i = 1; i <= n; i ++) { m[a[i].id] = b[i].id; } int ans = 0; for (int i = 1; i <= n; i ++) { add(m[i], 1); ans = (ans + i - sum(m[i])) % P; } cout << ans << endl; return 0; }
约翰家的N 头奶牛正在排队游行抗议。一些奶牛情绪激动,约翰测算下来,排在第i 位的奶牛的理智度为Ai,数字可正可负。排序
约翰但愿奶牛在抗议时保持理性,为此,他打算将这条队伍分割成几个小组,每一个抗议小组的理智度之和必须大于或等于零。奶牛的队伍已经固定了先后顺序,因此不能交换它们的位置,因此分在一个小组里的奶牛必须是连续位置的。除此以外,分组多少组,每组分多少奶牛,都没有限制。游戏
约翰想知道有多少种分组的方案,因为答案可能很大,只要输出答案除以1000000009 的余数便可。
方程:
\[ f(i) = \sum_{0≤j<i,~\sum_{t = j + 1}^i a[t] ≥0}f(j), ~f(0)=1 \]
时间复杂度:\(O(n^3)\),使用前缀和优化:
\[ f(i) = \sum_{0≤j<i,~s[i]-s[j] ≥0}f(j), ~f(0)=1 \]
时间复杂度:\(O(n^2)\),使用树状数组存\(f\),其中下标为\(s\)。
对于\(s\)数组须要使用离散化,而且\(0\)要加入离散化的过程,由于\(f(0)=1\)。
代码:
#include <iostream> #include <algorithm> using namespace std; const int maxn = 100005; const int P = 1000000009; int n, a[maxn],s[maxn]; int T[maxn]; int lb(int i) { return i & (-i); } void add_sum(int i, int d) { // 因为数组的大小加了1,因此要n + 1 while (i <= n + 1) { T[i] = (T[i] + d) % P; i += lb(i); } } int query_sum(int i) { int ans = 0; while (i > 0) { ans = (ans + T[i]) % P; i -= lb(i); } return ans; } int main() { cin >> n; for (int i = 1; i <= n; i ++) { cin >> a[i]; s[i] = s[i-1] + a[i]; a[i] = s[i]; } // 对s进行离散化 sort(a, a + n + 1); for(int i = 0; i <= n; i ++) s[i]=lower_bound(a, a + n + 1, s[i]) - a + 1; // 设置f[0] = 1, 此处的s[0]表示在原数组中第0号元素在离散化过的数组中的位置 add_sum(s[0], 1); int ans = 0; for (int i = 1; i <= n; i ++) { ans = query_sum(s[i]); add_sum(s[i], ans); } cout << ans << endl; return 0; }
约翰有n个牧场,编号为1到n。它们之间有n−1条道路,每条道路链接两个牧场,经过这些道路,全部牧场都是连通的。
1号牧场里有个大牛棚,里面有n头奶牛。约翰会把它们放出来散步。奶牛按编号顺序出发,首先出发的是第一头奶牛,等它到达了目的地后,第二头奶牛才会出发,以后也以此类推。每头奶牛的目的地都不一样,其中第iii头奶牛的目的地是\(t_i\)号牧场。假如编号较大的奶牛,在通过一座牧场的时候,遇到了一头编号较小的奶牛停在那里散步,就要和它打个招呼。请你统计一下,每头奶牛要和多少编号比它小的奶牛打招呼。
首先这道题能够这样理解:
全部的奶牛从\(1\)号到\(n\)号依次离开出发到各自的牧场\(t_i\),在通过的道路上若是遇到编号比本身小的牛打招呼,统计总共打多少次招呼。因为编号从小到大,因此只要路径上有牛就确定会打招呼。
至此,题目的要求的就是对于每头牛,统计在去的路径上有多少头牛。
定义一个农场编号与牛编号的映射关系:\(cow[t[i]] = i\)。
在\(dfs\)整张图的过程当中,咱们会发现
因此有如下的代码:
#include <iostream> using namespace std; const int maxn = 100005; int n, p[maxn], head[maxn], cow[maxn], T[maxn], ans[maxn]; int lb(int i) { return i & (-i); } void modify(int i, int delta) { while (i <= n) { T[i] += delta; i += lb(i); } } int query(int i) { int ret = 0; while (i > 0) { ret += T[i]; i -= lb(i); } return ret; } struct edge { int to, next; } g[maxn * 2]; int ecnt = 2; void add_edge(int u, int v) { g[ecnt] = (edge) {v, head[u]}; head[u] = ecnt ++; } void dfs(int u, int fa) { int current = cow[u]; // 这个头牛的答案就是查询:树状数组当中编号比它小的,在路径上的个数和 ans[current] = query(current); // 刚刚进入这个节点(及其子树),因此把这个节点加入树状数组 modify(current, 1); for (int e = head[u]; e != 0; e = g[e].next) if (g[e].to != fa) dfs(g[e].to, u); // 即将退出该节点,不再会访问到,因此将其从树状数组中删除 modify(current, -1); } int main() { cin >> n; for (int i = 1; i < n; i ++) { int a, b; cin >> a >> b; add_edge(a, b); add_edge(b, a); } for (int i = 1; i <= n; i ++) { cin >> p[i]; cow[p[i]] = i; } dfs(1, 0); for (int i = 1; i <= n; i ++) { cout << ans[i] << endl; } return 0; }
在一些扑克游戏里,如德州扑克,发牌是有讲究的。通常称呼专业的发牌手为荷官。荷官在发牌前,先要销牌(burn card)。所谓销牌,就是把当前在牌库顶的那一张牌移动到牌库底,它用来防止玩家猜牌而影响游戏。
假设一开始,荷官拿出了一副新牌,这副牌有N 张不一样的牌,编号依次为1到N。因为是新牌,因此牌是按照顺序排好的,从牌库顶开始,依次为1, 2,……直到N,N 号牌在牌库底。为了发完全部的牌,荷官会进行N 次发牌操做,在第i 次发牌以前,他会连续进行Ri次销牌操做, Ri由输入给定。请问最后玩家拿到这副牌的顺序是什么样的?
举个例子,假设N = 4,则一开始的时候,牌库中牌的构成顺序为{1, 2, 3, 4}。
假设R1=2,则荷官应该连销两次牌,将1 和2 放入牌库底,再将3 发给玩家。目前牌库中的牌顺序为{4, 1, 2}。
假设R2=0,荷官不须要销牌,直接将4 发给玩家,目前牌库中的牌顺序为{1,2}。
假设R3=3,则荷官依次销去了1, 2, 1,再将2 发给了玩家。目前牌库仅剩下一张牌1。
假设R4=2,荷官在重复销去两次1 以后,仍是将1 发给了玩家,这是由于1 是牌库中惟一的一张牌。
输入格式:
第1 行,一个整数N,表示牌的数量。
第2 行到第N + 1 行,在第i + 1 行,有一个整数Ri,0<=Ri<N
输出格式:
第1 行到第N行:第i 行只有一个整数,表示玩家收到的第i 张牌的编号。
维护一个数组,其中存的是每张牌是否还在牌库当中,若在,则值为\(1\),反之,值为\(0\)。
每次摸牌,先销牌\(s\)张就是在剩下的\(m\)张牌中日后寻找\(s\)张牌就是了,若是还未找到\(s\)就已经为原状态的最后一张了,其实只须要进行对\(m\)的牌数进行取模,其实这个想法很是好理解,由于销牌的这个过程是滚动的。
因此咱们定义\(r_0\)为原来的找牌的“指针”,\(r_t\)为找到牌的指针,会有下式:
\[ r_t = (s + r_0) \mod m \]
如今,咱们来思考一下\(r_t\)的意义,其实它就是说剩下的牌中(牌的前后位置关系始终未变,变的是找牌的指针)第\(r_t\)张,也就是说咱们要找到要维护的数组当中前缀和为\(r_t\)的那个位置就是第\(i\)张牌的位置,即第\(i\)个答案。
从上述的表述能够理解:咱们须要维护一个树状数组,并二分答案。
可是其实有一个比二分答案更为简单的作法,就是模拟\(lb\)经过二分的方法访问\(T[]\)来获得位置,时间复杂度仅为\(O(\log n)\),而不是\(O(\log^2n)\)。
代码:
#include <iostream> #include <cstdio> #include <stdio.h> using namespace std; const int maxn = 700005; const int maxx = 1 << 20; int n, T[maxn]; inline int lb(int i) { return i & (-i); } int query(int x) { int pos = 0; // 第一次查询的区间最大,其后每次减半,至关于二分,但这里访问T数组的时间复杂度为O(1),故时间复杂度为O(log n)而不是O(log^2 n) for (int i = maxx; i > 0; i >>= 1) { // 更新位置 int j = i + pos; // 整个过程至关于在进行lb,因此x表明直到pos的前缀和与原来所求的差值,即距query目标还差的一部分 if (j <= n && T[j] <= x) { pos = j; x -= T[j]; } } return pos + 1; } void modify(int i, int d) { while (i <= n) { T[i] += d; i += lb(i); } } int main() { scanf("%d", &n); // O(n)时间建树状数组,缘由是a[i]=1 for (int i = 1; i <= n; i ++) T[i] = lb(i); int r = 0; for (int m = n; m > 0; m --) { int s; scanf("%d", &s); r = (r + s) % m; // 在树状数组当中查询值为r的位置,时间复杂度为O(n) int pos = query(r); // 因为这张牌被发掉了,因此应该将这张牌从树状数组当中移除 modify(pos, -1); // 答案就为这个位置编号 printf("%d\n", pos); } return 0; }