P7444「EZEC7」猜排列【dp】

正题

题目链接:https://www.luogu.com.cn/problem/P7444


题目大意

一个长度为\(n\)的排列,已知每个\(c_i\)表示那个排列中\(mex\)\(i\)的区间个数。求满足条件的排列个数

\(1\leq n\leq 5\times 10^5,c_i\geq 0,\sum_{i=1}^nc_i=\frac{n(n+1)}{2}-1\)


解题思路

考虑一个朴素的\(dp\),设\(f_{i,l,r}\)表示加入了\(1\sim i\),然后最大区间是\([l,r]\)时的方案。

那么每次插入一个数\(i\)的时候如果\(c_i=0\)那么它一定在目前的最大区间里,否则需要扩展到区间外,每次有往左或者往右扩展。

不难发现对于\(1\sim i\)扩展到\(l\),那么\(r\)是固定的,所以我们可以直接用\(f_{i,l}\)来表示状态,但是这样还是\(O(n^2)\)的,其实状态数比较少的,因为对于一个\(i\)来说需要扩展的\(a_i\)都是一些倍数形的。

其实总状态数不会超过\(\sum_{i=1}^n\sqrt{a_i}\),因为上限很难到,所以用个\(vector\)记录一下状态就能够过了


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const ll N=5e5+10,P=998244353;
ll n,a[N],f[2][N],ed[2][N],vis[N];
vector<int> v[2]; 
signed main()
{
	scanf("%lld",&n);
	for(ll i=0;i<n;i++)
		scanf("%lld",&a[i]);
	ll Ans=0,s=0;
	for(ll i=1;i<=n;i++){
		ll l=i-1,r=n-i;
		if(l*(l+1)+r*(r+1)==a[0]*2)
			s=i,Ans++;
	}
	if(!Ans)return puts("0")&0;
	f[0][s]=1;ed[0][s]=s;v[0].push_back(s);
	for(ll i=1;i<n-1;i++){
		v[i&1].clear();
		for(ll p=0;p<v[~i&1].size();p++){
			ll l=v[~i&1][p],r=ed[~i&1][l];
			if(vis[l]==i)continue;vis[l]=i;
			if(!a[i]){
				if(r-l-i<0)continue;
				(f[i&1][l]+=f[~i&1][l]*(r-l-i+1)%P)%=P;
				ed[i&1][l]=r;v[i&1].push_back(l);
			}
			else{
				ll lk=l,rk=n-r+1;
				if(a[i]%lk==0){
					ll nr=r+a[i]/lk;
					(f[i&1][l]+=f[~i&1][l])%=P;
					ed[i&1][l]=nr;v[i&1].push_back(l);
				} 
				if(a[i]%rk==0){
					ll nl=l-a[i]/rk;
					(f[i&1][nl]+=f[~i&1][l])%=P;
					ed[i&1][nl]=r;v[i&1].push_back(nl);
				}
			}
		}
		for(ll p=0;p<v[~i&1].size();p++)
			f[~i&1][v[~i&1][p]]=0;
	}
	ll ans=0;
	for(ll i=1;i<=n;i++)
		(ans+=f[(n-2)&1][i])%=P;
	printf("%lld\n",ans*Ans%P);
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/14578729.html