CF1470E Strange Permutation

一、题目

点此看题

二、解法

首先因为是排列并且翻转操作互不相交,所以可知操作不同对应的序列一定不同。

然后你可以在脑海里想象一个排列自动机之类的东西,首先要定义自动机上的状态,从前往后考虑操作,那么设 \(s(i,c)\) 表示考虑后缀 \([i,n]\),总的代价不超过 \(c\) 的状态。

首先考虑怎么计算一个状态内的总方案数,考虑在原序列的空隙中插入标记表示这两个数要不要交换,一段连续的标记就表示操作这一整段,所以方案数是:\(\sum_{j=0}^c{i-1\choose j}\)

还有一个问题是找后继状态,一个点的后继状态有 \(O(nc)\) 个,挨个遍历肯定是不能接受的,所以我们考虑把后继状态按字典序排列之后维护一个方案数前缀和之类的东西,然后二分找到后继状态。

现在问题的关键在于构建每个点的后继状态,考虑 \(s(i,c)\) 和 \(s(i+1,c)\) 后继状态的差别只有操作 \(i\) 为左端点的后继状态,那么我们可以从后往前构建,每次最多加入 \(O(c)\) 个后继状态。

然后考虑字典序问题,新加入的后继状态都会改变 \(i\) 位置的值,而以前的后继状态 \(i\) 位置的值是不变的,把后继状态看成一个双端队列,每次只用在队首和队尾插入即可。此外由于这个性质,\(s(i,c)\) 的后继状态是 \(s(1,c)\) 的一段区间,所以不需要可持久化,记录一下每个点的后继状态从哪里开始即可。

时间复杂度 \(O(nc^3+qc\log nc)\)

三、总结

字符串问题,特别是这种依序考虑字符的字典序问题,可以考虑构建出自动机,学过的比如后缀自动机、序列自动机等。当然你可以自己创造一个自动机,思考的关键点在于:状态、构建、查找。

#include <cstdio>
#include <algorithm>
#include <deque>
using namespace std;
const int M = 120005;
#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,q,a[M],ln[5],L[5][M],R[5][M],st[5][M],s[5][M];
struct node
{
	int l,r,w;
	node(int L=0,int R=0,int W=0) : l(L) , r(R) , w(W) {}
	bool operator < (const node &b) const
	{
		return a[r]<a[b.r];
	}
}b[M];
int way(int n,int c)//C(n-1,c)
{
	int res=1;
	if(n<=1) return c==0;
	if(n<=c) return 0;
	for(int i=n-1;i>=n-c;i--) res*=i;
	for(int i=c;i>=1;i--) res/=i;
	return res;
}
int get(int n,int c)
{
	int res=0;
	for(int i=0;i<=c;i++) res+=way(n,i);
	return res;
}
node F(int p,int c,int rk)//find the first operation
{
	int i=st[c][p],l=i+1,r=ln[c],ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(s[c][mid]-s[c][i]>=rk)
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	return node(L[c][ans],R[c][ans],s[c][ans-1]-s[c][i]);
}
void work()
{
	n=read();m=read();q=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int c=1;c<=m;ln[c]=0,c++)
		for(int i=1;i<=n;i++)
			L[c][i]=R[c][i]=s[c][i]=st[c][i]=0;
	//construction
	for(int c=1;c<=m;c++)
	{
		deque<node> q;q.push_front(node(n,n,1));
		for(int i=n-1;i>=1;i--)
		{
			int t=0;
			for(int j=1;j<=c && i+j<=n;j++)
			{
				b[++t]=node(i,i+j,get(n-i-j,c-j));
				if(a[i+j]<a[i]) st[c][i+1]++;
			}
			sort(b+1,b+1+t);int p=0;
			for(int j=1;j<=t;j++)
				if(a[b[j].r]<a[i]) p=j;
			for(int j=p;j>=1;j--)//attention the order
				q.push_front(b[j]);
			for(int j=p+1;j<=t;j++)
				q.push_back(b[j]);
		}
		ln[c]=q.size();
		for(int i=0;i<q.size();i++)
		{
			s[c][i+1]=s[c][i]+q[i].w;//do prefix sum
			L[c][i+1]=q[i].l;R[c][i+1]=q[i].r;
		}
		for(int i=1;i<=n;i++)
			st[c][i]+=st[c][i-1];//maintain the start position
	}
	//query
	while(q--)
	{
		int p=read(),rk=read(),nw=1,c=m;
		if(get(n,m)<rk) {puts("-1");continue;}
		while(c && nw<=n)
		{//还是要这么写,不操作或者不能操作停止 
			node t=F(nw,c,rk);
			c-=(t.r-t.l);nw=t.r+1;rk-=t.w;
			if(t.l<=p && p<=t.r) p=t.r-(p-t.l);
		}
		printf("%lld\n",a[p]);
	}
}
signed main()
{
	T=read();
	while(T--) work();
}
上一篇:LeetCode 1920. 基于排列构建数组(Build Array from Permutation)


下一篇:关于全排列--手写next_permutation()函数