Atcoder 题面传送门 & 洛谷题面传送门

震惊,我竟然能独立切掉 AGC E 难度的思维题!

hb:nb tea 一道

感觉此题就是找性质,找性质,再找性质(

首先看到排列有关的问题,我们可以很自然地将排列拆成一个个置换环,即我们建一张图 \(G\),对于 \(i\in[1,n]\) 连边 \(i\to p_i\),那么题目的要求就可以转化为:对于每个点 \(i\),它置换环上下一步或者下下步为 \(a_i\)。

做出这个简单的转化后,就可以发现一个非常 trivial 的性质:

Observation \(1\). 如果存在三个及以上的 \(i\) 满足 \(a_i=x\),那么答案肯定是 \(0\)

证明显然,因为只有 \(x\) 上一个或者上上个才能在两步之内到达 \(i\)。

这给我们一个启发,我们可以建一张新的图 \(G'\),对于每个 \(i\) 连边 \(i\to a_i\),那么这个图满足每个点的入度 \(\le 2\),每个点的出度为 \(1\)。我们还可以发现,\(\forall i\),\(i,a_i\) 在 \(G\) 中肯定是在同一个置换环内的,因此 \(G'\) 连通块的情况和 \(G\) 连通块的情况是大体相同的。因此我们可以观察得出性质:

Observation \(2\). 对于某个 \(G\) 中某个环 \(C\),除了 \(|C|\) 为偶数并且并且每个 \(a_i\) 都是 \(i\) 向后两步的位置的情况除外,其他情况 \(C\) 中的元素在 \(G'\) 的基图中依然连通。

说人话,就是对于绝大部分情况,原来在同一个连通块中的元素后来依然在一个连通块中,唯一一个例外是如果置换环的大小 \(siz\) 为偶数,并且每个 \(a_i\) 都是 \(i\) 向后两步的位置,这种情况原来的置换环会分裂为两个大小为 \(\dfrac{siz}{2}\) 的置换环。

简单证明一下,对于一个置换环而言,如果存在某个 \(i\) 满足 \(a_i\) 是 \(i\) 的下一步位置,那么如果置换环大小为 \(2\) 结论显然成立,否则设 \(j\) 为 \(i\) 上一个位置,那么 \(a_j\) 必然是 \(i\) 或 \(a_i\) 中的一个,那么我们就可以将 \(j\) 加入 \(i\) 所在的连通块,再考虑 \(j\) 的上一个位置 \(k\),\(a_k\) 必然也是 \(i\) 或 \(j\) 中的一个,如此归纳下去即可得证。也就是说置换环分裂的情况只可能是每个 \(a_i\) 都是 \(i\) 往后两格的位置,而如果置换环大小为奇数,简单画张图即可发现,最后建出来的必然还是一个与原置换环大小相同的环。得证。


这又给我一个启发:不妨来观察一下 \(G'\) 中每个连通块的形态。我又画了几张图,发现了以下性质:

Observation \(3\). \(G'\) 一定是一个基环内向森林,并且每个不在基环树上的点都形成一条条链

证明不会

我们考虑再来看一下这个环和这些链有啥性质,以上图为例,按照之前的推论,\(G'\) 的每个连通块一定是由一个环(我们称之为主环),和一些以主环上某个点为结尾的链(如果某条链以环上某个点 \(x\) 结尾,那么我们就称这条链是挂在 \(x\) 上的),我们假设挂在 \(i\) 上的链的长度为 \(b_i\)——我们显然在 BFS 求出每个连通块的环的同时求出 \(b_i\)。

那么问题就来了,\(G'\) 里面这个主环和原来的置换环有什么关系呢?

方便起见,我们将主环上的点按原来置换环上的顺序依次编号 \(c_1,c_2,\cdots,c_k\)。

Obseration \(4\). 主环一定是由 \(c_1\) 沿着置换环每次跳 \(1\) 格或两格,跳 \(k\) 步之后形成的。

证明显然,因为置换环上相邻两个点 \(c_i,c_{i+1}\) 必然满足 \(c_{i+1}\) 是 \(c_i\) 下一个节点,或者 \(c_{i+1}\) 是 \(c_i\) 下下个节点,所以每次必然是跳 \(1\) 步或 \(2\) 步。

知道了这个性质之后,我们就有对于 \(k<\) 置换环大小 \(|C|\) 的情况,跳 \(k\) 步跳的总长度必然在 \(k\) 和 \(2k\) 之间,又因为它最后回到了原点,因此总长度必然是 \(|C|\) 的整数倍,而 \(2k<2|C|\),因此跳的总长度只可能是 \(|C|\),这样一来我们可以将连通块分为三类:

  • \(2k<|C|\),此时就算每步跳 \(2\) 格也达不到一周,总方案数就是 \(0\)。

  • \(|C|<2k<2|C|\),这种情况比较复杂我们过会儿讨论。

  • \(2k=2|C|\),即 \(k=|C|\),此时该连通块就是一个环,那么我们记 \(num_i\) 为有多少个这样的大小为 \(i\) 的连通块,那么对于每个环而言,它在原排列 \(p\) 中有三种可能:

    • 保持不变,即 \(p\) 中也存在一个和该置换环一模一样的置换环
    • 如果 \(i>1\) 且为奇数,那么存在一个长度等于 \(i\) 的置换环,满足从环上某个点开始每次跳两步得到的环与原连通块相同。
    • 和另一个大小为 \(i\) 的置换环拼成一个长度为 \(2i\) 的置换环,假设我们已经选出了这两个置换环 \(C_1,C_2\),那么我们固定住 \(C_1\) 的某个位置,置换环 \(C_1\) 放置的位置就确定了,此时 \(C_2\) 可以随意旋转,有 \(i\) 种可能。举个栗子,置换环 \((1,2,3,4)\) 和 \((5,6,7,8)\) 拼在一起有 \((1,5,2,6,3,7,4,8),(1,6,2,7,3,8,4,5),(1,7,2,8,3,5,4,6),(1,8,2,5,3,6,4,7)\) 四种可能(梦回 P4709?)

    因此考虑枚举有多少对长度为 \(i\) 的置换环拼在一起,记这个数为 \(j\),那么选出这 \(j\) 对置换环的方案数为 \(\dfrac{1}{j!}\prod\limits_{k=0}^{j-1}\dbinom{num_i-2k}{2}\),这 \(j\) 对置换环拼在一起的方案数为 \(i^j\),安放剩余置换环的方案数为 \(t^{num_i-2j}\),其中 \(t\) 表示每个置换环在原排列中的方案数,如果 \(i>1\) 且 \(i\) 为奇数那么 \(t=2\),否则 \(t=1\),因此总贡献为 \(i^j\times t^{num_i-2j}\dfrac{1}{j!}\prod\limits_{k=0}^{j-1}\dbinom{num_i-2k}{2}\),这个东西显然可以在枚举 \(j\) 的同时维护,时间复杂度 \(\mathcal O(n\log n)\) 或 \(\mathcal O(n)\),取决于你的心情。


到这里我们已经解决了本题的大部分情况,还剩最棘手的一种情况没有解决——那就是 \(|C|<2k<2|C|\),即图是一个纯正的基环树(大雾)的情况。

考虑这些链是哪里来的,观察一会儿可以发现:

Observation \(5\). 对于一个主环上的点 \(c_x\) 引出的长度为 \(len\) 的链,它在原置换环上有以下两种情况(其中蓝线为 \(G'\) 中的边,绿边为 \(G\) 中的边(注:下图中的 \(C_{x-len+1}\) 应为 \(C_{x-len+2}\),\(C_{x-len},C_{x-len-1}\) 也同理应该变为 \(C_{x-len+1},C_{x-len}\),传上去之后才发现有问题,懒得改了/cy):

说人话用数学语言来表述,也就是说假设这条链上 \(len\) 个点为 \(p_1,p_2,p_3,\cdots,p_{len}\),那么有以下可能:

  • \(p_1\) 夹在 \(c_x,c_{x-1}\) 中间,\(p_2\) 夹在 \(c_{x-1},c_{x-2}\) 中间,……,\(p_{len}\) 夹在 \(c_{x-len+1},c_{x-len}\) 中间
  • \(c_x,c_{x-1}\) 中间没有点,\(p_1\) 夹在 \(c_{x-1},c_{x-2}\) 中间,\(p_2\) 夹在 \(c_{x-2},c_{x-3}\) 中间,……,\(p_{len}\) 夹在 \(c_{x-len},c_{x-len-1}\) 中间

我们给 \(c_i,c_{i-1}\) 中间边的状态(中间夹了多少个点)量化为一个数组 \(e_i\),那么显然 \(e_i\le 2\),否则 \(c_i\) 就不是 \(c_{i-1}\) 的下个点或下下个点了,此时以下两种排列的方案就可以表述为:

  • 令 \(e_x,e_{x-1},e_{x-len+1}\) 加 \(1\)
  • 强制令 \(e_x=0\),并令 \(e_{x-1},e_{x-2},\cdots,e_{x-len}\) 加 \(1\)

不难发现这相当于一个环上的区间覆盖问题,第一种方案可以视作覆盖了区间 \([x-len+1,x]\),第二种方案可视作覆盖了区间 \([x-len,x]\),每个点覆盖的区间不能相交。

这样还是不太好直接下手,因为它是一个环,直接 \(dp\) 显然会出现后效性。不过我们可以很自然地想到断环成链,即找到环上第一个 \(b_i\ne 0\) 的点 \(ps\),枚举 \(ps\) 覆盖的区间(\([ps-b_{ps}+1,ps]\) or \([ps-b_{ps},ps]\)),这样就可以视作链上的 \(dp\) 了,\(dp_i\) 表述考虑链上 \(ps\sim i\) 的点的放置方案,转移就实时维护上一个 \(b_j\ne 0\) 的点 \(j\),分情况讨论:

  • \(j\ge i-b_i+1\),\(dp_j=0\),因为无论覆盖 \([i-b_i+1,i]\) 还是 \([i-b_i,i]\) 都会与上一个区间有交
  • \(j=i-b_i\),\(dp_j=dp_i\),因为只能覆盖 \([i-b_i+1]\)
  • \(j<i-b_i\),\(dp_j=2dp_i\)

具体实现时,不需真的建出 \(dp\) 数组,由于 \(dp_i\) 只与上一个 \(j\) 的 \(dp\) 值有关,因此可以维护一个变量表示上一个位置的 \(dp\) 值,复杂度 \(\mathcal O(\text{环的大小})\),而由于 \(\sum\text{环的大小}\) 为 \(\mathcal O(n)\),因此这部分复杂度是 linear 的。


于是这题就真的做完了,复杂度 \(n\log n\) or \(\mathcal O(n)\)

细节多得一批……程序下面放了两组 hack 数据,是我在与 std 对拍过程中曾经错过的数据。

const int MAXN=1e5;
const int MOD=1e9+7;
const int INV2=5e8+4;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,a[MAXN+5],in[MAXN+5],deg[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
for(int i=(ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int bel[MAXN+5],comp=0,siz[MAXN+5],cyc[MAXN+5],b[MAXN+5];
bool vis[MAXN+5];
void dfscomp(int x){
if(bel[x]) return;bel[x]=comp;
for(int e=hd[x];e;e=nxt[e]) dfscomp(to[e]);
}
vector<int> c[MAXN+5];
void findcyc(int x){
if(vis[x]) return;c[bel[x]].pb(x);
vis[x]=1;findcyc(a[x]);
}
int num[MAXN+5],way[MAXN+5];
int main(){
scanf("%d",&n);init_fac(n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),in[a[i]]++;
for(int i=1;i<=n;i++) deg[i]=in[i];
for(int i=1;i<=n;i++) adde(i,a[i]),adde(a[i],i);
for(int i=1;i<=n;i++) if(in[i]>=3) return puts("0"),0;
for(int i=1;i<=n;i++) if(!bel[i]) comp++,dfscomp(i);
for(int i=1;i<=n;i++) siz[bel[i]]++,b[i]=1;
queue<int> q;
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
while(!q.empty()){
int x=q.front();q.pop();vis[x]=1;
if(!--in[a[x]]) q.push(a[x]),b[a[x]]+=b[x];
}
for(int i=1;i<=n;i++) if(vis[i]&&deg[i]==2) return puts("0")&0;
for(int i=1;i<=n;i++) if(!vis[a[i]]&&vis[i]) b[a[i]]+=b[i];
for(int i=1;i<=n;i++) b[i]--;
for(int i=1;i<=n;i++) if(!vis[i]) findcyc(i);
for(int i=1;i<=comp;i++) cyc[i]=c[i].size();
int ans=1;
for(int i=1;i<=comp;i++){
if(siz[i]==cyc[i]){num[siz[i]]++;continue;}
if(siz[i]==2){way[i]=1;continue;}
if((cyc[i]<<1)<siz[i]) return puts("0")&0;
int ps=-1,len=-1;
for(int j=0;j<c[i].size();j++){
int x=c[i][j];
if(b[x]){ps=j;len=b[x];break;}
} assert(~ps);
int pre=ps,pre_dp=1;
for(int j=ps+1;j<c[i].size()+ps;j++){
int id=c[i][j%c[i].size()];
if(b[id]){
int cur_dp=0;
if(j<ps-len+1+c[i].size()){
if(j-b[id]+1>pre) cur_dp=pre_dp;
if(j-b[id]>pre) cur_dp=(cur_dp+pre_dp)%MOD;
} pre_dp=cur_dp;pre=j;
}
} way[i]=(way[i]+pre_dp)%MOD;
if(len!=cyc[i]){
pre=ps;pre_dp=1;
for(int j=ps+1;j<c[i].size()+ps;j++){
int id=c[i][j%c[i].size()];
if(b[id]){
int cur_dp=0;
if(j<ps-len+c[i].size()){
if(j-b[id]+1>pre) cur_dp=pre_dp;
if(j-b[id]>pre) cur_dp=(cur_dp+pre_dp)%MOD;
} pre_dp=cur_dp;pre=j;
}
} way[i]=(way[i]+pre_dp)%MOD;
} ans=1ll*ans*way[i]%MOD;
}
for(int i=1;i<=n;i++){
int mul=1,sum=0;
for(int j=0;(j<<1)<=num[i];j++){
sum=(sum+1ll*mul*qpow(((i&1)&&(i^1))?2:1,num[i]-(j<<1))%MOD*ifac[j])%MOD;
mul=1ll*mul*(num[i]-(j<<1))%MOD*(num[i]-(j<<1)-1)%MOD*INV2%MOD*i%MOD;
} ans=1ll*ans*sum%MOD;
} printf("%d\n",ans);
return 0;
}
/*
6
2 3 1 1 4 4
*/
/*
6
2 3 1 1 4 5
*/

Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)的更多相关文章

  1. Atcoder Grand Contest 032 E - Modulo Pairing(乱搞+二分)

    Atcoder 题面传送门 & 洛谷题面传送门 神仙调整+乱搞题. 首先某些人(including me)一看到最大值最小就二分答案,事实上二分答案对这题正解没有任何启发. 首先将 \(a_i ...

  2. Atcoder Grand Contest 015 F - Kenus the Ancient Greek(找性质+乱搞)

    洛谷题面传送门 & Atcoder 题面传送门 一道难度 Au 的 AGC F,虽然看过题解之后感觉并不复杂,但放在现场确实挺有挑战性的. 首先第一问很简单,只要每次尽量让"辗转相除 ...

  3. AtCoder Grand Contest 008

    AtCoder Grand Contest 008 A - Simple Calculator 翻译 有一个计算器,上面有一个显示按钮和两个其他的按钮.初始时,计算器上显示的数字是\(x\),现在想把 ...

  4. AtCoder Grand Contest 008 D - K-th K

    题目传送门:https://agc008.contest.atcoder.jp/tasks/agc008_d 题目大意: 给你一个长度为\(N\)的序列\(A\),请你构造一个长度为\(N^2\)的序 ...

  5. AtCoder Grand Contest 008 A

    Problem Statement Snuke has a calculator. It has a display and two buttons. Initially, the display s ...

  6. AtCoder Grand Contest 008题解

    传送门 \(A\) 分类讨论就行了 然而我竟然有一种讨论不动的感觉 int x,y; inline int min(R int x,R int y){return x<y?x:y;} inlin ...

  7. AtCoder Grand Contest 012

    AtCoder Grand Contest 012 A - AtCoder Group Contest 翻译 有\(3n\)个人,每一个人有一个强大值(看我的假翻译),每三个人可以分成一组,一组的强大 ...

  8. AtCoder Grand Contest 011

    AtCoder Grand Contest 011 upd:这篇咕了好久,前面几题是三周以前写的... AtCoder Grand Contest 011 A - Airport Bus 翻译 有\( ...

  9. AtCoder Grand Contest 031 简要题解

    AtCoder Grand Contest 031 Atcoder A - Colorful Subsequence description 求\(s\)中本质不同子序列的个数模\(10^9+7\). ...

随机推荐

  1. 【UE4 C++ 基础知识】<3> 基本数据类型、字符串处理及转换

    基本数据类型 TCHAR TCHAR就是UE4通过对char和wchar_t的封装 char ANSI编码 wchar_t 宽字符的Unicode编码 使用 TEXT() 宏包裹作为字面值 TCHAR ...

  2. Convolutional Neural Network-week2编程题2(Residual Networks)

    1. Residual Networks(残差网络) 残差网络 就是为了解决深网络的难以训练的问题的. In this assignment, you will: Implement the basi ...

  3. [no_code]团队任务拆解Alpha

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 团队任务拆解 我们在这个课程的目标是 远程协同工作,采用最新技术开发软件 这个作业在哪个具体方面帮 ...

  4. C/C++编程笔记:浪漫流星雨表白装b程序

    作为一个未来可能会成为一个专业程序员的小伙们,不知道你们现在学到哪里了,学了点东西之后有没有想在你女朋友面前装个大大的b呢,今天小编就给你一个机会来研究一下下边的代码吧,保证大写的N,当然大佬是排除在 ...

  5. Python环境配置详细步骤以及第一个程序

    打开python官网:https://www.python.org/ 在官网找与自己电脑系统匹配的版本路径  这里以python3.7.2版本为例: 下载完成后,使用管理员身份进行安装:  打开命令提 ...

  6. C语言零基础入门难发愁,那就快来看看这篇基础整理资料吧

    C语言程序的结构认识 用一个简单的c程序例子,介绍c语言的基本构成.格式.以及良好的书写风格,使小伙伴对c语言有个初步认识. 例1:计算两个整数之和的c程序: #include main() { in ...

  7. 21.10.14 test

    题目 WOJ5078 到 WOJ5081 T1 Problem A \(\color{green}{100}\) 由于每轮要选择尽量多的边删除,所以想到无向图的生成树,因为在生成树上再加一条边就会形成 ...

  8. Spring Security:简单的保护一个SpringBoot应用程序(总结)

    Spring Security 在 Java类中的配置 在 Spring Security 中使用 Java配置,可以轻松配置 Spring Security 而无需使用 XML . 在Spring ...

  9. python 修饰器(decorator)

    转载:Python之修饰器 - 知乎 (zhihu.com) 什么是修饰器,为什么叫修饰器 修饰器英文是Decorator, 我们假设这样一种场景:古老的代码中有几个很是复杂的函数F1.F2.F3.. ...

  10. Spring Cloud 生产环境性能优化

    先思考几个问题: 什么是百万并发连接? 什么是吞吐量? 操作系统能否支持百万连接? 操作系统维持百万连接需要多少内存? 应用程序维持百万连接需要多少内存? 百万连接的吞吐量是否超过了网络限制? 百万的 ...