题意:
有一个数组a,先手可以把数列排成任意形状,然后后手可以任意交换两个相邻且互质的数。
然后先手要求序列尽可能小,后手要求序列尽可能大
问最终的序列长什么样子(进过后手操作过的)
吐槽:
这是一道AGC的E题,1600分,很吓人,而且是当时那场agc中过的人数最少的。
但是如果仔细挖掘其性质,却发现都只是一些基本的图论操作。
分析:
根据样例还有思考容易发现两两不互质的数之间的相对位置肯定不会改变。
我们考虑对两个不互质的数之间连一条边,得到了若干连通块。
先手的操作是边定向(交换数的位置就相当于对边定向),使得每个连通块都是DAG
后手就是要求在这个图中字典序最大的拓扑序。
不难发现先手的最优方法是对每个连通块中最小的点作为起点,然后向着没有被遍历的边走去。
同时后手的策略就是找到入度为0的点中最大的,然后把他加入答案中。
实现:
先对两两gcd不为1的点对进行构图,再通过dfs求出连通块和每个点的入度。
同时在dfs的时候处理出先手的遍历顺序,方便拓扑进行
之后通过拓扑求出答案。
代码:
应该还是挺短的吧,900B,写的应该也是易懂的吧。(但愿)
#include <bits/stdc++.h>
using namespace std ;
#define pb push_back
#define SZ(x) ((int)x.size())
const int N = 2010 ;
int vis[N], ok[N][N], in[N], a[N] ;
vector <int> g[N] ;
int n ;
void dfs(int rt) {
vis[rt] = 1 ;
for (int i = 1; i <= n; i++)
if (!vis[i] && ok[rt][i]) {
in[i]++ ;
g[rt].pb(i) ;
dfs(i) ;
}
}
void Top() {
priority_queue <int> q ;
for (int i = 1; i <= n; i++) if (!in[i]) q.push(i) ;
while (!q.empty()) {
int now = q.top() ;
q.pop() ;
cout << a[now] << " " ;
for (int i = 0; i < SZ(g[now]); i++) q.push(g[now][i]) ;
}
}
signed main(){
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]) ;
sort(a + 1, a + n + 1) ;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (__gcd(a[i], a[j]) != 1) ok[i][j] = ok[j][i] = 1 ;
for (int i = 1; i <= n; i++) if (!vis[i]) dfs(i) ;//遍历连通块
Top() ;
}