【题解】AtCoder At_abc399_d [ABC399D] Switch Seats
题目大意
请点击 这里 查看原题面。
有一个长度为 2 ⋅ N 2\cdot N 2⋅N 的序列 A A A,其中 1 , 2 , … , N 1,2,\dots,N 1,2,…,N 各出现了两次。现在要找满足如下条件的数对 ( a , b ) (a,b) (a,b) 的个数:
- a a a 的两次出现不相邻。
- b b b 的两次出现不相邻。
- 交换一个 a a a 和一个 b b b 的位置任意多次之后,可以使两个 a a a 和 两个 b b b 相邻。
有多测,且 1 ≤ N ≤ 2 ⋅ 1 0 5 1\le N\le 2\cdot 10^5 1≤N≤2⋅105。
思路
N
N
N 的值很大,对于每组数据,我们需要一个时间复杂度为
O
(
N
)
O(N)
O(N),总时间复杂度
O
(
∑
1
T
N
)
O(\sum_1^T N)
O(∑1TN)。考虑只枚举二元组中的一个数,通过计算得到第二个数并验证。为了更高效、更方便,我们不妨记录一下
1
1
1 到
N
N
N 在数组中两次出现的位置。实际上,直接开两个数组就可以维护,我使用了 vector
,一个动态数组,虽然浪费空间,但是代码难度不高。
我们可以发现,如果想要满足第三条要求,那么 a a a 和 b b b 需要满足以下条件:
- a a a 的第一次出现位置和 b b b 的第一次出现位置相邻。
- a a a 的第二次出现位置和 b b b 的第二次出现位置相邻。
我们需要规定一个“限制”或者“顺序”来避免重复统计。每次只统计第一次出现在
a
a
a 后面且相邻的
b
b
b,然后判断第二次是否相邻。但是还有一个小细节:当数据中出现形容 1 2 1 2
的时候,程序遍历到
1
1
1,统计了一次,然后遍历到
2
2
2,又会统计一次。但这是错误的,因为这相当于是调用了两次
1
1
1 的第二个位置。所以我们要小心这个问题。
代码
// 评测记录:https://atcoder.jp/contests/abc399/submissions/64303061
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int t, n, a[400010]; // 注意数组大小
vector<int> p[200010]; // p 记录位置
bool check(int x, int y) // 判断两个位置是否相邻
{
return abs(x - y) == 1;
}
int main()
{
cin >> t;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
p[i].clear(); // 多测初始化
for (int i = 1; i <= n * 2; i++)
{
cin >> a[i];
p[a[i]].push_back(i);
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
if (check(p[i][0], p[i][1]))
continue; // 两个 a 相邻
int j = a[p[i][0] + 1];
if (p[j][0] != p[i][0] + 1)
continue; // 细节处理
if (check(p[j][0], p[j][1]))
continue; // 两个 b 相邻
if (check(p[i][1], p[j][1]))
ans++; // 第二次出现的位置相邻就统计答案
}
cout << ans << endl;
}
return 0;
}
总结
这道题难度不大,个人感觉是 普及 / 提高 − \color{gold}普及/提高- 普及/提高− 左右。最后,希望这篇题解对你有帮助,如有想法欢迎评论或私信提出!