\(Atcoder\) \(Grand\) \(Contest\) \(010E\)
高质量思维题
\(AT\) 官方评分 \(3800+\) ,按 \(CF\) 评分差不多 \(3100\) 。
有一个 \(n\) 个数组成的序列 \(a_i\) ,第一个人会把整个序列任意排列,然后第二个人可以选择两个相邻的互质的数交换位置。
第一个人希望最终序列的字典序尽量小。(指的是在经过第二个人的修改后最小),而第二个人希望字典序尽量大,求最终序列。
\(1\le n\le2000\) ,\(1\le n \le 10^8\)
这道题的思维打开方式是,先想到一个结论,当第一个人重新排列之后,会得到一个新序列,两个数若是不互质,由于第二个人只能交换互质且相邻的两数,此时的相对位置(就是谁在谁的前面,谁在谁后面的意思),一定是不会被第二个人改变的。
那么我们先考虑第一个人怎么进行排列。
由于第二个人可以交换相邻互质的数,那么第一个人这时如果把互质的数字交换位置以试图满足自己的要求,让字典序变小。
而第二个人想要字典序变大,肯定会被第二个人改回去,故只需考虑不互质的数即可,也就是说,第一个人只能改变不互质的数的相对位置使得字典序变小。
那么我们在每一对不互质的数之间连一条无向边,那么其实第一个人就是在给无向边定向,使其成为一个有向图。
而字典序最小,小的数字尽可能向前放,先给原序列从小到大排序,遍历数组,如果当前点没有被访问过,那么就表明这个点是当前可以走的最小的点,从这个点开始搜索,按照搜索到的顺序,由前向后定向,这样可以保证第一个人的选择最优。
而对于第二个人,要做的就是在已经定好方向的图上求最大的访问字典序。
显然,在拓扑排序的过程中,可以使用一个大根堆维护当前可以选的最大点,每次输出即可。
而对于第一个人策略的证明,是很多题解没有提及的,下证这种情况最优。
设拓扑排序最后的一个点(也可能是一群点)为 \(x\) ,从这个点为中心的话,有很多类似于子树的东西在这个点汇合。
那么必然,每次从最小的点开始定向时,如果在 \(x\) 处,不按照遍历时间顺序建边,因为最小的在当前子树,那么其他子树中必然会存在一种更大的解法导致策略不正确,于是策略成立。
某个已经过了这道题的大佬告诉我直接排序,然后找互质的数,小的向大的连边,这显然是一种典型的错误解法,他居然能过,望大家多加注意,引以为戒。
#include<bits/stdc++.h> #define N 2005 using namespace std; int n,a[N],d[N],s[N][N],len[N],vis[N],e[N][N],t[N]; inline int read() { int x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while (isdigit(ch)){x=x*10+ch-48;ch=getchar();} return x*f; } int gcd(int a,int b) { if(!b) return a; else return gcd(b,a%b); } struct node { int val,id; bool operator < (const node &x) const { return val<x.val; } }; priority_queue<node>q; int tot; void topsort() { int pd=0; for(int i=1;i<=n;i++) { if(!d[i]) { q.push({a[i],i}); } } while(1) { node x=q.top(); printf("%d ",x.val);pd++; q.pop(); for(int i=1;i<=len[x.id];i++) { d[s[x.id][i]]--; if(!d[s[x.id][i]]) q.push({a[s[x.id][i]],s[x.id][i]}); } if(pd==n) break; } } void dfs(int u) { for(int i=1;i<=t[u];i++) { if(!vis[e[u][i]]) { s[u][++len[u]]=e[u][i]; d[e[u][i]]++; //cout<<a[u]<<" "<<a[e[u][i]]<<endl; vis[e[u][i]]=1; dfs(e[u][i]); } } } int main() { n=read(); for(int i=1;i<=n;i++) { a[i]=read(); } 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) { e[i][++t[i]]=j; e[j][++t[j]]=i; } } } for(int i=1;i<=n;i++) { if(!vis[i]) { vis[i]=1; dfs(i); } } topsort(); return 0; } ```` ### 总结 没有用到什么神妙算法,代码简洁,但一定是提升思维深度的一道好题,注重问题的分析过程就会有收获!