CodeForces 612E Square Root of Permutation

2019-11-05 09:44:46来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

CodeForces 612E Square Root of Permutation

洛谷题目页面传送门 & CodeForces题目页面传送门

定义一个\(1\sim n\)的排列\(a\)的平方\(a^2=b\),当且仅当\(\forall i\in[1,n],b_i=a_{a_i}\),即\(a^2\)为将\(a\)\([1,2,\cdots,n]\)上映射\(2\)次所得的排列。现在给定一个\(1\sim n\)的排列\(a\),求\(b\)使得\(b^2=a\),即求\(a\)的平方根。若有多解,输出任意一个。若无解,输出\(-1\)

\(n\le10^6\)

看到关于映射的题目,首先想到转化为图论。\(\forall i\in[1,n]\),从\(i\)\(a_i\)建一条有向边,得到一张有向图\(G=(V=[1,n],E=\{(x,y)\mid a_x=y\})\)。很显然,对于一个排列建出的图一定是由若干个环组成的,因为每个节点的入度、出度均为\(1\)。那么将\(a\)平方之后,每个节点会指向它原来指向的节点原来指向的节点。考虑平方之后每个环会变成什么样子:很显然,若此环大小为奇,则仍然是一个奇环;若此环大小为偶,则会分裂成\(2\)个大小为原来一半的环。

现在我们得到\(a\)的图,需要根据上述变形规律还原出它的平方根。考虑每个环,分\(2\)种情况:

  1. 大小为奇。它在\(a\)的平方根的图中可能本来就是一个奇环,也可能是由一个偶环分裂出来的一半;
  2. 大小为偶。它在\(a\)的平方根的图中不可能是一个奇环,则只能是一个大小是\(4\)的倍数的偶环分裂出来的一半。

显然,需要合并的情况都是\(2\)个大小相同的环合并,所以不同大小的环是互相独立的,我们可以对于每个大小分别考虑还原。分\(2\)种情况:

  1. 大小为奇。那么大小为它的每个环要么是自己还原,要么是另找一个环合并。显然自己还原是无条件的,所以我们贪心地选择将每个环自己还原(将每个节点指向的节点设为平方根图中该节点指向的节点指向的节点);
  2. 大小为偶。此时大小为它的每个环只能是另找一个环合并。于是如果大小为它的环的个数为奇的话,则不能两两配对,直接输出\(-1\)走人;否则任意组合两两合并(合并时在\(2\)个环中都任找一个起点,然后\((1,1)\to(2,1)\to(1,2)\to(2,2)\to(1,3)\to\cdots\)(其中\((x,y)\)表示第\(x\)个环中从起点开始数的第\(y\)个节点)作为平方根中的环)。

每个节点都被访问常数次,所以时间复杂度为\(\mathrm O(n)\)

下面贴代码:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
void read(int &x){//快读 
    x=0;char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
void prt(int x){//快写 
    if(x>9)prt(x/10);
    putchar(x%10^48);
}
const int N=1000000;
int n; 
int a[N+1];//平方之后的排列 
bool vis[N+1];//DFS时的标记数组 
vector<int> v;//此SCC(环)中的点 
void dfs(int x){//DFS 
    if(vis[x])return;
    vis[x]=true;
    v.pb(x);
    dfs(a[x]);
}
vector<vector<int> > vv;//所有环 
vector<int> havsz[N+1];//大小为[i]的所有环的编号 
int ans[N+1];//a的平方根 
int main(){
    read(n);
    for(int i=1;i<=n;i++)read(a[i]);
    for(int i=1;i<=n;i++)if(!vis[i]){//找出每个环 
        v.clear();
        dfs(i);
        if(v.size()&1){//如果是奇环直接自己还原 
            vector<int> v0(v.size());//还原之后的环 
            for(int j=0,now=0;j<v.size();j++,(now+=2)%=v.size())v0[now]=v[j];
            for(int j=0;j<v.size();j++)ans[v0[j]]=v0[(j+1)%v.size()];//将还原之后的环用排列的形式存到a的平方根里 
        }
        else vv.pb(v);//否则存下来等到后面找其他环合并 
    }
    for(int i=0;i<vv.size();i++)havsz[vv[i].size()].pb(i); 
    for(int i=2;i<=n;i+=2)//枚举偶数大小 
        if(havsz[i].size()&1)return puts("-1"),0;//不能两两配对 
        else for(int j=0;j<havsz[i].size();j+=2){//枚举环对 
            vector<int> &v0=vv[havsz[i][j]]/*第1个环*/,&v1=vv[havsz[i][j+1]]/*第2个环*/,v2(2*v0.size())/*还原之后的环*/;
            for(int k=0,now=0;k<v0.size();k++,now+=2)v2[now]=v0[k],v2[now+1]=v1[k];
            for(int k=0;k<v2.size();k++)ans[v2[k]]=v2[(k+1)%v2.size()];//将还原之后的环用排列的形式存到a的平方根里
        }
    for(int i=1;i<=n;i++)prt(ans[i]),putchar(' ');//输出答案 
    return 0;
}

原文链接:https://www.cnblogs.com/ycx-akioi/p/CodeForces-612E.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:多重背包问题

下一篇:stl源码学习(版本2.91)--list