模拟赛2021.4.4

2021.4.4

T1

题意:初始时有\(n\)个灯,灯有\(m\)种颜色,进行\(q\)次操作,每次将一种颜色的灯改变状态(熄灭或点亮),每次操作后询问开着的灯的连续段大小

\(n,q\leq 10^5,1\leq m\leq n\)

数据规模\(10^5\),不难想到应该为\(O(n\sqrt n)\)\(O(n\log^2n)\)的算法

但维护这种不连续段的极大子段个数难以使用线段树类的数据结构维护

所以考虑使用分块的思想

可以先进行一个转换:\(Ans=\sum_{i=1}^na_i-\sum_{i+1==j}[a_i\And a_{j}]\)其中\(a_i\)表示第\(i\)个灯是非亮起

然后将每种颜色的灯按其出现次数分为大于\(K\)\(A\)类和小于等于\(K\)\(B\)

1.加入/删除\(A\)类中颜色\(x\)

直接枚举每个颜色为\(x\)的灯即可,单次\(O(\sqrt n)\)

2.加入/删除\(B\)类中颜色为\(x\)

我们先预处理出两两\(B\)类元素的相互贡献,加入/删除时枚举每种已经出现的\(B\)类元素

至于\(AB\)间的贡献则在加入\(A\)中元素时记录即可

还需要特殊考虑\(x\)\(x\)自己的贡献

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;char k;bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
int s,m,q,a[100005],ans,id[100005],b[325][325],c[100005],vn[100005];
bool vis[100005];
const int Block=320;
vector<int>h[100005],p;
void Del(int n){//nowvis = 1
	--ans;
	if(a[n-1]!=a[n]&&vis[a[n-1]])++ans;
	if(h[a[n-1]].size()>Block)--c[a[n-1]];
	if(vis[a[n+1]])++ans;
	if(h[a[n+1]].size()>Block)--c[a[n+1]];
}
void Add(int n){//nowvis = 0
	++ans;
	if(a[n]==a[n-1]||vis[a[n-1]])--ans;
	if(h[a[n-1]].size()>Block)++c[a[n-1]];
	if(vis[a[n+1]])--ans;
	if(h[a[n+1]].size()>Block)++c[a[n+1]];
}int Abs(int x){return x<0?-x:x;}
void prep(int x,int y){
	for(int i=0,j=0;i!=h[p[x]].size()&&j!=h[p[y]].size();){
		if(Abs(h[p[x]][i]-h[p[y]][j])<2)++b[x][y],++b[y][x];
		if(h[p[x]][i]<h[p[y]][j])++i;
		else ++j;
	}
}
int main(){fre("light");
	s=read,m=read,q=read;
	for(int i=1;i<=s;++i)a[i]=read,h[a[i]].push_back(i),a[i]==a[i-1]&&++vn[a[i]];
	for(int i=1;i<=m;++i)
		if(h[i].size()>Block){
			id[i]=p.size();
			p.push_back(i);
		}
	for(int i=0;i<p.size();++i)
		for(int j=i+1;j<p.size();++j)
			prep(i,j);
	for(int i=1;i<=q;++i){
		int n=read;
		if(vis[n]){
			if(h[n].size()<=Block){
				for(int j=0;j<h[n].size();++j)
					Del(h[n][j]);
			}else{
				ans-=h[n].size();
				ans+=c[n];
				for(int j=0;j<p.size();++j)
					if(p[j]!=n&&vis[p[j]])ans+=b[id[n]][j];
				ans+=vn[n];
			}
			vis[n]=0;
		}else{
			if(h[n].size()<=Block){
				for(int j=0;j<h[n].size();++j)
					Add(h[n][j]);
			}else{
				ans+=h[n].size();
				ans-=c[n];
				for(int j=0;j<p.size();++j)
					if(p[j]!=n&&vis[p[j]])ans-=b[id[n]][j];
				ans-=vn[n];
			}
			vis[n]=1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

时间复杂度\(O((n+q)\sqrt n)\)

T2

题意:一个包含\(n\)个灯的红绿灯系统,有着固定的周期,在一个周期中,每个红绿灯恰好一次由红变绿,恰好一次由绿变红(并且都是在整数时刻发生改变)。一个红绿灯如果当前是红色则会显示还需要多久变绿。你观察了\(m\)次这个红绿灯系统,并且记下了每次观察的结果。但非常不幸的是,你忘记了每次观察的时间。你想知道能否通过你记录下的信息确定它的周期,如果可以输出周期,否则输出-1。

\(1\leq nm\leq 10^5\)

可以发现,我们真正需要用到的其实只有此时为红灯的情况

而在\(mod\ T\)意义下,两两信息间均为红灯的灯的差分数组应相等

所以我们可以在两两信息间连上有向边,边权为时间差

这就是典型的同余最短路的模型,使用\(\tt spfa\)可以做到\(O(nm)\)(好像在同余最短路中可以保证\(spfa\)的时间复杂度

当然也可以使用\(\tt Floyd\),但这样需要对于\(n>m\)的情况变下形,转换成灯之间的同余最短路

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

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;char k;bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
int n,m,*a[100005],vis[100005],sta[100005],T,f[320][320];
int main(){fre("crossing");
	m=read,n=read;
	if(n>m){
		for(int i=0;i<=n;++i)
			a[i]=new int[m+5]();
		for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
				a[i][j]=read;
	}
	else{
		for(int i=0;i<=m;++i)
			a[i]=new int[n+5]();
		for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
				a[j][i]=read;
		swap(n,m);
	}memset(f,0x7f>>1,sizeof(f));
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			if(a[i][j])
				for(int k=1;k<=m;++k)
					if(a[i][k]>a[i][j])
						f[j][k]=min(f[j][k],a[i][k]-a[i][j]);
	for(int k=1;k<=m;++k)
		for(int i=1;i<=m;++i)
			for(int j=1;j<=m;++j)
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	int ans=**f;
	for(int i=1;i<=m;++i)
		ans=min(ans,f[i][i]);
	printf("%d",ans==**f?-1:ans);
	return 0;
}

T3

题意:有\(n\)个房间,编号为\(1\)\(n\)。有\(n-1\)个隧道,第\(i\)个隧道连接房间\(i\)\(i+1\)。隧道在正常情况下是关闭着的,要打开第\(i\)个隧道需要有\(a_i\)个人在房间\(i\)按住开关或者\(b_i\)个人在房间\(i+1\)按住开关。按开关的人不能进行任何其它操作(比如移动或者同时按另一个开关),一旦他们松开开关,隧道会立刻关上。在房间\(1\)有一个隧道通往出口,要打开这个隧道需要\(m\)个人按住开关。你想知道在保证这个隧道在任何时刻都不会被打开的情况下,最多可以有多少个人(你可以指定他们初始在哪个房间)。

\(1\leq n\leq 1000,1\leq m,a_i,b_i\leq 10^4\)

不难想到一个\(dp\),设\(f_{i,j}\)表示前\(i\)在第\(i\)个点有\(j\)人时最多能有多少人

可是这样不会出问题吗?第二维不会很大吗?

其实并不会(我就是死在了这里

如果一个房间人数超过了\(\max(a_i+b_i)\)\(m\),则这个房间的人可以自由通行而不用把人留下来

所以第二维不会超过\(2\cdot 10^4\)

考虑转移:

如果\(j< a_i\),\(f_{i,j}\rightarrow f_{i+1,j+b_i},f_{i+1,k}(k< j)\),表示人最初从右而来或者人在右过不去

如果\(a_i\leq j< a_i+b_i\),\(f_{i,j}\rightarrow f_{i+1,j-a_i}\),表示人往右走

如果\(a_i+b_i\leq j\)\(f_{i,j}\rightarrow f_{i+1,j}\),人可以自由通行

时间复杂度\(O(nm)\),空间复杂度\(O(nm)/O(m)\)

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;char k;bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
int _max(int &v,int x){return v=max(v,x);}
int f[1005][20005],s,m,a[1005],b[1005],g[1005][20005],tk[1005];
int main(){fre("escape");
	s=read,m=read;
	for(int i=1;i<s;++i)a[i]=read,b[i]=read;
	memset(f,-1,sizeof(f));
	for(int i=0;i<m;++i)f[1][i]=i;
	for(int i=1;i<s;++i){
		for(int j=tk[i];j--;)
			_max(f[i][j],j+_max(g[i][j],g[i][j+1]));
		for(int j=0;~f[i][j];++j)
			if(j<a[i]){
				_max(f[i+1][j+b[i]],f[i][j]+b[i]);
		 		_max(g[i+1][b[i]-1],f[i][j]);
				_max(tk[i+1],b[i]);
			}else if(a[i]+b[i]<=j)_max(f[i+1][j],f[i][j]);
			else _max(f[i+1][j-a[i]],f[i][j]);
	}int ans=0;
	for(int i=1;i<=20000;++i)_max(ans,f[s][i]);
	cout<<ans;
	return 0;
}
posted @ 2021-04-04 16:32  ファイナル  阅读(108)  评论(0)    收藏  举报