P15651 题解

· · 题解

把题目中给定的操作想象成环上有一个分界点,每次可以把它往一边动两个位置,然后把经过的两个数异或起来。

那么我们得到第一个观察:需要把 a 分段,每段异或起来对应 b 上面一个数。

首先有些情况下我们无法做到把一整段异或起来之后变成一个数。具体地,考虑分界点上那一段,我们发现:(0,0),(1,1),(2,2) 状物无解,而其余情况均有解。事实上,这种情况无解当且仅当左右两边 \bmod\ 3 的余数相同。

对于外面的段,可以用类似的分析说明它的长度不能是 3 的倍数。

接下来我们考虑一个合法的操作序列。首先经过一系列操作之后分界点一定会从它一开始所在的那个段里面走出来,走到它的两个端点之一。这样可以大大简化之后的讨论。

根据之前的分析,我们发现此时每一段的长度都不是 3 的倍数。

考虑把之后的操作分成若干阶段,每一阶段的内容是走到某一个环上的分段里面,经过一些操作再走出来。

不难得到如下的观察:如果分界点从某个分段的一端走到另一端,那么它的长度模 3 的余数一定会变化;否则,如果分界点从一端进去又走了回来,那么这个余数保持不变。

所以我们不用关心每一阶段的具体内容,只需要关心它从哪一头进去,从哪一头出来。

接下来的部分中,我们用“一次操作”代指这里的“一个阶段”。

对于每个长度 >1 的段,一定有至少一次操作作用在它身上。我们贪心地让除了最后一次操作之外的部分尽量简化,把多出来的工作全让最后一次操作承担。容易说明这是可以做到的。

现在把操作分成两类:I 形操作从一端走到另一端,U 形操作从一端走进去又走回来。由于除了最后一次操作之外都尽量简化,那么 U 形操作直接简化到没有了;而对于最后一次操作,也可以把它拆成两次 I 形操作(读者自证不难)。

也就是说,我们可以对操作序列进一步简化:每一次操作是从一个分段的一端走到另一端。我们先不关心这个玩意具体的实现。

容易发现,在这种极端抽象的模型中,我们不需要关心走的方向,所以可以把这个有向路径改写成无向图。这个无向图的任意一条欧拉路径都是合法的。而根据欧拉路径的性质,如果一条边重复了至少三次,那就可以去掉两次。也就是说,存在一个方案使得每段至多经过两次。

这是一个很强的性质。如果一段经过次数非常多,那它的长度一定需要足够长。而如果一段只需要经过一次或者两次,那么它对长度几乎没有限制——事实上,在 \bmod\ 3 的余数正确的前提下,只有 1 需要特判。

接下来枚举分界点最后的位置。在这种情况下,可能的欧拉路径只有一种,如下所示:

其余情况都是它的镜像或者退化版本。

把路径按照 1\cdots n 的顺序分成四段:第一段只经过一次,第二段经过两次,第三段一次都不经过,第四段经过两次。

分界点的情况需要特殊处理,不过事实上对它的限制除了两侧 \bmod\ 3 的余数之外也是几乎没有。这样容易得到一个单次 O(n^2m) 的 dp。需要记录用了几个 a_i,分成了几段,并枚举下一段的长度。总复杂度 O(n^3m)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=257;
ll T,n,m,pos,flip;
ll a[N],b[N];
namespace A{
    ll a[N],b[N];
    bool f[N][N][4];
    bool check_zero(int l,int r){
        return l==0&&r==1||l==1&&r==2||l==2&&r==0;
    }
    bool check(int type,int dis){
        if (type==0){
            return dis%3==2;
        }
        if (type==2){
            return dis==1;
        }
        return dis>=4&&dis%3==1;
    }
    bool solve(){
        memset(f,0,sizeof(f));
        f[0][0][pos==0]=1;
        for (int i=0;i<m;++i) for (int j=0;j<n;++j){
            f[i][j][2]|=f[i][j][1];f[i][j][3]|=f[i][j][2];
            for (int k=0;k<4;++k) if (f[i][j][k]){
                for (int l=j+1;l<=n;++l) if (a[l]==b[i+1]){
                    if (j<pos&&pos<l){
                        if (check_zero((pos-j)%3,(l-pos)%3)){
                            f[i+1][l][1]=1;
                        }
                    }
                    else if (check(k,l-j)){
                        f[i+1][l][k|(l==pos)]=1;
                    }
                }
            }
        }
        if (f[m][n][1]||f[m][n][2]||f[m][n][3]){
            cout<<"Yes\n";
            for (int i=1;i<=n-m;++i) cout<<"1 ";cout<<'\n';
            return 1;
        }
        return 0;
    }
}
bool solve2(){
    for (int i=1;i<=n;++i){
        A::a[i]=A::a[i-1]^a[i];
    }
    for (int i=1;i<=m;++i){
        A::b[i]=A::b[i-1]^b[i];
    }
    return A::solve();
}
void solve(){
    for (int i=1;i<=n;++i){
        if (solve2()){
            return;
        }
        ++pos;
        ll tmp=a[n];
        for (int i=n;i>1;--i){
            a[i]=a[i-1];
        }
        a[1]=tmp;
    }
    if (solve2()){
        return;
    }
    pos=0;flip=1;
    reverse(a+1,a+1+n);reverse(b+1,b+1+m);
    for (int i=1;i<=n;++i){
        if (solve2()){
            return;
        }
        ++pos;
        ll tmp=a[n];
        for (int i=n;i>1;--i){
            a[i]=a[i-1];
        }
        a[1]=tmp;
    }
    if (solve2()){
        return;
    }
    cout<<"No\n";
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>T>>T;
    while(T--){
        cin>>n>>m;pos=0;flip=0;
        for (int i=1;i<=n;++i){
            cin>>a[i];
        }
        for (int i=1;i<=m;++i){
            cin>>b[i];
        }
        solve();
//      cout<<T<<endl;
    }
    return 0;
}

(代码不包含构造部分。)

至于往正解优化也很简单:发现从前到后每一段都可以贪心选取。由于第一、第二和第四段长度不固定,所以这里的贪心是简单的。第三段需要预处理一下匹配长度,所以总复杂度是 O(n^2m)。应该可以用 bitset 去掉一个 w 但我懒得写。细节比较多。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=257;
ll T,n,m,pos,flip;
ll a[N],b[N];
namespace A{
    ll a[N],b[N],f1[N],f2[N],f3[N],match[N][N],p2[N],p3[N];
    vector<ll> ans,L,M,R;
    void left(){
        ans.emplace_back(2);
    }
    void right(){
        ans.emplace_back(1);
    }
    void construct(){
        ans.clear();L.clear();M.clear();R.clear();
        int stat=3,pl=0,pr=0;
        for (int i=m;i;--i){
            if (stat==1){
                M.emplace_back(f1[i]-f1[i-1]);
            }
            else if (stat==2){
                if (p2[i]){
                    pl=pos-f1[i-1];pr=f2[i]-pos;
                    stat=1;
                }
                else{
                    R.emplace_back(f2[i]-f2[i-1]);
                }
            }
            else{
                if (p3[i]!=-1){
                    f2[i-p3[i]]=f3[i]-p3[i];
                    i-=p3[i]-1;
                    stat=2;
                }
                else{
                    L.emplace_back(f3[i]-f3[i-1]);
                }
            }
        }
        if (pl||pr){
            if (pr&1){
                if (pl==1){
                    pr-=2;++pl;
                    right();
                }
                left();
                pl-=2;++pr;
            }
            while(pr){
                pr-=2;++pl;
                right();
            }
            M.insert(M.begin(),pl);
        }
        reverse(R.begin(),R.end());
        for (auto x:R){
            int p=(x-4)/3;
            while(p--){
                right();right();left();
            }
            right();right();
        }
        for (auto x:R){
            left();
        }
        for (auto x:M){
            int p=(x-2)/3;
            while(p--){
                left();left();right();
            }
            left();
        }
        for (auto x:L){
            int p=(x-4)/3;
            while(p--){
                left();left();right();
            }
            left();left();
        }
        for (auto x:L){
            right();
        }
        assert(ans.size()==n-m);
        for (auto x:ans){
            cout<<(flip*3^x)<<' ';
        }
        cout<<'\n';
    }
    bool solve(){
        if (a[n]!=b[m]){
            return 0;
        }
        if (pos==0&&(n-m)%3){
            return 0;
        }
        for (int i=m;i;--i){
            for (int j=n;j;--j){
                match[i][j]=(b[i]==a[j])?match[i+1][j+1]+1:0;
            }
        }
        memset(f1,-1,sizeof(f1));
        memset(f2,-1,sizeof(f2));
        memset(f3,-1,sizeof(f3));
        memset(p2,0,sizeof(p2));
        memset(p3,-1,sizeof(p3));
        f1[0]=(pos==0?-1:0);
        f2[0]=(pos==0?0:-1);
        for (int i=1,p=1;i<pos&&p<=m;++i){
            if ((i-2*p)%3==0&&a[i]==b[p]){
                f1[p++]=i;
            }
        }
        for (int i=1;i<=m;++i){
            if (f1[i-1]==-1){
                continue;
            }
            int tot=n-((i-1)*2+(m-i));tot=(tot%3+3)%3;
            if ((4-tot+(i-1)*2-pos)%3){
                continue;
            }
            for (int o=pos+(2-tot);o<=n;o+=3){
                if (a[o]==b[i]){
                    f2[i]=o;
                    p2[i]=1;
                    break;
                }
            }
        }
        for (int i=0;i<m;++i){
            if (f2[i]==-1||f2[i+1]!=-1){
                continue;
            }
            for (int j=f2[i]+4;j<=n;j+=3){
                if (a[j]==b[i+1]){
                    f2[i+1]=j;break;
                }
            }
        }
        for (int i=0;i<=m;++i){
            if (f2[i]==-1){
                continue;
            }
            int r=(i==0?0:n);
            for (int j=f2[i];j<=r;j+=3){
                if (a[j]==b[i]){
                    int k=match[i+1][j+1];
                    if (f3[i+k]==-1||f3[i+k]>j+k){
                        f3[i+k]=j+k;
                        p3[i+k]=k;
                    }
                }
            }
        }
        for (int i=0;i<m;++i){
            if (f3[i]==-1){
                continue;
            }
            for (int j=f3[i]+4;j<=n;j+=3){
                if (a[j]==b[i+1]){
                    if (f3[i+1]==-1||f3[i+1]>j){
                        f3[i+1]=j;
                        p3[i+1]=-1;
                    }
                    break;
                }
            }
        }
        if (f3[m]!=-1){
            if (f3[m]<n){
                if (p3[m]==-1){
                    f3[m]=n;
                }
                else if (p3[m]){
                    f3[m-1]=f3[m]-1;
                    p3[m-1]=p3[m]-1;
                    f3[m]=n;p3[m]=-1;
                }
                else{
                    f2[m]=f3[m]=n;
                }
            }
            cout<<"Yes\n";
            construct();
            return 1;
        }
        return 0;
    }
}
bool solve2(){
    for (int i=1;i<=n;++i){
        A::a[i]=A::a[i-1]^a[i];
    }
    for (int i=1;i<=m;++i){
        A::b[i]=A::b[i-1]^b[i];
    }
    return A::solve();
}
void solve(){
    for (int i=0;i<=m+1;++i){
        for (int j=0;j<=n+1;++j){
            A::match[i][j]=0;
        }
    }
    for (int i=1;i<=n;++i){
        if (solve2()){
            return;
        }
        ++pos;
        ll tmp=a[n];
        for (int i=n;i>1;--i){
            a[i]=a[i-1];
        }
        a[1]=tmp;
    }
    if (solve2()){
        return;
    }
    pos=0;flip=1;
    reverse(a+1,a+1+n);reverse(b+1,b+1+m);
    for (int i=1;i<=n;++i){
        if (solve2()){
            return;
        }
        ++pos;
        ll tmp=a[n];
        for (int i=n;i>1;--i){
            a[i]=a[i-1];
        }
        a[1]=tmp;
    }
    if (solve2()){
        return;
    }
    cout<<"No\n";
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>T>>T;
    for (int t=1;t<=T;++t){
//      cerr<<t<<endl;
        cin>>n>>m;pos=0;flip=0;
        for (int i=1;i<=n;++i){
            cin>>a[i];
        }
        for (int i=1;i<=m;++i){
            cin>>b[i];
        }
        solve();
//      cout<<T<<endl;
    }
    return 0;
}