构造、贪心

如果第一次操作取出了最左边的数 $a_1$,那么最后一次操作必须取出序列中的另一个 $a_1$ 完成回文。

于是通过第一次操作可以确定最后一次操作,也就是说第一次操作和与之对应的最后一次操作将序列 $a$ 分割为两个双端队列,使用 $L$ 表示左边的双端队列,$R$ 表示右边的双端队列。

接下来的第二次操作和与之对应的倒数第二次操作,第二次操作将会在 $L$ 的左端或 $R$ 的右端取出一个数,倒数第二次操作将会在 $L$ 的右端或 $R$ 的左端取出一个数,由于回文的性质这两个数必须相等

由于要求字典序最小的解,如果能操作 $L$ 的左端那么必然优先操作 $L$ 的左端。

在第一次操作确定时,存在 $L$ 的左端和 $R$ 的右端都可操作,而先操作 $R$ 的右端有解而先操作 $L$ 的左端无解的情况吗?不存在,因为序列中每个数恰好出现两次,两个操作互不影响。第一次操作将序列“分割”成两个双端队列,如果第一次操作取出 $a_1$ 不行再尝试第一次操作取出 $a_{2n}$,两种方案都不可行则问题无解。

注意特判 $L$ 或 $R$ 仅剩一个元素的情况,这个元素不能和自己配对

时间复杂度 $O(n)$。

#include<bits/stdc++.h>
using namespace std;
ifstream fin("palin.in");
ofstream fout("palin.out");
int n,a[1'000'005];
string run(list<int> l,list<int> r){
    string retl,retr;
    for(int i=1;i<=n-1;i++){
        int ll=0,lr=0,rl=0,rr=0;
        if(!l.empty()) ll=l.front(),lr=l.back();
        if(!r.empty()) rl=r.front(),rr=r.back();
        if(ll&&l.size()!=1u&&ll==lr){//特判l只剩一个元素的情况
            retl+='L',retr+='L';
            l.pop_front();
            l.pop_back();
        }
        else if(ll&&ll==rl){
            retl+='L',retr+='R';
            l.pop_front();
            r.pop_front();
        }
        else if(rr&&rr==lr){
            retl+='R',retr+='L';
            r.pop_back();
            l.pop_back();
        }
        else if(rr&&r.size()!=1u&&rr==rl){//特判r只剩一个元素的情况
            retl+='R',retr+='R';
            r.pop_back();
            r.pop_front();
        }
        else return "-1";
    }
    reverse(retr.begin(),retr.end());
    return retl+retr;
}
void solution(){
    fin>>n;
    for(int i=1;i<=n*2;i++) fin>>a[i];
    int pre=find(a+2,a+n*2+1,a[1])-a,suf=find(a+1,a+n*2,a[n*2])-a;
    string ans=run(list<int>(a+2,a+pre),list<int>(a+pre+1,a+n*2+1));
    if(ans!="-1") return fout<<'L'<<ans<<'L'<<'\n',void();
    ans=run(list<int>(a+1,a+suf),list<int>(a+suf+1,a+n*2));
    if(ans!="-1") return fout<<'R'<<ans<<'L'<<'\n',void();
    fout<<"-1"<<'\n';
}
int T;
int main(){
    fin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 12 月 01 日
如果觉得我的文章对你有用,请随意赞赏