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));
	}
}
posted @ 2021-08-25 10:47  C202044zxy  阅读(79)  评论(2编辑  收藏  举报