Codeforces Round 855 (Div. 3) E题题解

Unforgivable Curse (hard version)

在这里插入图片描述

问题建模

给定两个字符串s和t,每次能让一个字符与其相差k或k+1个距离的字符进行交换,问能否让字符串s通过若干次该操作使其变为字符串t

问题分析

方法1分析性质

1.分析操作对元素位置的影响

使用一次该操作,可以让当前元素与另一个元素交换位置,再用一次则可以将该元素放到与之相邻的位置,再一次则可以让相邻两个元素完成交换,且不影响其余元素。

在这里插入图片描述

2.分析可以使用操作的元素可以与相邻元素交换位置的作用

对于可以使用该操作的元素,其可以与相邻元素交换位置,则所有可以使用该操作的元素之间可以通过连续使用该操作,从而可以任意交换顺序,则对于这些位置的元素只需检查两个字符串字符是否对应都有即可。而不能使用该操作的元素则需要单独比较是否对应相等

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =210, Mod = 998244353, P = 2048;

void solve() {
    int n,k;
    cin >>n >>k;
    string s,t;
    cin >>s >>t;
    vector<int> cnt(26,0);
    bool ok=true;
    for(int i=0;i<n;i++){
        if(i>=k||i+k<n){
            cnt[s[i]-'a']++,cnt[t[i]-'a']--;
        }else {
            ok&=(s[i]==t[i]);
        }
    }
    if(ok&&count(cnt.begin(),cnt.end(),0)==26) puts("YES");
    else puts("NO");
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

方法2通过DFS得到相互可以交换位置的字符集合

每个位置一次操作可以到达的点为一个可以相互交换的集合,可以先通过DFS得到每个点所属集合。然后将两个字符串中对应位置的字符存入所在集合里,排序后形成形式同一的字符串进行比较。

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =2e5+10, Mod = 998244353, P = 2048;
int n,k;
int a[N];
vector<char> g[N];
int idx;
void dfs(int u,int idx){
    if(u<0||u>=n||a[u]) return ;
    a[u]=idx;///记录u点所在集合编号
    dfs(u+k,idx),dfs(u+k+1,idx),dfs(u-k,idx),dfs(u-k-1,idx);
}

string make(string str){
    for(int i=1;i<=idx;i++){
        g[i].clear();
    }
    for(int i=0;i<n;i++){
        ///将各点字符存入所属集合内
        g[a[i]].push_back(str[i]);
    }
    for(int i=1;i<=idx;i++){
        ///将各个集合内的字符排序
        sort(g[i].begin(),g[i].end());
    }
    string ans;
    ///将各个集合内的元素弄成形式统一的字符串用于比较
    for(int i=0;i<n;i++){
        ans+=g[a[i]].back();
        g[a[i]].pop_back();
    }
    return ans;
}

void solve() {
    cin >>n >>k;
    string s,t;
    cin >>s >>t;
    idx=0;
    memset(a,0,sizeof(int)*(n+1));
    for(int i=0;i<n;i++){
        if(!a[i]){
            dfs(i,++idx);
        }   
    }   
    
    if(make(s)==make(t))    puts("YES");
    else puts("NO");
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

方法3通过并查集连通可交换位置

先通过并查集将每个位置可到达的位置连通,然后将连通的字符进行记录,最后比较两个字符串每个连通集内的字符。

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =2e5+10, Mod = 998244353, P = 2048;
int n,k;
int a[N];
int cnt[N][26];
int p[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}

void solve() {
    cin >>n >>k;
    string s,t;
    cin >>s >>t;
    for(int i=0;i<n;i++)    p[i]=i;
    for(int i=0;i<n;i++)    memset(cnt[i],0,sizeof(int)*(26));
    
    ///将可以相互到达的位置连通
    for(int i=0;i<n;i++){
        if(i+k<n){
            int fa=find(i),fb=find(i+k);
            if(fa!=fb){
                p[fa]=fb;
            }
        }
        if(i+k+1<n){
            int fa=find(i),fb=find(i+k+1);
            if(fa!=fb){
                p[fa]=fb;
            }
        }  
    }   

    for(int i=0;i<n;i++){
        ///将该位置字符记录在对应连通块内
        cnt[find(i)][s[i]-'a']++;
        cnt[find(i)][t[i]-'a']--;
    }    

    for(int i=0;i<n;i++){
        if(find(i)==i){
            ///检查该连通块内字符是否相等
            for(int j=0;j<26;j++){
                if(cnt[i][j]!=0){
                    puts("NO");
                    return ;
                }
            }
        }
    }
    puts("YES");
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值