ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[省选前集训2021] 模拟赛7

2021-04-04 18:04:56  阅读:142  来源: 互联网

标签:颜色 int 复杂度 sqrt 省选前 leq 2021 include 集训


题目描述

有一排 \(n\) 个灯,每个灯颜色 \(1\) 到 \(m\),一开始所有灯都是关着的。

有 \(q\) 次操作,每次改变某种颜色灯的状态,每次操作后查询有多少个极长的开着灯的连续段。

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

解法

\(O(nq)\) 暴力期望 \(13\) 分。

如果一个灯从关变开,那么可以合并左边的连续段和右边的连续段。

所以一个点的贡献只和它左边有没有值,右边有没有值有关,答案是所有点贡献之和。

考虑一个颜色被点亮时,影响的颜色是固定的,而且影响的方式也是固定的,现在我们把颜色作为考虑的单位,所以 \(m\leq 100\) 也能做了,期望得分 \(35\) 分。

然后好像是套路的分块调整复杂度,设影响颜色超过 \(\sqrt m\) 的颜色为大颜色,否则称为小颜色:

  • 小颜色对其他颜色的贡献,直接暴力改就行了,时间复杂度 \(O(\sqrt m)\)
  • 大颜色对小颜色的贡献,在小颜色的时候暴力查一下,每次最多查 \(O(\sqrt m)\) 个大颜色,时间复杂度 \(O(\sqrt m)\),大颜色对大颜色的贡献,直接暴力改可以做到 \(O(\sqrt m)\)

哈哈,总时间复杂度 \(O(q\sqrt m)\)(设 \(n,m\) 同阶)

#include <cstdio>
#include <vector>
#include <iostream>
#include <cmath>
#include <map>
using namespace std;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || x>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0) {x=-x;putchar('-');}
	if(x<=9) {putchar(x+'0');return ;}
	write(x/10);putchar(x%10+'0');
}
int n,m,q,t,ans,a[M],num[M],fl[M],s[M];
vector<int> g[M],g1[M],g2[M];
void work(int x)
{
	int f=fl[x]==0?1:-1;fl[x]^=1;
	//先修改
	for(int i=0;i<g1[x].size();i++)
		s[g1[x][i]]+=g2[x][i]*f;
	//再查询
	int sum=0;
	if(g[x].size()<t)//小颜色暴力查 
	{
		for(int i=0;i<g[x].size();i++)
			sum+=(fl[g[x][i]]==1?1:0);
	}
	else sum=s[x];
	ans+=f*(num[x]-sum);
}
int main()
{
	freopen("light.in","r",stdin);
	freopen("light.out","w",stdout);
	n=read();m=read();q=read();t=sqrt(n);
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1,j=1;i<=n;i=j)
	{
		j=i;
		for(;j<=n && a[i]==a[j];j++);
		num[a[i]]++;
		if(i) g[a[i]].push_back(a[i-1]);
		if(j<=n) g[a[i]].push_back(a[j]);
	}
	for(int i=1;i<=m;i++)
	{
		map<int,int> mp;
		for(int j=0;j<g[i].size();j++)
		{
			int v=g[i][j];
			if(g[v].size()>=t) mp[v]++;
		}
		map<int,int>::iterator it=mp.begin();
		for(;it!=mp.end();it++)
		{
			g1[i].push_back((*it).first);
			g2[i].push_back((*it).second);
		}
	}
	for(int i=1;i<=q;i++)
	{
		int c=read();
		work(c);
		printf("%d\n",ans);
	}
}

十字路口

题目描述

有 \(n\) 个红绿灯,定义周期为红灯持续时间\(+\)绿灯持续时间,已知每一盏灯的周期是相同的,且一开始都是红灯。某个人观察了 \(m\) 次,如果是绿灯则记录下 \(0\),如果是红灯则记录下变为绿灯的时间,但是你不知道每次观测的具体时间,问能否确定周期,如果能确定周期又是多少?

\(1\leq nm\leq 100000,0\leq x_{i,j}\leq10000\)

解法

考试时候做不出来主要是题意有点迷,这时候可以玩一下样例检查一下自己的理解。

设 \(x_i\) 表示第 \(i\) 次观察的时间,那么根据两次观测的结果可以列出方程,设 \(i\) 行某个灯到绿灯的时间是 \(a\),\(j\) 行这个灯到绿灯的时间是 \(b\),如果 \(a<b\) 那么 \(x_j-x_i=b-a\),这时候建一条 \((i,j)\) 的有向边,那么原图的环长一定是周期的倍数,所以找到原图的最小环就是周期,时间复杂度 \(O(m^3+nm^2)\)

因为本题给的条件是 \(nm\leq 100000\),所以肯定要拿 \(n,m\) 较小的那个来搞以保证复杂度。设 \(y_i\) 表示第 \(i\) 个灯的红灯持续时间,用类似的方法可以求他,时间复杂度 \(O(n^3+mn^2)\)

平衡一下两种方法就可以做到 \(O(nm\sqrt {nm})\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || x>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0) {x=-x;putchar('-');}
	if(x<=9) {putchar(x+'0');return ;}
	write(x/10);putchar(x%10+'0');
}
int n,m,ans=inf,a[M*M],f[M][M];
int id(int x,int y)
{
	return (x-1)*n+y;
}
int main()
{
	freopen("crossing.in","r",stdin);
	freopen("crossing.out","w",stdout);
	n=read();m=read();//m行n列 
	if(n<m)
	{
		for(int i=1;i<=n*m;i++)
			a[i]=read();
	}
	else
	{
		for(int i=1;i<=m;i++)
			for(int j=1;j<=n;j++)
				a[(j-1)*m+i]=read();
		swap(n,m);
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			f[i][j]=inf;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				if(a[id(i,j)] && a[id(i,k)]>a[id(i,j)])
					f[j][k]=min(f[j][k],a[id(i,k)]-a[id(i,j)]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				f[j][k]=min(f[j][k],f[j][i]+f[i][k]);
	for(int i=1;i<=n;i++)
		ans=min(ans,f[i][i]);
	if(ans==inf) puts("-1");
	else printf("%d\n",ans);
}

密室逃脱

题目描述

有 \(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 10000\)

解法

这道题就真的是状态定义的艺术了。

一开始我是从后往前 \(dp\) 的,设 \(dp[i][j]\) 为考虑后 \(i\) 个房间,有 \(j\) 个走到了 \(i\) 房间的最大放置人数,原理是我考虑人从后往前走,但是可能会遇到前面的人去给后面开门的情况,所以就凉了。

正解是从前往后 \(dp\),但是状态定义十分神奇,设 \(f[i][j]\) 为考虑前 \(i\) 个房间,在全局的所有情况下第 \(i\) 个房间最多会有 \(j\) 个人的最大放置人数,这样就巧妙地解决了跑来开门的问题,妙处就是:我们考虑的是部分,但是把全局的情况定义到状态中了,接下来只需要严格按这个东西转移就可以了:

  • 若 \(j<a_i\),可以是后面的人开门到这里来:\(f[i+1][j+b_i]\),或者是两个门之间割裂:\(f[i+1][0\sim b_i-1]\)
  • 若 \(a_i\leq j<a_i+b_i\),前面的人开门到后面去:\(f[i+1][j-a_i]\)
  • 若 \(a_i+b_i\leq j\),可以直接开门到后面去 \(f[i+1][j]\)

时间复杂度 \(O(n\times\max(n,a_i+b_i))\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || x>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M],b[M],f[M][20*M];
int main()
{
	freopen("escape.in","r",stdin);
	freopen("escape.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<n;i++)
		a[i]=read(),b[i]=read();
	for(int i=1;i<m;i++)
		f[1][i]=i;
	for(int i=1;i<n;i++)
		m=max(m,a[i]+b[i]);
	for(int i=1;i<n;i++)
	{
		int s=0,mx=0;
		for(int j=0;j<=m;j++)
		{
			if(j<a[i])
			{
				f[i+1][j+b[i]]=max(f[i+1][j+b[i]],f[i][j]+b[i]);
				mx=max(mx,f[i][j]);
			}
			else if(j<a[i]+b[i])
				f[i+1][j-a[i]]=max(f[i+1][j-a[i]],f[i][j]);
			else
				f[i+1][j]=max(f[i+1][j],f[i][j]);
		}
		for(int j=0;j<b[i];j++)
			f[i+1][j]=max(f[i+1][j],mx+j);
	}
	for(int i=0;i<=m;i++)
		ans=max(ans,f[n][i]);
	printf("%d\n",ans);
}

标签:颜色,int,复杂度,sqrt,省选前,leq,2021,include,集训
来源: https://www.cnblogs.com/C202044zxy/p/14616589.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有