灯
题目描述
有一排 (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);
}