CSP-S模拟10 欧几里得的噩梦 清扫 购物 ants

T1:线性基+二分图思想。给你n个数,保证二进制表示中1的个数<=2,求这n个数子集异或可以得到的数个数以及最少保留多少个数可以满足的到异或后数集合与原集合相同。(n<=5e5)

考场:思路就是BFS找可以找到的数,然后尝试两两异或的到另一个,就把另一个删除,但是会少删除,因为假设
1010,0100,0001-->1111,1111删不掉,但是它其实可以删,因为异或有传递性。
正解:考虑找出可以删除的数,假如第i个数a位和b位是1,那么把a--b连边,发现如果下一个数b--c,再下一个数c--d,再出现一个a--d,就是环,对于异或的含义就是a--b--c,异或时b位就可以消掉了,所以剩下ac-1,继续下去一样。
个数:因为保证了每个加进去的数一定不能互相异或出来,所以每个数异或结果一定不同,每个数可以选或者不选,ans=\(2^x\).x是选的数个数。一旦重复,一定是环,和构造矛盾。

点击查看代码






#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=5e5+100;const  ll mod=1e9+7;
int n,m;
int fa[N],del[N];
inline int getfa(int x)
{
	if(x==fa[x])return x;
	return fa[x]=getfa(fa[x]);
}
inline int qpow(int x,int y)
{
	int ans=1;
	while(y)
	{
		if(y&1)
		ans=(ll)ans*x%mod;
		x=(ll)x*x%mod;
		y>>=1;
	}
	return ans;
}
int main()
{
  // freopen("1.in","r",stdin);
   //freopen("a.out","w",stdout);
   n=re(),m=re();
   _f(i,1,m+1)fa[i]=i;int no=0;
   _f(i,1,n)
   {
	   int op=re();
	   if(op==2)
	   {
		   int a1=re(),a2=re();
		   int fa1=getfa(a1),fa2=getfa(a2);
		   if(fa1==fa2)no++,del[i]=1;
		   else fa[fa1]=fa2;
	   }
	   else 
	   {
		   int a1=re();
		   int fa1=getfa(a1),fa2=getfa(m+1);
		   if(fa1==fa2)no++,del[i]=1;
		   else fa[fa1]=fa2;
	   }
   }
   chu("%d %d\n",qpow(2,n-no),n-no);
   _f(i,1,n)
   if(!del[i])chu("%d ",i);
    return 0;
}
/*
6 20
2 10 19
2 8 17
2 2 4
2 3 10
2 13 14
2 1 10

*/

T2:清道夫正在清扫一棵树。这棵树有个节点,第i个节点有ai个石头。清道夫在每一次清扫中,会选择两个不同的叶子,然后在它们最短路径上的每个点都扫除恰好一个石头。请问能否恰好清除所有石头。(n<=5e5)

考场:发现任意一个点是根等效;对于合法情况,可以递推得到每个非叶子需要向上传递和自己内部交流的路径数量。
up_num=sum_val_son-val_root。向上2倍,自己一样,多出来的就是向上的。
所以对于,每个非叶子合法判断有2个
(1)必须有足够的叶子消耗本节点的val。\(upnum>=0\)
(2)内部经过自己的路径必须能分配开。\(maxson<=lefnum,lefnum=val-upnum\)

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=1e5+100;
int head[N],tot,n;
ll val[N];
struct Node
{
	int to,nxt;
}e[N<<1];//双
inline void Add(int x,int y)
{
	e[++tot].to=y;e[tot].nxt=head[x];head[x]=tot;
	e[++tot].to=x;e[tot].nxt=head[y],head[y]=tot;
}
inline void dfs(int x,int fa)
{
	int son=0;
	ll sum=0;ll mxson=0;
	for(rint i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa)continue;
		dfs(to,x);
		++son;
		mxson=max(mxson,val[to]);
		sum+=val[to];
	}
//	chu("deal:%d\n",x);
	if(!son)return;
	if(son==1)
	{
		if(sum!=val[x])
		{
			chu("NO");exit(0);
		}
		if(x==1)
		{
			chu("YES");exit(0);
		}
		//否则一定合法,结束
		return;
	}
	if(sum-mxson<sum-val[x])
	{
		chu("NO");exit(0);
	}
	int tmp=val[x];
	val[x]-=(sum-val[x]);
	if(val[x]>tmp)
	{
		chu("NO");exit(0);
	}
	//chu("val[%d]:%d\n",x,val[x]);
	if(val[x]<0)
	{
		chu("NO");exit(0);
	}
}
int du[N];
int main()
{
  // freopen("1.in","r",stdin);
   //freopen("a.out","w",stdout);
   n=re();
   _f(i,1,n)val[i]=re();
   _f(i,1,n-1)
   {
	   int x=re(),y=re();
	   Add(x,y);du[x]++;du[y]++;
   }
   //1是根吧
   int ol=0;
   _f(i,1,n)if(du[i]==1)ol++;
   if(ol==n-1)
   {
	   ll mxval=0;ll sumval=0;
	   _f(i,2,n)mxval=max(val[i],mxval),sumval+=val[i];
	   if(sumval==val[1]*2 && mxval<=sumval-mxval)
	   chu("YES");
	   else chu("NO");
	   return 0;
   }
   dfs(1,0);
   if(val[1]!=0)
   {
	   chu("NO");
   }
   else chu("YES");
    return 0;
}
/*
5
4 3 1 1 4
1 2
2 3
2 4
1 5

*/

T4:长度为n的序列ai,值域是连续的1~n,给出m个询问,m个询问每次求[l,r]区间的连续数出现最长长度。(n,m<=1e5)

可以用权值线段树维护区间上连续的一段值域最长长度,在左右子树合并时,维护mmx,lmx,rmx(序列最长,靠左最长,靠右最长)。\(O(m*logn*[移动长度])\)

点击查看代码


#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=1e5+100;
int a[N],n,m,lmx[N<<2],rmx[N<<2],mmx[N<<2],len[N<<2];
struct Q
{
	int l,r,id,code;
	bool operator<(const Q&U)const
	{
		if(code==U.code)
		{
			if(code&1)return r<U.r;
			return r>U.r;
		}
		return code<U.code;
	}
}q[N];
int ans[N],belong[N];
#define lson (rt<<1)
#define rson (rt<<1|1)
inline void Build(int rt,int l,int r)
{
	len[rt]=r-l+1;
	if(l==r)return;
	int mid=(l+r)>>1;
	Build(lson,l,mid);Build(rson,mid+1,r);
}
inline void Pushup(int rt)
{
	if(lmx[lson]==len[lson])
	lmx[rt]=len[lson]+lmx[rson];
	else 
	lmx[rt]=lmx[lson];

	if(rmx[rson]==len[rson])
	rmx[rt]=len[rson]+rmx[lson];
	else 
	rmx[rt]=rmx[rson];

	mmx[rt]=max(max(mmx[lson],mmx[rson]),rmx[lson]+lmx[rson]);
}
inline void Insert(int rt,int l,int r,int pos)
{
	if(l==r)
	{
		mmx[rt]=lmx[rt]=rmx[rt]=1;return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)Insert(lson,l,mid,pos);
	if(pos>mid)Insert(rson,mid+1,r,pos);
	Pushup(rt);
}
inline void Erase(int rt,int l,int r,int pos)
{
	if(l==r)
	{
		mmx[rt]=lmx[rt]=rmx[rt]=0;return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)Erase(lson,l,mid,pos);
	if(pos>mid)Erase(rson,mid+1,r,pos);
	Pushup(rt);
}
int main()
{
  // freopen("1.in","r",stdin);
   //freopen("a.out","w",stdout);
   n=re(),m=re();
   _f(i,1,n)a[i]=re();
   int base=sqrt(n);
   _f(i,1,n)belong[i]=i/base;
   _f(i,1,m)
   {
	   q[i].l=re(),q[i].r=re();q[i].id=i;
	   q[i].code=belong[q[i].l];
   }
   Build(1,1,n);
   sort(q+1,q+1+m);
   int l=q[1].l,r=q[1].r;
   _f(i,l,r)Insert(1,1,n,a[i]);
   ans[q[1].id]=mmx[1];//线段树维护的是值域
   _f(i,2,m)
   {
	   while(r<q[i].r)Insert(1,1,n,a[++r]);
	   while(l>q[i].l)Insert(1,1,n,a[--l]);
	   while(r>q[i].r)Erase(1,1,n,a[r--]);
	   while(l<q[i].l)Erase(1,1,n,a[l++]);
	   ans[q[i].id]=mmx[1];
   }
   _f(i,1,m)
   chu("%d\n",ans[i]);
    return 0;
}
/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7
*/

正解:回滚莫队。
询问按照左端点块排序,然后按照右端点升序。
【1】记录当前询问r,如果左端点所属于的块不一样了,全部清空,r更新成当前块的右端点;否则保留r继续。
【2】对于query_r的拓展,因为r升序,所以r不需要撤销,只需要拓展。
【3】在整个本块区间内处理剩余部分,如果r在块内,就是整个部分,否则就是块内剩余。
【4】统计答案,撤销【3】中的操作,用队列实现回滚。
针对本题目,
方法一:维护lb,rb表示i值域向左向右连续段,每次update只需要更新端点值就可以。
\(O(n根号n)\)
为什么更快?
首先【3】【4】可以保证根号长度,对于【1】操作,只会最多每根号n次进行一次,跳跃长度是根号n的,所以加起来只会进行n根号n次【2】操作也是莫队正常移动的次数,还只需要考虑右端点。所以是对的。

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=1e5+100;
int lb[N],rb[N],belong[N],n,m,a[N],ans[N];
struct Query
{
	int l,r,code,id;
	inline bool operator<(const Query&U)const
	{
		if(code==U.code)return r<U.r;
		return code<U.code;
	}
}q[N];
struct Tiger
{
	int pos,val,bl;
	Tiger(){}
	Tiger(int _pos,int _val,int _bl)
	{
		pos=_pos;val=_val;bl=_bl;
	}
}st[N];
int top;
int main()
{
  // freopen("1.in","r",stdin);
   //freopen("a.out","w",stdout);
   n=re();m=re();
   _f(i,1,n)a[i]=re();
   int base=sqrt(n);
   _f(i,1,n)belong[i]=ceill((double)i/base);//chu("belong[%d]:%d\n",i,belong[i]);
   _f(i,1,m)
   {
	   q[i].l=re(),q[i].r=re();q[i].id=i;
	   q[i].code=belong[q[i].l];
   }
   sort(q+1,q+1+m);
   //chu("out\n");
   int r=0;int sum=0;
	_f(i,1,m)
	{
		//chu("now:%d\n",i);
		if(q[i].code!=q[i-1].code)//左端点离开
		{
			_f(j,1,n)lb[j]=rb[j]=0;//全部清空
			sum=0;
			r=q[i].code*base;//右边界
		}
	//	chu("r:%d  sum:%d\n",r,sum);
		while(r<q[i].r)//把r+1加进去
		{
			r++;
			lb[a[r]]=lb[a[r]-1]+1;
			rb[a[r]]=rb[a[r]+1]+1;
			int tmp=lb[a[r]]+rb[a[r]]-1;
			rb[a[r]-lb[a[r]]+1]=tmp;
			lb[a[r]+rb[a[r]]-1]=tmp;
			sum=max(sum,tmp);
		}//多的部分
		int res=sum;top=0;
		//chu("sum:%d\n",sum);
		int bj=min(q[i].r,q[i].code*base);
		for(rint l=q[i].l;l<=bj;++l)
		{
			lb[a[l]]=lb[a[l]-1]+1;
			rb[a[l]]=rb[a[l]+1]+1;
			int tmp=lb[a[l]]+rb[a[l]]-1;
			st[++top]=Tiger(a[l]-lb[a[l]]+1,rb[a[l]-lb[a[l]]+1],1);
			st[++top]=Tiger(a[l]+rb[a[l]]-1,lb[a[l]+rb[a[l]]-1],2);
			rb[a[l]-lb[a[l]]+1]=tmp;
			lb[a[l]+rb[a[l]]-1]=tmp;
			res=max(res,tmp);
		}
	//	chu("res:%d\n",res);
		//回滚
		f_(op,top,1)//倒着,因为操作是正,前面必须可以覆盖后面
		{
			if(st[op].bl==1)
			rb[st[op].pos]=st[op].val;
			else 
			lb[st[op].pos]=st[op].val;
		}
		_f(op,q[i].l,bj)
		lb[a[op]]=rb[a[op]]=0;
		ans[q[i].id]=res;
	}
	_f(i,1,m)chu("%d\n",ans[i]);
    return 0;
}
/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7
*/

方法二:并查集。好像多一个log?复杂度不对......

点击查看代码






#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=1e5+100;
int belong[N],n,m,a[N],ans[N],siz[N],vis[N],fa[N];
struct Query
{
	int l,r,code,id;
	inline bool operator<(const Query&U)const
	{
		if(code==U.code)return r<U.r;
		return code<U.code;
	}
}q[N];
int st[N],top;
inline int getfa(int x)
{
	if(x==fa[x])return x;
	return getfa(fa[x]);
}
inline int Merge(int x,int y)
{
	//chu("merge:%d--%d\n",x,y);
	int fx=getfa(x),fy=getfa(y);
	if(fx==fy)return -1;
	if(siz[fx]>siz[fy])swap(fx,fy);
	fa[fx]=fy;
	siz[fy]+=siz[fx];
	return fx;//返回被修改的点
}
int main()
{
  // freopen("1.in","r",stdin);
   //freopen("a.out","w",stdout);
   n=re();m=re();
   _f(i,1,n)a[i]=re();
   int base=sqrt(n);
   _f(i,1,n)belong[i]=ceill((double)i/base);//chu("belong[%d]:%d\n",i,belong[i]);
   _f(i,1,m)
   {
	   q[i].l=re(),q[i].r=re();q[i].id=i;
	   q[i].code=belong[q[i].l];
   }
   sort(q+1,q+1+m);
	int r=0,sum=1;
	_f(i,1,m)
	{
		if(q[i].code!=q[i-1].code)
		{
		//	chu("in?\n");
			_f(j,1,n)fa[j]=j,vis[j]=0,siz[j]=1;
			r=q[i].code*base;//直接是右端点
			sum=1;
		}
		//chu("now get:%d--%d\n",q[i].l,q[i].r);
		while(r<q[i].r)
		{
			++r;
			if(vis[a[r]-1])
			Merge(a[r]-1,a[r]);
			if(vis[a[r]+1])
			Merge(a[r],a[r]+1);//
			vis[a[r]]=1;
			int tmp=siz[getfa(a[r])];
			sum=max(sum,tmp);
		}
		//chu("nowsum:%d\n",sum);
		int ls=sum;top=0;
		for(int l=q[i].l;l<=min(q[i].r,q[i].code*base);++l)
		{
			if(vis[a[l]-1])
			{
				int will=Merge(a[l]-1,a[l]);
				if(will!=-1)st[++top]=will;
			}
			if(vis[a[l]+1])
			{
				int will=Merge(a[l],a[l]+1);
				if(will!=-1)st[++top]=will;
			}
			vis[a[l]]=1;
			ls=max(ls,siz[getfa(a[l])]);
		}
		//回退
		f_(op,top,1)
		{
			int tp=st[op];
			int fath=getfa(tp);
			siz[fath]-=siz[tp];
			fa[tp]=tp;
		}
	//	top=0;
		_f(op,q[i].l,min(q[i].r,q[i].code*base))
		{
			vis[a[op]]=0;
		}
		ans[q[i].id]=ls;
	}
   _f(i,1,m)chu("%d\n",ans[i]);
    return 0;
}
/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7


void del()
{
    while(sta[0]>top)
    {
        size[find(sta[sta[0]])]-=size[sta[sta[0]]];
        fa[sta[sta[0]]]=sta[sta[0]];
        sta[0]--;
    }
}
*/
posted on 2022-09-24 16:28  HZOI-曹蓉  阅读(74)  评论(0)    收藏  举报