有关mex的题目

https://codeforces.com/problemset/problem/739/A

题意:

你有 m 个区间,要求构造一个长度为 n 的序列使得这 m 个区间中 mex 最小的最大。

int main(){
	ans=n=read(),m=read();
	for(int i=1;i<=m;++i){
		int x=read(),y=read();
		ans=min(y-x+1,ans);
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;++i)
		printf("%d ",i%ans);
	putchar('\n');
	return 0;
}

https://codeforces.com/problemset/problem/1375/D

const int N=1003;
int n,a[N],stk[N*2],top,lef;bool sta[N],vis[N];
inline int qrmex(){
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;++i)
		vis[a[i]]=1;
	for(int i=0;i<=n;++i)
		if(!vis[i])return i;
	return 114514;
}
int main(){
	int T=read();
	while(T--){
		lef=n=read(),top=0;
		for(int i=1;i<=n;++i)a[i]=read();
		memset(sta,0,sizeof(sta));
		for(int i=1;i<=n;++i)
			if(a[i]==i-1){sta[i]=1;--lef;}
		while(lef){
			int mex=qrmex();
			if(mex==n){//当前无法使一个数能达到目标状态,就先将这个数变成 n ,这样会空出一个位置来
				for(int i=1;i<=n;++i)
					if(!sta[i]){stk[++top]=i,a[i]=n;break;}
			}else{//否则就将这个数固定在目标状态,以后不再管它
				stk[++top]=mex+1;
				a[mex+1]=mex;
				sta[mex+1]=1;
				--lef;
			}
		}
		printf("%d\n",top);
		for(int i=1;i<=top;++i)
			printf("%d ",stk[i]);
		putchar('\n');
	}
	return 0;
}

https://www.luogu.com.cn/problem/P8445

const int N=1000006,INF=0x3f3f3f3f;
int n,a[N],b[N],c[N],ans=-INF;
vector<int>lis[N];
int main(){
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=n;++i)b[i]=read();
	for(int i=1;i<=n;++i){
		if(a[i]==b[i])
			c[i]=a[i];
		else c[i]=n+2;
	}
	for(int i=1;i<=n;++i)
		lis[c[i]].push_back(i);
	for(int i=0;i<=n;++i){//枚举mex
		int siz=lis[i].size(),len=0;
		for(int j=1;j<siz;++j){
			len=lis[i][j]-lis[i][j-1]-1;
			ans=max(ans,len-i);
		}
		if(siz){
			ans=max(ans,lis[i][0]-1-i);
			ans=max(ans,n-lis[i][siz-1]-i);
		}else ans=max(ans,n-i);
	}
	printf("%d\n",ans);
	return 0;
}

https://www.luogu.com.cn/problem/P2391

因为雪花只会保留最后一个染的颜色 所以我们倒叙操作对没染色的雪花进行染色即可 已经染色的雪花就不管了

因为数据1e6 区间修改线段树是过不了的 考虑用到并查集维护序列的连通性

因为每个雪花最多只会被染色一次 所以复杂度为线性

fa[x] 表示x前第一个未染色的雪花

#include<bits/stdc++.h>
using namespace std;
#define reg register
inline int read(){
    reg char c;reg int f=1,x=0;
    while(!isdigit(c)) { if(c=='-') f=-1;c=getchar(); }
    while(isdigit(c)) { x=x*10+c-'0';c=getchar(); }
    return x*f;
}
int n,m,p,q,fa[1000001],col[1000001];
inline int myfind(int x){ if(x==fa[x]) return x;return fa[x]=myfind(fa[x]); }
int main(){
    n=read(),m=read(),p=read(),q=read();
    for(reg int i=1;i<=n;++i) fa[i]=i;
    for(reg int i=m;i>=1;--i){//依照题意,倒序染色(颜色以最后一次染上的颜色为准)
        int l=(i*p+q)%n+1,r=(i*q+p)%n+1;
        if(l>r) swap(l,r);
        for(reg int j=r;j>=l;){
            int t=myfind(j);
            if(t==j){
                col[j]=i,fa[j]=myfind(j-1)/*维护连通性*/;
            }j=fa[j];//直接跳到下一个可染色的点
        }
    }
    for(reg int i=1;i<=n;++i) printf("%d\n",col[i]);
}

这里扯一个其他的用到并查集维护序列的题

https://www.luogu.com.cn/problem/P4145

题意:区间修改更号 区间求和

分析:貌似是用线段树维护 但是区间开根号不知道怎么维护

通过观察可以得到,在此题数据范围内一个数开方 6 次就一定会变成 1 ,我们就没必要再对这个数开方了,可以利用并查集直接跳过.

fa[x]表示x后第一个不为1的数

#include<iostream>
#include<cmath>

#define LL long long
#define N 100010

using namespace std;

LL c[N<<2],a[N],f[N],n,m,x,l,r,t;

LL find(LL x)
{
	return f[x]==x?x:f[x]=find(f[x]);//一定路径压缩,我T了七八次。。。 
}

void add(LL x,LL y)
{
	for(;x<=n;x+=x&-x)  c[x]+=y;
}

LL ask(LL x)
{
	LL res=0;
	for(;x;x-=x&-x)  res+=c[x];
	return res;
}//普通的树状数组add和ask函数 

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		add(i,a[i]);
		f[i]=i;
	}//初始化 
	f[n+1]=n+1;//因为n要合并到n+1 
	
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>l>>r;
		if(l>r)  swap(l,r);//交换 
		if(x)  cout<<ask(r)-ask(l-1)<<'\n';
		else while(l<=r)//这里直接用l模拟指针 
		{
			LL t=(LL)sqrt(a[l]);//要减少a[l]-t
			add(l,t-a[l]);a[l]=t;//更新 
			f[l]=a[l]<=1?l+1:l;//开方到1后直接把父亲更改为下一个数 
			l=f[l]==l?l+1:find(f[l]);//如果这个数没有开方到1,到下一个数,否则找下一个为1的数 
		}
	}
	
	return 0;
}

https://www.luogu.com.cn/problem/P6852

非常好的一题!!!!完全不止绿题的水准

#include<bits/stdc++.h>
using namespace std;
const int N=1000006;
int n,m,sum[N],l[N],r[N],nl[N],nr[N],ans[N],fa[N];
inline int fidf(int x){return x==fa[x]?x:fa[x]=fidf(fa[x]);}
inline void merge(int x,int y){
	int fx=fidf(x),fy=fidf(y);
	if(fx!=fy)fa[fx]=fy;
}
int main(){
	n=read(),m=read();
	for(int i=0;i<=n;++i)nl[i]=n+1,r[i]=n;//注意是一个0到n,而不是0到n-1的排列
	for(int i=1;i<=m;++i){
		int L=read(),R=read(),x=read();
		nl[x]=min(nl[x],L),nr[x]=max(nr[x],R);
		if(x==0)++sum[L],--sum[R+1];//差分来区间加,若某位置有值则说明这个位置不能填0
		else l[x-1]=max(l[x-1],L),r[x-1]=min(r[x-1],R);
	}
	for(int i=n-1;i>=0;--i){//由mex的性质可推得
		l[i]=max(l[i],l[i+1]);
		r[i]=min(r[i],r[i+1]);
	}
	for(int i=1;i<=n;++i)fa[i]=i;
	for(int i=0;i<=n;++i){//特判0能否填
		if(i)sum[i]+=sum[i-1];
		if(l[0]<=i&&i<=r[0]&&!sum[i])
			{ans[i]=0;merge(i,i+1);goto step;}
	}
	return puts("-1"),0;
	step:
	for(int i=1;i<=n;++i){
		for(int j=l[i];j<=r[i];++j){
			j=fidf(j);//找空位,节省时间
			if(nl[i]<=j&&j<=nr[i]){j=nr[i];continue;}
			if(j<=r[i]){ans[j]=i;merge(j,j+1);goto end;}
		}
		return puts("-1"),0;
		end:;
	}
	for(int i=0;i<=n;++i)
		printf("%d ",ans[i]);
	putchar('\n');
	return 0;
}

https://codeforces.com/problemset/problem/1527/D

非常好的一道题 又算是长见识了

const int N=200005;
int n;ll ans[N];
vector<int>edge[N];
int dep[N],fat[N],son[N],tps[N];ll siz[N];
void dfs1(int u,int f){
	siz[u]=1,dep[u]=dep[f]+1,fat[u]=f;son[u]=0;//多组数据清空son!!!
	for(auto v:edge[u]){
		if(v==f)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int t){
	tps[u]=t;
	if(son[u])dfs2(son[u],t);
	for(auto v:edge[u]){
		if(v==son[u]||v==fat[u])continue;
		dfs2(v,v);	
	}
}
inline int qrlca(int x,int y){
	while(tps[x]!=tps[y]){
		if(dep[tps[x]]<dep[tps[y]])swap(x,y);
		x=fat[tps[x]];
	}return dep[x]<dep[y]?x:y;
}
inline ll getsiz(int loc){//暴力跳父亲应该是能hack掉的
	ll tmp=siz[loc];
	while(fat[tps[loc]]>1)
		loc=fat[tps[loc]],tmp=siz[loc];
	while(fat[loc]>1)
		loc=fat[loc],tmp=siz[loc];
	return tmp;
}
int main(){
	int T=read();
	while(T--){
		n=read();
		for(int i=0;i<=n;++i)edge[i].clear();
		for(int i=0;i<=n+1;++i)ans[i]=0;//ans会计算到n+1
		for(int i=1;i<n;++i){
			int u=read()+1,v=read()+1;
			edge[u].push_back(v);
			edge[v].push_back(u);
		}
		dfs1(1,0);dfs2(1,1);
		ll tmp=1;
		for(auto v:edge[1]){ans[1]+=tmp*siz[v];tmp+=siz[v];}
		int l=1,r=1;ll sizl=0,sizr=0;
		for(int i=2;i<=n;++i){
			int a=qrlca(i,l),b=qrlca(i,r);
			if(a==l&&b==1){
				if(l==1)sizl=getsiz(i);
				l=i;
			}else if(a==1&&b==r){
				if(r==1)sizr=getsiz(i);
				r=i;
			}else if(a!=i&&b!=i)break;
			if(l==1)ans[i]=siz[r]*(n-sizr);
			else if(r==1)ans[i]=siz[l]*(n-sizl);
			else ans[i]=siz[l]*siz[r];
		}
		printf("%lld ",(ll)n*(n-1)/2-ans[1]);
		for(int i=2;i<=n+1;++i)printf("%lld ",ans[i-1]-ans[i]);
		putchar('\n');
	}
	return 0;
}

https://codeforces.com/problemset/problem/1613/D

为啥这么设置状态 因为题目要求与mex相差不超过1 所以我们只考虑mex和与之相差1的数

转移方程:

dp[x[i]-1][1]=dp[x[i]-1][1]+(dp[x[i]-1][1]+dp[x[i]-1][0])

dp[x[i]+1][0]=dp[x[i]+1][0]+(dp[x[i]+1][0]+dp[x[i]-1][0])

dp[x[i]+1][1]=dp[x[i]+1][1]+(dp[x[i]+1][1])

括号外不选x[i]和括号内选x[i]的方案数

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=500005;
const ll MOD=998244353;
int n;
ll dp[N][2],ans;
int main(){
	int T=read();
	while(T--){
		n=read();ans=0;
		for(int i=0;i<=n+2;++i)
			dp[i][0]=dp[i][1]=0;
		dp[0][0]=1;
		for(int i=1;i<=n;++i){
			int x=read();
			//dp_{x,0/1} 表示到当前位置 mex 值取 x ,无/有 x+1 的方案数
			dp[x+1][0]=(dp[x+1][0]+(dp[x+1][0]+dp[x][0])%MOD)%MOD;
			dp[x+1][1]=(dp[x+1][1]+(dp[x+1][1]))%MOD;
			dp[x-1][1]=(dp[x-1][1]+(dp[x-1][1]+dp[x-1][0])%MOD)%MOD;
			//可以考虑加入这个数或不加入这个数,第二个括号内即是加入这个数的贡献
		}
		dp[0][0]=(dp[0][0]-1+MOD)%MOD;//对于最终的答案而言是不合法的
		for(int i=0;i<=n;++i)
			ans=(ans+dp[i][0]+dp[i][1])%MOD;
		printf("%lld\n",ans);
	}
	return 0;
}

https://codeforces.com/contest/1793/problem/D

分析:

枚举每段子串肯定不行 考虑依次找到mex=1 =2 =3... 的子串有多少个累加起来即可

考虑mex=i 满足要求的子串一定包括了 1,2,3..i-1 并且一定不包括i

这个很好维护 每次不断更新[L,R] 即可

又因为两个序列都必须满足 所有对两个序列求并集

然后分情况讨论

posa[now] 表示第一个序列数值为now的位置 posb[now] 表示第二个序列数值为now的位置

[L,R] 表示两个序列都包含了[1,i-1]的区间

(1) posa[now]和posb[now] 都大于R

(2) posa[now]和posb[now] 都小于L

(3) posa[now]和posb[now] 一个大于R一个小于L

(4) posa[now]和posb[now] 有一个在[L,R]中 这种情况是不会对答案造成贡献的

特别地 mex=1 和 mex=n+1 的两种情况得单独考虑

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define int ll
const int maxn=2e5+5;
int n; 
ll a[maxn],posa[maxn],b[maxn],posb[maxn];
ll ans;
void solve();
ll calc(int x){
	return x*(x+1)/2;
}
signed main(){
	int T;T=1;
	while(T--)solve();
     return 0;
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),posa[a[i]]=i;
	for(int i=1;i<=n;i++)scanf("%lld",&b[i]),posb[b[i]]=i;
	int now=2,l,r,L,R;
	ll tl,tr;
	l=r=posa[1];
	L=R=posb[1];
	ans+=calc(min(l,L)-1)+calc(n-max(r,R))+calc(max(r,R)-min(l,L)-1);
	while(now<=n){
		tl=min(l,L);
		tr=max(r,R);
		l=min(l,posa[now]);r=max(r,posa[now]);
	    L=min(L,posb[now]);R=max(R,posb[now]);
		if((posa[now]>=tl&&posa[now]<=tr)||(posb[now]>=tl&&posb[now]<=tr)){
			now++;continue;
		}
	    if(posa[now]>=tr&&posb[now]>=tr)
	    ans+=tl*(min(posa[now],posb[now])-tr);
	    else if(posa[now]<=tl&&posb[now]<=tl)
	    ans+=(tl-max(posa[now],posb[now]))*(n-tr+1);
	    else ans+=((max(posa[now],posb[now])-tr))*(tl-min(posa[now],posb[now]));
	    now++;
	}++ans;
	cout<<ans;
}

posted @ 2022-09-13 16:44  wzx_believer  阅读(25)  评论(0编辑  收藏  举报