NOI 2023 Singapore Final 题解

发布时间 2023-06-17 16:40:58作者: LFCode

去年写过新加坡 NOI 2022 的题解,当时感觉那套题还挺有趣的……但数据结构题怎么这么多。

脱离文化课苦海之后发现 2023 的题也有了,所以就有了这篇题解。

和去年一样 E 只翻译了题面而没有题解,因为 E 是个交互,最近暂时没有练习交互题的打算。而且我估计我也不会做。

题面

注:所有题目空间限制都是 1G

A

\(n\) 门课程和 \(k\) 个知识点,如果你想要学习第 \(i\) 门课程,那么你必须在学习前对每个知识点的熟练度分别达到 \(u_{i,1},u_{i,2},\cdots,u_{i,k}\),学习过后每个知识点的熟练度会分别增加 \(v_{i,1},v_{i,2},\cdots,v_{i,k}\)。最初所有知识点的熟练度都是 \(0\)。你需要计算最多能学习几门课程。

输入格式:第一行两个整数 \(n,k\),后面 \(n\) 行每行 \(k\) 个整数给出 \(u\),后面 \(n\) 行每行 \(k\) 个整数代表 \(v\)

输出格式:一个整数代表最多学习几门课程。

数据范围:\(1\le n\times k\le 10^6,u_{i,j},v_{i,j}\le10^9\)

B

你有 \(n\) 台机器和 \(m\) 个项目,第 \(i\) 个项目会依次使用 \([l_i,r_i]\) 的所有机器。现在 \(m\) 个项目会依次执行下去。

如果某台机器相邻两次被使用之间有超过 \(s\) 台机器被使用,那么该机器需要进行一次调整。

给出 \(q\)\(s\) 的值,对于每个给定的 \(s\) 输出调整次数。

输入格式:第一行三个整数 \(n,m,q\),后面 \(m\) 行每行两个整数给出项目信息,最后一行 \(q\) 个整数给出所有询问的 \(s\)

输出格式:一行 \(q\) 个整数,对每个询问给出答案。

数据范围:\(n,m,q\le2\times10^5,s\leq10^{12}\)

C

给订一张 \(n\) 个点 \(m\) 条边的无向图。每个顶点都有一个设定的高度 \(h_i\)(保证 \(h_1=h_n=0\))。你要从 \(1\) 前往 \(n\),每个时刻可以沿着一条边行走或停在原地,同时可以让所处海拔 \(+1,-1\) 或不变。要求位于每个顶点时所处的海拔都不可以低于相应的 \(h\),且最后必须回到 \(0\) 海拔,求最少需要的时间。

输入格式:第一行两个整数代表 \(n,m\),后面一行 \(n\) 个整数给出 \(h\),后面 \(m\) 行给出边。

输出格式:一个整数代表答案。

数据范围:\(n\le2\times10^5,m\le4\times10^5,h_i\leq10^8\),图中无自环。

D

\(n\) 个整点,编号为 \(1\)\(n\)。现在你有 \(m\) 个区间,第 \(i\) 个区间覆盖了 \([l_i,r_i]\) 的整点。有 \(q\) 组询问,每个询问给出一个区间 \([x,y]\),你需要回答是否能够从你有的区间当中选出一些,使得它们的并可以覆盖 \([x,y]\) 内所有整点,且不会覆盖这个区间外的任何整点。

输入格式:第一行三个整数 \(n,m,q\),后面 \(m\) 行给出区间信息,后面 \(q\) 行给出询问信息。

输出格式:\(q\) 行,如果能做到输出 \(\text{YES}\),否则输出 \(\text{NO}\)

数据范围:\(n,m,q\le5\times10^5\),区间有可能只包含一个整点。

E

现在有 \(300\) 个细菌,这些细菌可以分成三种:有毒、普通、抗药,其中有毒菌株不超过 \(30\) 只。注意你并不知道有毒菌株的确切数目。

你可以自由选择一些细菌放进一个机器中进行竞争,经过一轮竞争后,有毒菌一定死亡,普通菌只在有毒菌最初存在的情况下死亡,抗药菌一定不会死亡。机器会告诉你竞争后有多少细菌没有死亡。

你最多可以使用机器 \(150\) 次,并确定出每个细菌的种类。

交互格式略。

题解

A

对于每个知识点,将所有课程按所需该知识点熟练度从小到大排序,用一个指针维护当前对于该知识点有多少门课程已经可以满足限制了。

每当有一个课程可以学习的时候,更新每个知识点对应的指针,以及每个课程满足限制的知识点个数。同时看一看是否有新的课程满足了全部限制。

B

按照套路用一个 set 维护使用机器的连续段,当有一个连续段要被删除或部分删除的时候,在线段树上做区间加。线段树的下标是修改间隔时间长度,数值是次数。

对于每个询问,在线段树上区间求和就可以了。

C

  • 性质 1:一定存在一种用时最短的方案满足其分为三个阶段:先上升,再持平,最后下降。
    • 证明:比较显然。
  • 性质 2:一定存在一种用时最短的方案,满足其持平时长为 \(0\)\(1\)
    • 证明:如果持平时长 \(>1\),那么我们把第一、第三阶段时长全部加长,总时长不变。

于是以 \(1\) 号、\(n\) 号顶点分别作为起点跑最短路,并对 \(\text{Dij}\) 稍作修改即可:\(d_v=\max\{d_u+1,h_v\}\)。计算答案的时候看一看是否在经过某条边的时候持平。

D

容易想到一个两枚 \(\log\) 的分治:把询问放到分治树 \(\text{LCA}\) 的位置,分治过程中维护以某个点为左 / 右端点所能延伸到的最远位置,那么回答询问时,我们只需要讨论跨过终点连接两边用到了一个还是两个区间。这是一个二维数点。回答完当前分治区间上的所有询问后,更新当前区间内每个点的最远延伸位置。这个可以转化为求区间最值。很遗憾,会被卡,只能得 \(60\) 分。下面是几种单 \(\log\) 做法。

思路 1 考虑改进上面的分治。二维数点离线进行,更新最远延伸位置时抛弃数据结构改用线性 \(\text{RMQ}\)。不好写,但确实是单 \(\log\)

思路 2 另一种分治。对每个分治区间,对左半边的点以这个位置为左端点能够覆盖到的 \(\ge mid\) 的右端点中最靠左的位置(设为 \(mnr\))。同理对右半边的店维护一个 \(mxl\)。这可以用单调栈来求。询问 \([x,y]\) 可行当且仅当 \(mnr_x\le y\)\(mxl_y\ge x\)。细节见代码。

思路 3 扫描线。扫描的过程中维护经过每个点所能到达的左端点最大值。回答询问时查询区间 \(\min\)。需要用到 \(\text{SegBeats}\),这个做法是杜爷教的。

E

to be completed.

代码

哈哈,去年我代码前必加的那句话现在无了。

A

#include<cstdio>
#include<algorithm>
#include<vector>
#define v(x,y,z) x[((y)-1)*m+z]
using std::vector;
const int N=1000086;
int n,m,qh,qt,q[N],p[N],a[N],b[N],cnt[N],nc[N];
vector<int>vec[N];
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			v(a,i,j)=read();p[i]+=(v(a,i,j)==0);
			if(v(a,i,j)==0)cnt[j]++;vec[j].push_back(i);
		}
		if(p[i]==m)q[++qt]=i;
	}
	for(int j=1;j<=m;j++)
		std::sort(vec[j].begin(),vec[j].end(),[&](int x,int y){return v(a,x,j)<v(a,y,j);});
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			v(b,i,j)=read();
	while(qh<=qt){
		int np=q[qh++];
		for(int i=1;i<=m;i++){
			nc[i]+=v(b,np,i);
			while(cnt[i]<n&&v(a,vec[i][cnt[i]],i)<=nc[i]){
				int x=vec[i][cnt[i]++];
				p[x]++;if(p[x]==m)q[++qt]=x;
			}
		}
	}
	printf("%d",qt);
}

B

#include<cstdio>
#include<set>
using std::set;
const int N=200086,M=30000086;
int tot=1,n,m,q,ql[N],qr[N];
long long tme,tm;
struct node{
	long long s;int lc,rc;node(){s=lc=rc=0;}
	int ls(){return lc?lc:lc=++tot;}
	int rs(){return rc?rc:rc=++tot;}
}t[M];
struct iv{
	int l,r;long long t;
	iv(int ll,int rr,long long tt){l=ll;r=rr;t=tt;}
	bool operator <(const iv b)const{return l==b.l?r<b.r:l<b.l;}
};
set<iv>s;
long long read(){
	char ch=getchar();long long nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
bool change(int k,long long l,long long r,long long x,long long v){
	t[k].s+=v;if(l==r)return true;long long mid=(l+r)>>1;
	if(x<=mid)change(t[k].ls(),l,mid,x,v);else change(t[k].rs(),mid+1,r,x,v);
	return true;
}
long long ask(int k,long long l,long long r,long long x,long long y){
	if(l>=x&&r<=y)return t[k].s;long long mid=(l+r)>>1,ret=0;
	if(x<=mid&&t[k].lc)ret+=ask(t[k].lc,l,mid,x,y);
	if(mid<y&&t[k].rc)ret+=ask(t[k].rc,mid+1,r,x,y);
	return ret;
}
signed main(){
	n=read();m=read();q=read();
	for(int i=1;i<=m;i++){ql[i]=read();qr[i]=read();tm+=qr[i]-ql[i]+1;}
	for(int i=1;i<=m;i++){
		int l=ql[i],r=qr[i];long long nt=tme+1;
		set<iv>::iterator li=s.lower_bound(iv(l,0,0));
		set<iv>::iterator ri=s.lower_bound(iv(r+1,0,0));
		if(li!=s.begin()){
			li--;
			if(li->r>=r){
				long long lt=l-li->l+li->t;
				change(1,1,tm,nt-lt,r-l+1);
				s.insert(iv(li->l,l-1,li->t));
				if(li->r>r)s.insert(iv(r+1,li->r,lt+r-l+1));
				s.erase(li);s.insert(iv(l,r,nt));tme+=r-l+1;continue;
			}
			li++;
		}
		for(set<iv>::iterator it=li;it!=ri;){
			if(it->r>r)break;change(1,1,tm,it->l-l+nt-it->t,it->r-it->l+1);
			set<iv>::iterator lst=it;it++;s.erase(lst);
		}
		li=s.lower_bound(iv(l,0,0));
		if(li!=s.begin()){
			li--;
			if(li->r>=l){
				change(1,1,tm,nt-(li->t+l-li->l),li->r-l+1);
				s.insert(iv(li->l,l-1,li->t));s.erase(li);
			}
		}
		ri=s.lower_bound(iv(r+1,0,0));
		if(ri!=s.begin()){
			ri--;
			if(ri->l<=r&&ri->r>r){
				change(1,1,tm,(nt+ri->l-l)-ri->t,r-ri->l+1);
				s.insert(iv(r+1,ri->r,ri->t+r+1-ri->l));s.erase(ri);
			}
		}
		s.insert(iv(l,r,nt));
		tme+=r-l+1;
	}
	for(int i=1;i<=q;i++)printf("%lld ",ask(1,1,tm,read()+1,tm));
}

C

#include<cstdio>
#include<queue>
using namespace std;
const int N=200086,M=400086,INF=2e9;
int n,m,tot=1,a[N],d0[N],d1[N],eu[M],ev[M],h[N];
bool vis[N];
struct edge{int v,nxt;}e[M<<1];
struct node{
	int no,dis;
	node(int n,int d){no=n;dis=d;}
	bool operator <(const node b)const{return dis>b.dis;}
};
priority_queue<node>q;
int Max(int a,int b){return a>b?a:b;}
int Min(int a,int b){return a<b?a:b;}
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
bool add(int u,int v){e[++tot].v=v;e[tot].nxt=h[u];h[u]=tot;return true;}
bool dij(int s,int dis[]){
	for(int i=1;i<=n;i++)dis[i]=INF;
	while(!q.empty())q.pop();q.push(node(s,dis[s]=0));
	while(!q.empty()){
		node np=q.top();q.pop();if(np.dis!=dis[np.no])continue;
		for(int i=h[np.no];i;i=e[i].nxt){
			int nd=Max(dis[np.no]+1,a[e[i].v]);
			if(nd<dis[e[i].v])q.push(node(e[i].v,dis[e[i].v]=nd));
		}
	}
	return true;
}
int main(){
	n=read();m=read();for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=m;i++){
		eu[i]=read();ev[i]=read();
		add(eu[i],ev[i]);add(ev[i],eu[i]);
	}
	dij(1,d0);dij(n,d1);int ans=INF;
	for(int i=2;i<n;i++)ans=Min(ans,Max(d0[i],d1[i])*2);
	for(int i=1;i<=m;i++){
		ans=Min(ans,Max(d0[eu[i]],d1[ev[i]])*2+1);
		ans=Min(ans,Max(d0[ev[i]],d1[eu[i]])*2+1);
	}
	printf("%d",ans);
}

D(思路 2)

#include<cstdio>
#include<vector>
using std::vector;
const int N=500086;
int n,m,q,st[N],ql[N],qr[N],mxl[N],mnr[N],ans[N];
bool p[N];
vector<int>vl[N],vr[N],vq[N<<2];
int Max(int a,int b){return a>b?a:b;}
int Min(int a,int b){return a<b?a:b;}
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
bool pushq(int k,int l,int r,int x){
	int mid=(l+r)>>1;if(l==r||(ql[x]<=mid&&qr[x]>mid)){vq[k].push_back(x);return true;}
	return qr[x]<=mid?pushq(k<<1,l,mid,x):pushq(k<<1|1,mid+1,r,x);
}
bool Div(int k,int l,int r){
	if(l==r){
		if(p[l])
			for(vector<int>::iterator it=vq[k].begin();it!=vq[k].end();it++)
				ans[*it]=true;
		return true;
	}
	int mid=(l+r)>>1;Div(k<<1,l,mid);Div(k<<1|1,mid+1,r);
	for(int i=mid,tp=0;i>=l;i--){
		mnr[i]=r+1;int mr=0;
		for(vector<int>::iterator it=vl[i].begin();it!=vl[i].end();it++){
			if(*it>r)continue;
			if(*it<mid)mr=Max(mr,*it);else mnr[i]=Min(mnr[i],*it);
		}
		while(tp&&(st[tp]<=mr+1||mnr[st[tp]]>=mnr[i]))mnr[i]=Min(mnr[i],mnr[st[tp--]]);
		st[++tp]=i;
	}
	for(int i=mid+1,tp=0;i<=r;i++){
		mxl[i]=0;int ml=r+1;
		for(vector<int>::iterator it=vr[i].begin();it!=vr[i].end();it++){
			if(*it<l)continue;
			if(*it>mid+1)ml=Min(ml,*it);else mxl[i]=Max(mxl[i],*it);
		}
		while(tp&&(st[tp]>=ml-1||mxl[st[tp]]<=mxl[i]))mxl[i]=Max(mxl[i],mxl[st[tp--]]);
		st[++tp]=i;
	}
	for(vector<int>::iterator it=vq[k].begin();it!=vq[k].end();it++)
		ans[*it]=mnr[ql[*it]]<=qr[*it]&&mxl[qr[*it]]>=ql[*it];
	return true;
}
int main(){
	n=read();m=read();q=read();
	for(int i=1;i<=m;i++){
		int l=read();int r=read();if(l==r)p[l]=true;
		vl[l].push_back(r);vr[r].push_back(l);
	}
	for(int i=1;i<=q;i++){ql[i]=read();qr[i]=read();pushq(1,1,n,i);}
	Div(1,1,n);for(int i=1;i<=q;i++)if(ans[i])puts("YES");else puts("NO");
}

E

to be completed.