Codeforces Round #740 Div. 2
E. Bottom-Tier Reversals
题目描述
给定一个长度为 \(n\) 的排列(\(n\) 为奇数),每次你可以翻转一个长度为奇数的前缀,构造方案使得 \(\frac{5n}{2}\) 之内将这个排列排好序,如果无法达到这个目标输出 \(-1\)
\(n\leq 2000\)
解法
考虑能排序的必要条件:对于所有 \(i\) 满足 \(i\) 和 \(a_i\) 有相同的奇偶性,因为是构造题我们盲猜这就是充要条件。
然后我好不容易给出一个 \(\frac{7n}{2}\) 的构造做法,赛场上直接被这题送走\(...\)
正解是从后往前考虑再加上归纳法,也就是每次考虑把 \(n\) 放在最后一个位置,\(n-1\) 放在倒数第二个位置,需要在五步之内完成这个构造,手玩可以得到下列构造过程:
- 找到 \(n\) 所在的位置 \(x\),翻转前缀 \(x\) 使得 \(n\) 到位置 \(1\)
- 找到 \(n-1\) 所在的位置 \(y\),翻转前缀 \(y-1\) 使得 \(n\) 到位置 \(i-1\)
- 翻转前缀 \(y+1\) 使得 \(n-1\) 在第二个位置,\(n\) 在第三个位置。
- 翻转前缀 \(3\),使得 \(n\) 在第一个位置,\(n-1\) 在第二个位置。
- 最后翻转前缀 \(n\) 即可达到目的。
因为翻转只会影响前缀,所以我们把数放在后面就等价于归纳到长度为 \(n-2\) 的排列,那么一定在 \(\frac{5n}{2}\) 步之内得到解。
总结
从限制较小的地方考虑(比如此题后缀受翻转影响小考虑把数放后面),构造问题常考虑归纳法。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,fk,a[M],ans[M];
void zxy(int k)
{
for(int i=1;i<=k/2;i++)
swap(a[i],a[k-i+1]);
ans[++m]=k;
}
signed main()
{
T=read();
while(T--)
{
n=read();m=fk=0;
for(int i=1;i<=n;i++)
{
a[i]=read();
if(i%2!=a[i]%2) fk=1;
}
if(fk) {puts("-1");continue;}
for(int i=n;i>1;i-=2)
{
int x=0;
for(int j=1;j<=i;j++)
if(a[j]==i) x=j;
zxy(x);
for(int j=1;j<=i;j++)
if(a[j]==i-1) x=j;
zxy(x-1);zxy(x+1);
zxy(3);zxy(i);
}
printf("%d\n",m);
for(int i=1;i<=m;i++)
printf("%d ",ans[i]);
puts("");
}
}
F. Top-Notch Insertions
题目描述
考虑对一个长度为 \(n\) 的数组 \(a\) 进行插入排序,对于每个位置 \(i\),如果 \(a_{i-1}\leq a_i\) 那么不需要进行插入排序;否则找到第一个位置 \(j\) 满足 \(a_i<a_j\) 把 \(a_i\) 插入到 \(a_j\) 的前面,此时我们记录下插入信息 \((i,j)\)
现给出 \(m\) 个插入信息 \((x_i,y_i)\),问有多少个初始序列 \(a\) 的插入排序恰好能对应给定的插入信息。
\(n\leq 2\cdot 10^5,m\leq 2\cdot 10^5\),多组数据只保证 \(\sum m\leq 2\cdot 10^5\)
解法
这道题是真的难,但是我一个学弟随便切掉了😱
考虑初始序列 \(a\) 是 \([a_1,a_2,a_3,a_4,a_5]\),如果给出的插入信息是 \((3,1),(4,1),(5,3)\),那么可以唯一确定排序后的结果是 \([a_4,a_3,a_5,a_1,a_2]\),所以初始序列唯一对应一个排序序列,并且由于插入过程确定,所以排序序列也唯一对应一个初始序列,那么初始序列和排序序列构成一个双射,问题可以转化成对排序序列的计数。
还是用上面的例子,由排序序列可以得到限制 \(a_4\leq a_3\leq a_5\leq a_1\leq a_2\),我们考虑插入过程中还带来了什么限制:
- 如果 \(a_{i-1}\leq a_i\),那么 \(a_i\) 一定在 \(a_{i-1}\) 后面,限制已经被排序序列考虑。
- 如果需要插入,那么 \(a_i<a_j\) 会产生一个额外的限制,并且类似地其他的 \(\leq\) 都已经被考虑过了。
设排序序列 \(b\) 有 \(c\) 个位置满足 \(b_i<b_{i+1}\)(注意并不完全是上面所谓的”限制“个数和,因为如果 \(a_{i1},a_{i2}\) 同时插入到 \(a_j\) 的后面那么最终只会产生一个小于号),显然这是一个组合数问题,先把 \(b\) 差分,然后可以转化成有 \(n+1\) 个未知数,其中有 \(c+1\) 个未知数要求为正,要求和为 \(n\),利用隔板法可知解的个数是 \({2n-1-c\choose n}\)
现在的问题就是求 \(c\) 嘛,考虑关键的地方是要看 \(a_j\) 是否之前就被插入了,如果没有才让 \(c\leftarrow c+1\),可以用平衡树维护,按题目给的插入信息来模拟,如果 \(a_j\) 不在平衡树就相当于没有被插入过,然后打标记来维护插入后的整体平移即可,时间复杂度 \(O(m\log n)\)
总结
一开始我想根据题目的限制建拓扑图,但是这样会多出来很多无用的限制反而不好考虑,所以一定要去除无效限制,本题就是用考虑排序序列的方法去除了很多无效限制。
计数问题要找映射关系,如果有双射关系很可能有大用处。
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
const int M = 400005;
const int MOD = 998244353;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,fac[M],inv[M];
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}
int C(int n,int m)
{
if(n<m || m<0) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
namespace treap
{
int rt,cnt,ch[M][2],val[M],hp[M],tg[M];
void clear()
{
for(int i=1;i<=cnt;i++)
ch[i][0]=ch[i][1]=val[i]=tg[i]=0;
rt=cnt=0;
}
struct node
{
int p[2];
node() {p[0]=p[1]=0;}
}emp;
void add(int x,int v)
{
if(!x) return ;
val[x]+=v;tg[x]+=v;
}
void down(int x)
{
if(!tg[x]) return ;
add(ch[x][0],tg[x]);
add(ch[x][1],tg[x]);
tg[x]=0;
}
node split(int x,int v)
{
if(!x) return emp;
int d=val[x]<=v;down(x);
node y=split(ch[x][d],v);
ch[x][d]=y.p[d^1];
y.p[d^1]=x;
return y;
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
if(hp[x]<hp[y])
{
down(x);
ch[x][1]=merge(ch[x][1],y);
return x;
}
down(y);
ch[y][0]=merge(x,ch[y][0]);
return y;
}
int get(int x)//creat an element x
{
cnt++;val[cnt]=x;
hp[cnt]=rand();
return cnt;
}
int find(int x)//exist x?
{
node t=split(rt,x-1),w=split(t.p[1],x);
if(w.p[0]>0)//exist
{
add(w.p[0],1);add(w.p[1],1);
rt=merge(t.p[0],merge(w.p[0],w.p[1]));
return 0;
}
//if not,we need to insert
w.p[0]=merge(get(x),w.p[1]);
add(w.p[0],1);
rt=merge(t.p[0],w.p[0]);
return 1;
}
}
signed main()
{
T=read();init(4e5);
while(T--)
{
n=read();m=read();
treap::clear();int ans=0;
for(int i=1;i<=m;i++)
{
int a=read(),b=read();
if(treap::find(b)) ans++;
}
printf("%lld\n",C(2*n-ans-1,n));
}
}