C. Inversion Graph
https://codeforces.com/contest/1638/problem/C
题目描述
给你一个序列,找到所有逆序对 ( a i , a j ) (a_i, a_j) (ai,aj) 并将它们连接起来,求连通块个数。
输入描述
每个测试包含多个测试用例。第一行包含一个整数 t (1≤t≤105)——测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 n (1≤n≤105) — 排列的长度。
每个测试用例的第二行包含 n 个整数 p1,p2,…,pn (1≤pi≤n) — 排列的元素。
保证所有测试用例的 n 之和不超过 2×105。
输出描述
连通块个数
样例
#1
6
3
1 2 3
5
2 1 4 3 5
6
6 1 4 2 5 3
1
1
6
3 2 1 6 5 4
5
3 1 5 2 4
3
3
1
1
2
1
每个单独的测试用例如下图所示。彩色方块代表排列的元素。对于一个排列,每种颜色代表一些连通分量。不同颜色的数量就是答案。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u6Wdrd70-1683084240478)(images/1645000749170.png)]
提示
解析
观察上面样例的图,可以得知如下信息:
- 连通块的最大值一定大于右边的数(根据逆序对的特性可知)。
- 若某个数属于某个连通块,那么这个数一定小于该连通块的最大值。
- 若某个数大于左边的所有数(这也意味着左边的数无法与该数相连),则该数是一个新的连通块。
有上面的几个信息,我们知道只需要维护最大值就可以了,简单来说就是“大于该数的就是新的连通块,小于该数的则连接该连通块”,因此我们只需要维护最大值就行,如何维护?
我们可以使用单调栈维护(自顶向下为从大到小),只需要存储最大值就行,最后栈内剩余的数量即为连通块的数量。
以 [ 3 , 1 , 5 , 2 , 4 ] [3, 1, 5, 2, 4] [3,1,5,2,4] 为例: [ 3 ] → [ 3 ] → [ 3 , 5 ] → [ 5 ] → [ 5 ] [3] \rightarrow [3] \rightarrow [3, 5] \rightarrow [5] \rightarrow [5] [3]→[3]→[3,5]→[5]→[5]
-
栈为空,入栈 3,得 [ 3 ] [3] [3] 。
-
栈 [ 3 ] [3] [3] ,因为 1 < 3 1 < 3 1<3 ,所以 1 是属于 3 的连通块。
-
栈 [ 3 ] [3] [3] ,因为 5 > 3 5 > 3 5>3 ,所以 5 是新的连通块,得到 [ 3 , 5 ] [3, 5] [3,5] 。
-
栈 [ 3 , 5 ] [3, 5] [3,5] ,因为 2 < 5 2 < 5 2<5 和 2 < 3 2 < 3 2<3 ,表示 2 即使属于 5 的连通块,也是属于 3 的连通块,所以 3 也属于 5 的连通块。
这也是维护时需要注意的点:判断一个数是不是属于当前连通块的时候,如果是,则还需要判断是否也属于前面的连通块。
单调栈是否一定保证为单调递增(自底向上为从小到大)?
答:一定,因为若数小于栈顶即不属于当前连通块,就不入栈,若大于栈顶,则是新的连通块,会入栈。
AC Code
public class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
public static void main(String[] args) throws Exception {
int T = nextInt();
while(T-- != 0) {
int n = nextInt();
Stack<Integer> stack = new Stack<>();
for(int i = 0; i < n; i++) {
int x = nextInt();
if(stack.isEmpty()) {
stack.push(x);
continue;
}
if(x < stack.peek()) {
// 数小于栈顶,意为属于该连通块
int top = stack.peek(); // 保留最大连通块的值
// 判断是否也属于前面的连通块
while(!stack.isEmpty() && stack.peek() > x) stack.pop();
stack.push(top);
} else {
stack.push(x); // 这是新的连通块
}
}
out.println(stack.size());
}
out.flush();
}
public static int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
}