20231109 T1 星穹铁道题解

 

题面(简化)

给出 $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();
}