题面(简化)
给出 $a$ 数组 $(1\sim n)$,$y,x,z$ 数组 $(1\sim t)$,以及两个正整数 $l,r$。要求进行 $t$ 次变换,具体的,对于 $j\in (1,t)$:
$\forall a_i\equiv y_j (\bmod~x_i),a_i\to a_i+z_j$
限制:$z_j\in[-1,1],\forall j\in [1,k]$
要求输出 $t$ 次变换结束后有多少个 $a_i$ 满足 $a_i\in[l,r]$ ,并从小到大输出符合上述条件的 $a_i$ 的编号 $i$
题解
1.暴力算法
这道题暴力可以拿整整 $40pts$ ,而且这个暴力没有一点难度,所以是考场上的一个好选择。
直接模拟即可,复杂度 $O(tn)$,19行代码就可以搞定(不包括快读):
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
return s*w;
}
const int N=1e6+5;
vector<int> answ;
int n,t,a[N],y[N],z[N],x[N],l,r,ans;
signed main()
{
freopen("star.in","r",stdin);
freopen("star.out","w",stdout);
n=read(),t=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=t;i++) x[i]=read(),y[i]=read(),z[i]=read();
l=read(),r=read();
for(int i=1;i<=t;i++) for(int j=1;j<=n;j++) if(a[j]%x[i]==y[i]%x[i]) a[j]+=z[i];
for(int i=1;i<=n;i++) if(l<=a[i]&&a[i]<=r) ans++,answ.emplace_back(i);
printf("%lld\n",ans);
for(auto i:answ) printf("%lld ",i);
}
2.正解
其实也不难,但容易被暴力的简单和得分高蒙蔽双眼。
因为 $ | z_i | ≤1$,所以一个站点不会越过另一个站点。而显然两个站点相遇后站点就会一起移动,所以站点的相对顺序不会发生变化 |
这个结论很简明,但非常重要,必须再强调一遍:
站点的相对顺序不会发生变化
现在,你想到了什么?
因为相对顺序不发生变化,所以最后在区间内的站点原来也在一个区间内!
那么,我们可以想到两种阶梯方法:
1.对 $a_i$ 排序后二分。此方法见 wbx 的题解(因为我没有用这种方法,也懒的写)
2.线性做法,将 $l$ 和 $r$ 倒推回去,即可求出原来的区间。
两种方法都可以过这题,但是显然第二种复杂度更优。(第一种复杂度 $O(tlogn)$,而第二种复杂度 $O(t+n)$)
但第二种细节更多一点。如何倒推?显然,最简单的“有改即改”并不正确,比如下面这段代码:
for(int i=t;i>=1;i--)
{
if((l+z[i])%x[i]==y[i]%x[i]) l-=z[i];
if((r+z[i])%x[i]==y[i]%x[i]) r-=z[i];
}
看似无懈可击,其实犯了大错。仔细想想就知道,在区间端点 $l,r$ 往回跳的时候不止一种跳法,因为正向操作会使得不同的点变到同一个点位。我们要让 $l$ 尽可能地往左跳,$r$ 尽可能往右跳。
至于具体怎么实现,请看下面这份完整代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
return s*w;
}
const int N=1e6+5;
vector<int> answ;
int n,t,a[N],y[N],z[N],x[N],l,r,ans;
void solve()
{
for(int i=t;i>=1;i--)//注意这个循环,以及里面的细节
{
if(z[i]==1)
{
if(r%x[i]==y[i]%x[i]) r-=z[i];
if((l-z[i])%x[i]==y[i]%x[i]) l-=z[i];
}
else
{
if((r-z[i])%x[i]==y[i]%x[i]) r-=z[i];
if(l%x[i]==y[i]%x[i]) l-=z[i];
}
}
for(int i=1;i<=n;i++)
{
if(l<=a[i]&&a[i]<=r) ans++,answ.emplace_back(i);
}
printf("%lld\n",ans);
for(auto i:answ) printf("%lld ",i);
}
signed main()
{
n=read(),t=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=t;i++) x[i]=read(),y[i]=read(),z[i]=read();
l=read(),r=read();solve();
}