View on GitHub

Wenchong Huang

旧博客,文章搬迁完后删除

广州二中NOI模拟赛(三十五)

返回首页 返回模拟赛列表

tip:如果题目图片看不清楚,可以百度“在线图片转文字”转换一下

A.异或

看到若干个数异或最大值,很容易想到了线性基,但由于有个区间限制,又不太好搞,毕竟线性基这玩意儿不具有可差分性,那就非常糟糕

于是想到一个馊主意:线段树维护线性基

具体地,线段树的每个区间,保存这个区间所有数的线性基。建树时向上合并线性基;查询时将相应区间的线性基合并起来做询问

仔细分析一下,嗯……线段树一个log,线性基合并2个log(虽然不满),所以总的就是3个log,然后数据范围10^6……

现场实测这只能拿26分,当然如果用了题目里给你的超级IO优化,然后子任务8加个特判,可以卡到31分

26分代码如下

#include<bits/stdc++.h>
using namespace std;

int read()
{
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
}

void put(int x)
{
	if(x/10) put(x/10);
	putchar(x%10+'0');
}

int M;
const int N=1000010;
struct Basis
{
	int b[35],size;
	Basis(){memset(b,0,sizeof(b));size=0;}
	void insert(int x)
	{
		if(size==30) return;
		for(int i=M;i>=0;i--)
			if(x&(1<<i))
			{
				if(!b[i]){b[i]=x;size++;break;}
				else x^=b[i];
			}
	}
	int query(int x)
	{
		for(int i=M;i>=0;i--)
			if((x^b[i])>x) x^=b[i];
		return x;
	}
	void merge(Basis a)
	{
		if(size==30) return;
		for(int i=M;i>=0;i--)
			if(a.b[i]) insert(a.b[i]);
	}
};

Basis val[N<<2];
int a[N],n;

void build(int o,int l,int r)
{
	if(l==r){val[o].insert(a[l]);return;}
	int mid=(l+r)/2;
	build(o*2,l,mid);
	build(o*2+1,mid+1,r);
	val[o]=val[o*2];
	val[o].merge(val[o*2+1]);
}

Basis query(int o,int l,int r,int nl,int nr,int d)
{
	if(l==nl&&r==nr) return val[o];
	int mid=(l+r)/2;
	if(nr<=mid) return query(o*2,l,mid,nl,nr,d);
	else if(nl>mid) return query(o*2+1,mid+1,r,nl,nr,d);
	else
	{
		Basis ans=query(o*2,l,mid,nl,mid,d);
		ans.merge(query(o*2+1,mid+1,r,mid+1,nr,d));
		return ans;
	}
}

int main()
{
	n=read();
	bool flag=true;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(a[i]>=16) flag=false;
	}
	M=flag?4:30;
	build(1,1,n);
	int q=read(),l,r,d;
	while(q--)
	{
		l=read();r=read();d=read();
		Basis ans=query(1,1,n,l,r,d);
		put(ans.query(d));putchar('\n');
	}
	return 0;
}

其实本题没有那么复杂

正解是离线询问,并且只维护一个线性基

把询问按照右端点升序排序,一边维护线性基,一边处理询问

线性基的维护方式需要改变一下,需要保存这一位线性基来自原序列的哪一个位置

每处理到第p个位置,就完成右端点为p的询问,询问时也要做一些改变。普通的线性基询问时只要异或了更大,就异或上去,但现在还需要判定:这一位的线性基是否来自l后面的位置(l指询问左端点)

因此我们不难想到一个贪心策略:维护线性基时,如果后面位置的数可行,就尽量让线性基来自更后面的位置,这一点在下面代码的insert函数中有体现

时间复杂度O((n+q)log a)

再吐槽一句:题目给的IO优化好牛逼啊!普通的手写快读1.4s读完的东西,它0.05s就读完了!

#include<bits/stdc++.h>
using namespace std;

namespace IO
{
	const int S=1<<16;
	//Input Correlation
	char buf[S],*H,*T;
	inline char Get()
	{
		if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
		if(H==T) return -1;return *H++;
	}
	inline int read()
	{
		int x=0;char c=Get();
		while(!isdigit(c)) c=Get();
		while(isdigit(c)) x=x*10+c-'0',c=Get();
		return x;
	}
	//Output Correlation
	char obuf[S],*oS=obuf,*oT=oS+S-1,c,qu[55];int qr;
	inline void flush()
	{
		fwrite(obuf,1,oS-obuf,stdout);
		oS=obuf;
	}
	inline void putc(char x)
	{
		*oS++ =x;
		if(oS==oT) flush();
	}
	template <class I>
	inline void print(I &x)
	{
		if(!x) putc('0');
		while(x) qu[++ qr]=x%10+'0',x /= 10;
		while(qr) putc(qu[qr--]);
	}
}

using namespace IO;
const int N=1000010;
int a[N],n,q;
int base[35];
struct Qry{int l,r,d,id;} qry[N];
int ans[N];

bool operator < (const Qry &a,const Qry &b){return a.r<b.r||a.r==b.r&&a.l<b.l;}

inline void insert(int p)
{
	for(int i=30;i>=0;i--)
		if(a[p]&(1<<i))
		{
			if(!base[i]){base[i]=p;break;}
			if(base[i]<p) swap(base[i],p);
			a[p]^=a[base[i]];
		}
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	q=read();
	for(int i=1;i<=q;i++)
	{
		qry[i].l=read();
		qry[i].r=read();
		qry[i].d=read();
		qry[i].id=i;
	}
	sort(qry+1,qry+1+q);
	int now=1;
	for(int i=1;i<=n;i++)
	{
		insert(i);
		while(qry[now].r==i)
		{
			int l=qry[now].l,d=qry[now].d;
			for(int j=30;j>=0;j--)
				if(l<=base[j]&&(d^a[base[j]])>d)
					d^=a[base[j]];
			ans[qry[now].id]=d;
			now++;
		}
	}
	for(int i=1;i<=q;i++) print(ans[i]),putc('\n');
	flush();
	return 0;
}

B.公交旅行

非常简单的一道题,放在NOIP中也不为过。但是我考场上写错了一个细节!然后样例过了!然后爆零!!!

直接建图不太现实,那就考虑每条边保存两个信息:该路线的站点总数a,以及这条边的起点在路线中的编号b(从0开始)

那么跑最短路,每到一条边,将当前时间对这条边的a取个模,再用b去减一下,得到等待时间,再加上1,就是到达边指向的点的时间

时间复杂度O(SPFA)

#include<bits/stdc++.h>
#define INF 0x7fffffff
using namespace std;

int read()
{
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
}

void put(int x)
{
	if(x<0) putchar('-'),x=-x;
	if(x/10) put(x/10);
	putchar(x%10+'0');
}

struct Edge{int to,a,b,next;} e[1000010];
int h[100010],sum=0,n,m;
int stop[200010];
bool vis[100010];
int dis[100010];

inline void add_edge(int u,int v,int a,int b)
{
	sum++;
	e[sum].to=v;
	e[sum].a=a;
	e[sum].b=b;
	e[sum].next=h[u];
	h[u]=sum;
}

void SPFA()
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) dis[i]=INF;
	queue<int> q;
	q.push(1);vis[1]=1;dis[1]=0;
	while(!q.empty())
	{
		int u=q.front();
		for(int tmp=h[u];tmp;tmp=e[tmp].next)
		{
			int v=e[tmp].to,base=dis[u]%e[tmp].a;
			int dist=(e[tmp].b-base+e[tmp].a)%e[tmp].a+1;
			if(dis[u]+dist<dis[v])
			{
				dis[v]=dis[u]+dist;
				if(!vis[v]) vis[v]=1,q.push(v);
			}
		}
		vis[u]=0;q.pop();
	}
}

int main()
{
	n=read();m=read();
	int t,x;
	for(int i=1;i<=m;i++)
	{
		t=read();
		for(int j=0;j<t;j++)
			stop[j]=read();
		stop[t]=stop[0];
		for(int j=0;j<t;j++)
			add_edge(stop[j],stop[j+1],t,j);
	}
	SPFA();
	for(int i=2;i<=n;i++)
		put(dis[i]==INF?-1:dis[i]),putchar(' ');
	return 0;
}

C.硬币游戏

这题的暴力好难打啊,场上非常懵逼,然后就没拿分

有一个神奇的问题转化:将每个信封看做一条边,只要选定的边不构成环,就是合法的

问题变成了:给定r对边,每对边中选一条,使之成为一个森林,最大化选的边数

那么解决方法也非常暴力,就是贪心加边,如果这条边不能加,就考虑这对边中的另一条能不能加。

考虑这条边能不能加时,能直接加当然是最好,不能直接加的话,就考虑把已经选定了的一些边换成它们那对边中的另一条,然后判断换了之后这条边能不能直接加上去,这个过程用BFS实现

总时间复杂度O(r³)

#include<bits/stdc++.h>
using namespace std;

const int S=1<<16;
char buf[S],*H,*T;
inline char Get()
{
	if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
	if(H==T) return -1;return *H++;
}
inline int read()
{
	int x=0;char c=Get();
	while(!isdigit(c)) c=Get();
	while(isdigit(c)) x=x*10+c-'0',c=Get();
	return x;
}

const int N=10010;
struct Edge{int to,next,id;} eg[N*100];
int e[310][3][3],chs[N];
int h[N],sum,nc,np=0;
bool vis[N];
int col[N],fa[N],pt[N];
int pre[N],dep[N],cc[N];
queue<int> q;

void add_edge(int u,int v,int id)
{
	eg[++sum].to=v;
	eg[sum].id=id;
	eg[sum].next=h[u];
	h[u]=sum;
}

void dfs(int u,int la)
{
	fa[u]=la;col[u]=nc;
	for(int tmp=h[u];~tmp;tmp=eg[tmp].next)
	{
		int v=eg[tmp].to;
		if(v==la) continue;
		dep[v]=dep[u]+1;
		pre[v]=eg[tmp].id;
		dfs(v,u);
	}
}

void gao1(int id,int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	while(dep[u]>dep[v])
	{
		int ed=pre[u];
		if(!vis[ed]) vis[ed]=1,cc[ed]=id,q.push(ed);
		u=fa[u];
	}
	while(u!=v)
	{
		int ed=pre[u];u=fa[u];
		if(!vis[ed]) vis[ed]=1,cc[ed]=id,q.push(ed);
		ed=pre[v];v=fa[v];
		if(!vis[ed]) vis[ed]=1,cc[ed]=id,q.push(ed);
	}
}

void gao2(int x)
{
	if(!x) return;
	chs[x]^=1;
	gao2(cc[x]);
}

bool add(int p,int c)
{
	int u=e[p][c][0],v=e[p][c][1];
	sum=-1;nc=0;
	for(int i=1;i<=np;i++) col[pt[i]]=0,h[pt[i]]=-1;
	for(int i=1;i<p;i++)
		if(chs[i]!=-1)
			add_edge(e[i][chs[i]][0],e[i][chs[i]][1],i),
			add_edge(e[i][chs[i]][1],e[i][chs[i]][0],i);
	for(int i=1;i<p;i++) vis[i]=0;
	for(int i=1;i<=np;i++)
		if(!col[pt[i]])
		{
			nc++;
			dep[pt[i]]=0;
			dfs(pt[i],0);
		}
	if(col[u]!=col[v]) return true;
	while(!q.empty()) q.pop();
	gao1(0,u,v);
	while(!q.empty())
	{
		int ed=q.front();q.pop();
		int u=e[ed][chs[ed]^1][0],v=e[ed][chs[ed]^1][1];
		if(col[u]!=col[v]){gao2(ed);return true;}
		gao1(ed,u,v);
	}
	return false;
}

int main()
{
	for(int T=read();T;T--)
	{
		int r=read();np=0;
		for(int i=1;i<=r;i++)
			pt[++np]=e[i][0][0]=read()+1,
			pt[++np]=e[i][0][1]=read()+1,
			pt[++np]=e[i][1][0]=read()+1,
			pt[++np]=e[i][1][1]=read()+1;
		int ans=r;
		for(int i=1;i<=r;i++)
			if(add(i,0)) chs[i]=0;
			else if(add(i,1)) chs[i]=1;
			else ans--,chs[i]=-1;
		printf("%d\n",ans<<1);
	}
	return 0;
}