Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)
震惊,我竟然能独立切掉 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]&°[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(乱搞+找性质)的更多相关文章
- Atcoder Grand Contest 032 E - Modulo Pairing(乱搞+二分)
Atcoder 题面传送门 & 洛谷题面传送门 神仙调整+乱搞题. 首先某些人(including me)一看到最大值最小就二分答案,事实上二分答案对这题正解没有任何启发. 首先将 \(a_i ...
- Atcoder Grand Contest 015 F - Kenus the Ancient Greek(找性质+乱搞)
洛谷题面传送门 & Atcoder 题面传送门 一道难度 Au 的 AGC F,虽然看过题解之后感觉并不复杂,但放在现场确实挺有挑战性的. 首先第一问很简单,只要每次尽量让"辗转相除 ...
- AtCoder Grand Contest 008
AtCoder Grand Contest 008 A - Simple Calculator 翻译 有一个计算器,上面有一个显示按钮和两个其他的按钮.初始时,计算器上显示的数字是\(x\),现在想把 ...
- AtCoder Grand Contest 008 D - K-th K
题目传送门:https://agc008.contest.atcoder.jp/tasks/agc008_d 题目大意: 给你一个长度为\(N\)的序列\(A\),请你构造一个长度为\(N^2\)的序 ...
- AtCoder Grand Contest 008 A
Problem Statement Snuke has a calculator. It has a display and two buttons. Initially, the display s ...
- AtCoder Grand Contest 008题解
传送门 \(A\) 分类讨论就行了 然而我竟然有一种讨论不动的感觉 int x,y; inline int min(R int x,R int y){return x<y?x:y;} inlin ...
- AtCoder Grand Contest 012
AtCoder Grand Contest 012 A - AtCoder Group Contest 翻译 有\(3n\)个人,每一个人有一个强大值(看我的假翻译),每三个人可以分成一组,一组的强大 ...
- AtCoder Grand Contest 011
AtCoder Grand Contest 011 upd:这篇咕了好久,前面几题是三周以前写的... AtCoder Grand Contest 011 A - Airport Bus 翻译 有\( ...
- AtCoder Grand Contest 031 简要题解
AtCoder Grand Contest 031 Atcoder A - Colorful Subsequence description 求\(s\)中本质不同子序列的个数模\(10^9+7\). ...
随机推荐
- win10安装git fatal: open /dev/null or dup failed: No such file or directory错误解决方法
原因看大家意思应该是 非即插即用驱动文件null.sys问题. 网上有很多方案.最后试了一个可行的. 替换 windows/system32/drivers/null.sys为网盘中的文件即可. 链 ...
- logstash的安装和简单使用
logstash的安装和简单使用 一.安装 1.下载并解压 2.logstash 一些命令行参数 1.查看帮助信息 2.加载指定pipeline文件路径 3.检测配置文件语法是否有错误 4.热加载pi ...
- zuul过滤器filter 的编写
通过上一节(zuul的各种配置)的学习,我们学会了zuul路由的各种配置,这一节我们来实现一下zuul的过滤器功能.那么为什么需要用到zuul的过滤器呢?我们知道zuul是我们实现外部系统统一访问的入 ...
- [转]DDR内存条rank的概念和区分
1:什么是RANK? 答:CPU与内存之间的接口位宽是64bit,也就意味着CPU在一个时钟周期内会向内存发送或从内存读取64bit的数据.可是,单个内存颗粒的位宽仅有4bit.8bit或16bit, ...
- 0x02
#include<bits/stdc++.h> using namespace std; int n,a[10][10],vis[10],ans,b[10][10]; inline int ...
- cf2A Winner(implementation)
题意: N个回合. 每个回合:name score[名为name的这个人得了score分(可负可正)]. 问最后谁的累积分数是最高的.设为M.如果有好几个都得了M,找出这几个人中哪个最早回合累积分数超 ...
- popStar机机对战数据生成器代码(C#)
代码: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; ...
- java性能优化常用工具jmap、jstack
jmap:java内存映像工具 jmap用于生成堆转储快照,比较常用的option包括-heap,-histo,-dump [root@localhost script]# jmap -h Usage ...
- ansible简介安装配置
ansible简介 ansible是一款,自动化运维管理工具.顾名思义是用于批量去管理及安装服务及批量管理主机. ansible与saltstack对比 ansible优点:配置简单,部署容易除主管理 ...
- Linux 系统分区方案 详细教程
简单分区方案 实际上,很多时候我们只需要分两个区:/和交换分区,日常使用基本不会有任何影响,甚至于交换分区对于现在的电脑来说都不是必要的,我们完全可以只分配一个根分区.linux只需要一个/根分区就可 ...