【NAOI】套圈游戏

也许也可以放在t2 考察:性质观察能力+线性dp+dp优化+单调队列思想

题目背景:

假如要出比赛题再写,而且我数据和std的验证都没搞。

题目描述:

给一个长度为 \(n\) 的序列, 现在你要把这个序列拆成若干段,且保证每一段的 \(mex\) 相同。

询问最多能把这个区间分成几段,和分成最多段的方案数。答案对 \(10^9+7\) 取模。

\(mex\):这是指一个序列中最小的没有出现过的非负整数。如序列 \(mex\left \{1,2,3\right \}=0\),因为这个序列没有出现 \(0\)

输入格式:

共两行,第一行为一个正整数 \(n\),第二行为 \(n\) 个非负整数表示序列。

输出格式:

共一行,分别表示最大区间可能和其方案数。

数据范围:

\(1\le n \le 10^6\)

部分分:我们还没想。

题解:

这题可以做到 \(O(n)\),具体如下:

首先我们有一个性质就是所有小区间的 \(mex\) 等于原来序列的 \(mex\) 证明如下:

假设我们可以取出所有区间的 \(mex\) 都不是原区间的 \(mex\) 的一种方案,现在设原序列 \(mex=x_1\) 现在所有区间的 \(mex=x_2\)

那么一定有一个区间既有 \(x_1\)\(x_2\),所以原假设不成立。

所以所有小区间的 \(mex\) 等于原来序列的 \(mex\)

有了这个性质之后我们看一下怎么求最大段数,这个可以贪心。

我们先求出原序列的 \(mex\) 那么判断一个区间是否合法的方法就是看这个区间有没有包含所有小于 \(mex\) 的数。

接下来我们可以用一个类似单调队列的东西维护每一个颜色的最晚出现时间。这样我们就可以求出来对于每一个点,最早满足成段的位置。

然后我们就可以贪心了,发现区间越早结束一定不劣,所以我们直接暴力跳下一个最早结束位置就可以了。

然后是第二问,我们显然可以想到一个 dp, \(f_{i,j}\) 表示从左往右递推到了第 \(i\) 个点,现在 \(i\) 点作为第 \(j\) 段的结束点的方案数。这个东西是 \(O(n^3)\) 的,可以用前缀和优化到 \(O(n^2)\)

接下来考虑优化,我们发现有很多状态是多余的,而转移在前缀和优化后是\(O(1)\) 的。所以考虑优化状态数。

我们发现如果一个状态要对最终的答案有贡献,那么\(f_{i,j}\) 中的 \(j\) 一定为我们贪心算出来的最优区间的区间数或者最优区间数 \(-1\)

为什么,我们来证明一下:

假如我们有一个比当前最优区间下的区间次数小 \(2\) 即以上的答案,若要对答案有贡献,则需要在后续的数中创造多余原来最优区间多 \(2\) 的区间数,这与我们最开始的贪心矛盾。

所以假设错误,我们有了上述的结论。

所以我们可以靠这一点把状态数变为只有 \(2n\) 个,而且我们依旧可以用前缀和转移。最终复杂度 \(O(n)\)

std 我不知道有没有写挂(代码里我的 dp 是从后往前的 表示从 i 开始)

#include<bits/stdc++.h>
#define m(a) memset(a,0,sizeof(a))
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
int n,a[N],dui[N],g[N],f[N][2];
int bst[N],tmp[N],mex,ans1,vis[N];
void solve(){
	int cnt=0,l=1;
	for(int r=1;r<=n;r++){
		if(l>n) break;
		if(a[r]>=mex) continue;
		if(g[a[r]]==0) cnt++;
		g[a[r]]=r;
		while(l<=r&&cnt==mex){
			dui[l]=r;
			if(g[a[l]]==l){
				g[a[l]]=0;cnt--;
			}
			l++;
		}
	}
	int x=1;
	while(1){
		ans1++;
		bst[ans1]=dui[x];
		x=dui[x]+1;
		if(dui[x]==0){
			break;
		}
	}
	tmp[bst[ans1-1]+1]=1;
	for(int i=n;i>bst[ans1-1];i--){
		if(dui[i])f[i][0]++;
		f[i][0]+=f[i+1][0]; 
	}
	for(int i=ans1-1;i>=1;i--){
		for(int j=bst[i];j>=bst[i-1]+1;j--){
			int x=dui[j]+1;
			f[j][0]=(f[x][0]+f[j+1][0])%mod;
			f[j][1]=(f[j+1][1]+tmp[x])%mod;
		}
		for(int j=bst[i];j>=bst[i-1]+1;j--){
			f[j][0]=(f[j][0]-f[bst[i]+1][0])%mod;
			tmp[j]=f[j][1];f[j][1]=0;
		}
	}
	if(bst[1]==1) cout<<ans1<<" "<<(f[1][0]%mod+mod)%mod;
	else cout<<ans1<<" "<<((f[1][0]-f[2][0])%mod+mod)%mod;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
	    cin>>a[i];
		vis[a[i]]++;
		}
	for(int i=0;i<N;i++){
	    mex=i;
		if(vis[i]==0) break;
	}
	if(mex==0) cout<<n<<" "<<1;
	else solve();
	return 0;
}
posted @ 2025-08-10 19:48  NeeDna  阅读(41)  评论(0)    收藏  举报