省选测试12

总结

省选测试12

省选测试12

仍然是三题打暴力,和前面的人分差巨大

最近有一段时间没有 \(A\) 题了

A. 灯

分析

首先把相同的颜色缩成一段,要求的就是总的加入的点的个数-相邻的不同颜色的点被点亮的个数

前面的部分比较好处理,直接记录一下每一种颜色有多少个点就行了

对于后面的部分,分情况考虑

当某种颜色出现的次数大于 \(\sqrt{n}\) 时

我们记录两个数组分别代表出现次数小于 \(\sqrt{n}\) 的颜色对它的贡献和出现次数大于 \(\sqrt{n}\) 的颜色对它的贡献

前面的那一部分我们可以在更新小块信息的时候顺便维护一下

后面的那一部分我们可以用一个二维数组预处理一下

当某种颜色出现的次数小于 \(\sqrt{n}\) 时

我们直接扫一遍它的 \(vector\) ,判断一下左右两边的元素能否作出贡献就行了

复杂度为 \(n\sqrt{n}\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5,maxm=355;
int n,m,q,a[maxn],nn,blo,jud[maxn],cnt[maxn],lk[maxm][maxm],sta[maxn],tp,ans,rk[maxn],ed[maxn];
std::vector<int> g[maxn];
int main(){
	n=read(),m=read(),q=read();
	for(rg int i=1;i<=n;i++) a[i]=read();
	rg int lst=-1,ans=0;
	for(rg int i=1;i<=n;i++){
		if(a[i]==lst) continue;
		else {
			a[++nn]=a[i];
			lst=a[i];
		}
	}
	n=nn;
	a[n+1]=0;
	blo=sqrt(n);
	for(rg int i=1;i<=n;i++) g[a[i]].push_back(i);
	for(rg int i=1;i<=m;i++) cnt[i]=g[i].size();
	for(rg int i=1;i<=m;i++) if(cnt[i]>=blo) sta[++tp]=i,rk[i]=tp;
	for(rg int i=2;i<=n;i++) lk[rk[a[i]]][rk[a[i-1]]]++,lk[rk[a[i-1]]][rk[a[i]]]++;
	rg int aa,bb;
	for(rg int i=1;i<=q;i++){
		aa=read();
		if(jud[aa]){
			ans-=cnt[aa];
			if(rk[aa]){
				ans+=ed[rk[aa]];
				for(rg int i=1;i<=tp;i++){
					if(jud[sta[i]]) ans+=lk[rk[aa]][i];
				}
			} else {
				for(rg int i=0;i<g[aa].size();i++){
					bb=g[aa][i];
					if(jud[a[bb-1]]) ans++;
					if(jud[a[bb+1]]) ans++;
					ed[rk[a[bb-1]]]--;
					ed[rk[a[bb+1]]]--;
				}
			}
			jud[aa]=0;
		} else {
			ans+=cnt[aa];
			if(rk[aa]){
				ans-=ed[rk[aa]];
				for(rg int i=1;i<=tp;i++){
					if(jud[sta[i]]) ans-=lk[rk[aa]][i];
				}
			} else {
				for(rg int i=0;i<g[aa].size();i++){
					bb=g[aa][i];
					if(jud[a[bb-1]]) ans--;
					if(jud[a[bb+1]]) ans--;
					ed[rk[a[bb-1]]]++;
					ed[rk[a[bb+1]]]++;
				}
			}
			jud[aa]=1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

B. 十字路口

分析

设周期为 \(T\)

对于 \(m<n\) 的数据

我们把每一次的观察看作点,以同一盏红绿灯作为关系连边

设该灯在第 \(i\) 次观察时还有 \(x_i\) 秒变为绿灯,第 \(i\) 次观察的时间为 \(t_i\)

在第 \(j\) 次观察时还有 \(y_i\) 秒 变为绿灯,第 \(j\) 次观察的时间为 \(t_j\)

因为该灯变为绿灯的时刻肯定是相隔若干个周期的,所以 \(x_i+t_i \equiv x_j+t_j (mod\ T)\)

所以如果 \(x_i>x_j\),我们由 \(i\) 向 \(j\) 建一条权值为 \(x_i-x_j\) 的边,强制规定一下边的方向

如果形成了环,那么肯定会有 \(x_1-x_2+x_2-x_3+..-x_1 \equiv 0(mod\ T)\)

所以形成的环的长度一定是 \(T\) 的倍数,我们在原图上求一个最小环就是答案了

对于 \(m \geq n\) 的数据

我们把每一盏灯看做点,以同一次观察作为关系连边

设两个红绿灯红灯变绿灯的时间分别为 \(t_i\) 和 \(t_j\),一个距离这个时刻 \(x_i\) 秒,一个距离这个时刻 \(x_j\) 秒

则有 \(t_i-x_i \equiv t_j-x_j (mod\ T)\)

本质上还是与上面那个式子是一样的

这样我们就可以根据 \(m\) 和 \(n\) 的关系判断选择使用哪一种建边方式了

时间复杂度为 \(nm\sqrt{nm}\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5;
int n,m,ans=0x3f3f3f3f;
std::vector<int> g[maxn],f[maxn];
int main(){
	n=read(),m=read();
	rg int aa;
	for(rg int i=1;i<=m;i++){
		g[i].push_back(0);
		for(rg int j=1;j<=n;j++){
			aa=read(),g[i].push_back(aa);
		}
	}
	if(m<=n){
		for(rg int i=1;i<=m;i++){
			for(rg int j=0;j<=m;j++){
				f[i].push_back(0x3f3f3f3f);
			}
		}
		for(rg int k=1;k<=n;k++){
			for(rg int i=1;i<=m;i++){
				for(rg int j=1;j<=m;j++){
					if(g[i][k] && g[j][k] && g[i][k]>g[j][k]){
						f[i][j]=g[i][k]-g[j][k];
					}
				}
			}
		}
		for(rg int k=1;k<=m;k++){
			for(rg int i=1;i<=m;i++){
				for(rg int j=1;j<=m;j++){
					f[i][j]=std::min(f[i][j],f[i][k]+f[k][j]);
				}
			}
		}
		for(rg int i=1;i<=m;i++) ans=std::min(ans,f[i][i]);
	} else {
		for(rg int i=1;i<=n;i++){
			for(rg int j=0;j<=n;j++){
				f[i].push_back(0x3f3f3f3f);
			}
		}
		for(rg int k=1;k<=m;k++){
			for(rg int i=1;i<=n;i++){
				for(rg int j=1;j<=n;j++){
					if(g[k][i] && g[k][j] && g[k][i]>g[k][j]){
						f[i][j]=g[k][i]-g[k][j];
					}
				}
			}
		}
		for(rg int k=1;k<=n;k++){
			for(rg int i=1;i<=n;i++){
				for(rg int j=1;j<=n;j++){
					f[i][j]=std::min(f[i][j],f[i][k]+f[k][j]);
				}
			}
		}
		for(rg int i=1;i<=n;i++) ans=std::min(ans,f[i][i]);
	}
	if(ans==0x3f3f3f3f) ans=-1;
	printf("%d\n",ans);
	return 0;
}

C. 密室逃脱

分析

\(dp\) 的定义还是很巧妙的

对于一个区间 \([i,j]\) 来说,如果这个区间里的人达到了一定的数量,那么这一群人一定想去区间里的那个地方就去哪一个地方

我们定义这样的区间为一个段

设 \(f[i][j]\) 为 \(i\) 当前所在的段内有 \(j\) 个人,可以放的最多人数

当 \(j<a[i]\) 时,\(i\) 处的人无法打开门,如果在 \(i+1\) 处新加的人小于 \(b[i]\) 那就只能新开一个段了

\(f[i+1][0\sim b[i]-1]=\max(f[i+1][0\sim b[i]-1],f[i][j]+0\sim b[i]-1) j\in[0,a[i]-1]\)

如果新加的人恰好等于 \(b[i]\),那么 \(i+1\) 处的人就可以打开门

\(f[i+1][j+b[i]]=\max(f[i+1][j+b[i]],f[i][j]+b[i]) j\in[0,a[i]-1]\)

\(如果 i+1\) 处新加的人大于 \(b[i]\) 时,我们完全可以看作 \(b[i]\) 个人开着门,剩下的人都去了 \(i\)

这种转移在上面已经体现了,没有必要再更新一次

当 \(a[i] \leq j < a[i]+b[i]\) 时,

第 \(i\) 个房间的人可以自己打开通道,但是为了保证第 \(i\) 个房间只能恰好 \(j\) 个人到达

所以第 \(i+1\) 个房间一个人不能放,没有贡献,而且必须舍弃 \(a[i]\) 个人去开门

\(f[i+1][j−a[i]]=\max(f[i+1][j-a[i]],f[i][j])\)

当 \(j \geq a[i]+b[i]\) 时,

和上面一样,但是不用留人去守门了

\(f[i+1][j]=\max(f[i+1][j],f[i][j])\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e3+5,maxm=2e4+10;
int n,m,a[maxn],b[maxn],mmax,f[2][maxm];
int main(){
	memset(f,~0x3f,sizeof(f));
	n=read(),m=read();
	for(rg int i=1;i<n;i++) a[i]=read(),b[i]=read(),mmax=std::max(mmax,std::max(a[i],b[i]));
	mmax=std::max(mmax,m)*2+1;
	rg int now=1;
	for(rg int i=0;i<m;i++) f[1][i]=i;
	for(rg int i=1;i<n;i++){
		now^=1;
		for(rg int j=0;j<=mmax;j++) f[now][j]=-0x3f3f3f3f;
		rg int haha=-0x3f3f3f3f;
		for(rg int j=0;j<a[i];j++) f[now][j+b[i]]=std::max(f[now][j+b[i]],f[now^1][j]+b[i]),haha=std::max(haha,f[now^1][j]);
		for(rg int j=0;j<b[i];j++) f[now][j]=std::max(f[now^1][j],haha+j);
		for(rg int j=a[i];j<a[i]+b[i];j++) f[now][j-a[i]]=std::max(f[now][j-a[i]],f[now^1][j]);
		for(rg int j=a[i]+b[i];j<=mmax;j++) f[now][j]=std::max(f[now][j],f[now^1][j]);
	}
	rg int ans=-0x3f3f3f3f;
	for(rg int i=0;i<=mmax;i++) ans=std::max(ans,f[now][i]);
	printf("%d\n",ans);
	return 0;
}
上一篇:$Noip2013/Luogu1967$ 货车运输 最大生成树+倍增$lca$


下一篇:wqs二分学习笔记