CF1698E PermutationForces II Solution

· · 题解

UPD:修改了一个错误,感谢评论区指出(

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

题意

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

题解

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

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

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

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

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

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

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

我们可以发现下面几点:

因此我们不妨令第 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;
}