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


    题目描述

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

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

    (1leq n,qleq 10^5,1leq mleq n)

    解法

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

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

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

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

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

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

    哈哈,总时间复杂度 (O(qsqrt 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
    ",ans);
    	}
    }
    

    十字路口

    题目描述

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

    (1leq nmleq 100000,0leq 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))

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

    平衡一下两种方法就可以做到 (O(nmsqrt {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
    ",ans);
    }
    

    密室逃脱

    题目描述

    (n) 个房间编号为 (1,n),有 (n-1) 的通道,第 (i) 个通道连接房间 (i)(i+1),通道正常情况下是关闭着的,要打开第 (i) 个通道需要有 (a_i) 个人在房间 (i) 按住开关或者 (b_i) 个人在房间 (i+1) 按住开关,按开关的人不能进行其他操作(比如移动和按另一个开关),一旦松开开关通道会立即关上。

    在房间 (1) 有一个通道通往出口,需要 (m) 个人按住开关,你想知道在保证这个通道无论如何都不会被打开的情况下,最多可以有多少个人(你可以任意指定他们所在的初始房间)

    (1leq nleq 1000,1leq m,a_i,b_ileq 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][0sim b_i-1])
    • (a_ileq j<a_i+b_i),前面的人开门到后面去:(f[i+1][j-a_i])
    • (a_i+b_ileq j),可以直接开门到后面去 (f[i+1][j])

    时间复杂度 (O(n imesmax(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
    ",ans);
    }
    
  • 相关阅读:
    Rman-10038: Database Session For Channel D1 Terminated Unexpectedly
    MySQL从入门到项目实践 pdf下载
    Oracle_优化器使用(oracle11g)
    当sqlserver启用sa账户时,出现Microsoft SQL Server 错误代号: 15535 解决方法 (转)
    sqlserver 如何分析“死锁调度程序”转储?
    AtCoder Beginner Contest 213
    LOJ
    [学习笔记] 计算几何
    [COCI 2009-2010 #6] XOR
    BZOJ
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14616589.html
Copyright © 2020-2023  润新知