【Codeforces】CF1698E PermutationForces II Solution

搞出一个比官方题解更优的 \(O(n)\) 解法(?


题意

给定 \(n\) , \(s\) ,对给定的 \(1\cdots n\) 的排列 \(a\)\(n\) 次操作,其中第 \(i\) 次选定 \(x,y\in [i,\min\{i+s,n\}]\) 将序列中\(x\)\(y\) 的数所在位置交换,其中 \(x\) 可以等于 \(y\)。操作后的 \(a\) 变为了排列 \(b\) 。现在给出 \(b\) 并把其中的一些项替换为 \(-1\) ,问有多少个不同的可能的排列 \(b\)

题解

我们发现对一定范围内的数进行选择比较麻烦,因此我们考虑把交换数组中的两个数改为交换两个数的下标。我们可以将操作换成在下标数组上进行操作。

具体地说,首先我们构造一个数组 \(c_{1\cdots n}\) 其中 \(c_i=j\) 意味着 \(a_j=i\)

类似地我们构造一个数组 \(d_{1\cdots n}\) ,其中,

  • 若数 \(i\)\(b\) 中的下标已经确定为 \(j\) ,令 \(d_i=j\)
  • 若数 \(i\)\(b\) 中的下标未确定(即被替换为 \(-1\) ),令 \(d_i=-1\)

那么我们就可以将题目中的操作转化为:

\(i\) 次操作选取 \(x,y\in [i,\min\{i+s,n\}]\) 并交换 \(c_x\)\(c_y\) 的值。最终 \(c\) 在操作后变为 \(d\)

这样我们每次都是操作的范围是连续的一段区间,显然舒服的多。

现在我们再来分析被转化后的操作。

我们可以发现下面几点:

  • \(\forall i\) ,还原后的 \(d_i\) 的值必然是 \(c_1,c_2\cdots c_{\min\{i+s,n\}}\) 中的一个;
  • \(i\) 步的操作对后续操作的可选下标对应数值的范围的影响仅仅为不能选择第 \(i\) 步操作后的 \(c_i\)
  • 若第 \(i\) 步选择的 \(x,y\) 均不等于 \(i\) ,那么这对序列后续操作的影响和 \(x=y=i\) 时等价,因为此时的 \(x,y\) 的选择对后续每个位置可选的数的范围没有影响。

因此我们不妨令第 \(i\) 次操作中 \(x=i\) ,从而我们可以认为第 \(i\) 次操作确定还原后的 \(d_i\) 的值。

进一步地,我们还可以将“操作”转化为:

\(i\) 次操作选取 \(c_1,\cdots ,c_{\min\{i+s,n\}}\) 中一个不存在于 \(c'_1,c'_2,\cdots,c'_{i-1}\) 的数 \(c_k\) , 然后 \(c'_i \leftarrow c_k\)

最终,\(c'\) 变为 \(d\)

这样,我们就会发现每一个未确定的 \(d_i\) 的可选值的个数就是 \(c_1,\cdots ,c_{\min\{i+s,n\}}\)在所有操作前未确定 \(d\) 中下标的数的个数减去\(d_1\cdots d_{i-1}\)\(-1\) 的个数(即未确定数值的个数)

此外,我们判断是否存在解的方式就是看是否存在 \(d_i\) 使得其在 \(c\) 中的下标 \(j\) 满足 \(i+s<j\) 。若有说明无论如何 \(d_i\) 所在位置无论什么情况下都不可能取到 \(d_i\),无解 ;若没有则显然可以做到。

判断是否有解之后,将 \(d_i\) 每一位可选值的个数乘起来就是答案了。代码中是若干次线性扫描,因此时间复杂度为 \(O(n)\)

如果还有不懂的地方就看看代码吧~

代码

#include<cstdio>
#include<iostream>
#define MAXN 200000
#define MOD 998244353
using namespace std;

int a[MAXN+5],b[MAXN+5],c[MAXN+5],d[MAXN+5];
bool vis[MAXN+5];

int main() {
    ios::sync_with_stdio(0);
    int t,n,s,cnt,p;
    bool flag;
    long long ans;
    cin>>t;
    while (t--) {
        ans=1;cnt=0;flag=0;p=0;
        cin>>n>>s;
        for (int i=1;i<=n;++i) d[i]=-1,vis[i]=0;
        for (int i=1;i<=n;++i) cin>>a[i],c[a[i]]=i;
        for (int i=1;i<=n;++i) cin>>b[i];
        for (int i=1;i<=n;++i) if (b[i]!=-1) d[b[i]]=i,vis[i]=1;
        for (int i=1;i<=n;++i) if (a[d[i]]-i>s) flag=1;
        if (flag==1) {
            cout<<"0\n";
            continue;
        }
        for (int i=1;i<=n;++i) {
            while (p<i+s&&p<n) {++p;if (!vis[c[p]]) ++cnt;}
            if (d[i]==-1) {
                ans=(ans*cnt)%MOD;
                --cnt;
            }
        }
        cout<<ans<<"\n";
    }
    return 0;
}
posted @ 2022-06-29 18:59  思考人生中…  阅读(55)  评论(0编辑  收藏  举报