Codeforces Round 855 (Div. 3)(E~F)

68 阅读2分钟

E-Unforgivable Curse

思路

相距k或者k+1的点可以互换,仔细如果a 和 b可以互换,b和c可以互换那么,a和c也可以互换,那就想到可以用并查集来维护所有可以随意互换的连通块,用是否每个连通块都字母的情况都完全相同来判断。 但是这里有一个问题,每一个位置都要加进去 例如

5 3
abccc
baccc

可以 <15>,<2,5>,<1,5><1,5>,<2,5>,<1,5>来实现,所以不管是不是相等都要维护

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 2e5+10;
int p[N];
map<int,int> cf;
int find(int x)  // 并查集
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int dt[N/10][26][2];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,k;
        cf.clear();
        int l=0;
        memset(dt,0,sizeof dt);
        cin>>n>>k;
        for(int i=0;i<=n;i++)p[i]=i;
        string s,t;
        cin>>s>>t;
        t=" "+t,s=' '+s;
        p[k+1]=1;
        for(int i=k+2;i<=n;i++)
        {
                p[find(i)]=find(i-k);
                p[find(i)]=find(i-k-1);
        }
        for(int i=1;i<=n;i++)
        {
            
                if(cf.find(find(i))==cf.end())cf[find(i)]=l++;
                dt[cf[find(i)]][s[i]-'a'][0]++,dt[cf[find(i)]][t[i]-'a'][1]++;
        }
        //cout<<cf[2];
        bool fl=true;
        for(int i=0;i<l;i++)
        {
            for(int j=0;j<26;j++)if(dt[i][j][0]!=dt[i][j][1])fl=false;
        }
        if(fl)puts("YES");
        else puts("NO");
    }
}

然后就超时了

优化

仔细想想为什么是k和k+1,然后就会发现
1和k+1可以互换,也可以和k+2互换
2可以k+2,k+3可以互换,
我们会发现,1,2,k+1,k+2,k+3 1,2,k+1,k+2,k+3是一个连通块 所以会有下面的结论
前提条件 s 和 t字母种类和数量完全一样,否则直接No

  • if(n>=2k)if(n>=2k) 所有位置组成一个连通块,所以肯定可以可以
  • if(k<n<2n)if(k<n<2n) 只要判断 [nk+1,k][n-k+1,k]是不是完全相等,因为,这个位置的字母不能互换,即不能动
  • if(n<=k)if(n<=k) 所有位置都不能互换,直接判断是不是相等就行
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 2e5+10;
int p[N];
int dt[26][2];
int main()
{
    int tt;
    ios::sync_with_stdio(false);
    cin>>tt;
    while(tt--)
    {
        int n,k;
        bool fl=true;
        memset(dt,0,sizeof dt);
        cin>>n>>k;
        for(int i=0;i<=n;i++)p[i]=i;
        string s,t;
        cin>>s>>t;
        t=" "+t,s=' '+s;
        for(int i=1;i<=n;i++)dt[t[i]-'a'][0]++,dt[s[i]-'a'][1]++;
        for(int i=0;i<26;i++)if(dt[i][0]!=dt[i][1])fl=false;
 
        if(!fl)
        {
            puts("NO");
            continue;
        }
        if(n/2>=k)
        {
            puts("YES");
            continue;
        }
        for(int i=max(0,n-k)+1;i<=k&&i<=n;i++)
        {
            if(s[i]!=t[i])fl=false;
        }
        if(fl)puts("YES");
        else puts("NO");
    }
}

F. Dasha and Nightmares

思路

  • 字符串的数据这么大,而且最后答案也和字母种类有关,就可以尝试从26这个字母所有的种类入手
  • 看每种字母的数量都要奇数,就可以考虑缩减字符串,例如字符串 a="aaabbc"a= "aaabbc",就可以缩减成a="ac"a="ac" 即奇数留下,偶数去除,而这种字符刚好可以使用二进制来表示
  • 因为最后要有25种字母,所以可以枚举缺少的字母

具体实现

可以使用 一个map map cf[27]cf[27]
例如 cf[0][2]cf[0][2]在缺少a的字符串中缩减为b的数量

因为所有字母种类的数量都要为奇数,且种类为25,所以就可以求出每一种字符串在最后缺少某种字母的唯一匹配
例如 b 在最后缺少a的唯一匹配为 cdef...xyz

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 1e6+10;
map<int,int>cf[27];
int st[N][27];
int mi[30];
int main()
{
   std::ios::sync_with_stdio(false);
   std::cin.tie(0);
   int n,cgh=0;
   mi[0]=1,mi[1]=2;
   for(int i=2;i<28;i++)mi[i]=mi[i-1]*2;
   for(int i=0;i<26;i++)cgh+=1<<i;
   cgh=(~cgh);
   long long sum=0;
   cin>>n;
   for(int i=0;i<n;i++)
   {
       string tem;
       cin>>tem;
       for(auto ch:tem)
       {
           st[i][ch-'a']++;
       }
       int a=0;
       for(int j=0;j<26;j++)
       {
           if(st[i][j]&1)a+=1<<j;
       }
       for(int j=0;j<26;j++)if(st[i][j]==0)cf[j][a]++;
   }
   for(int tr=0;tr<26;tr++)
   {
       for(auto &i:cf[tr])
       {
           if(i.second==0)continue;
           int cnt=((~i.first)^cgh)-mi[tr];
           if(cf[tr].find(cnt)==cf[tr].end())continue;
           sum+=1ll*(i.second)*cf[tr][cnt];
           i.second=0,cf[tr][cnt]=0;
       }
   }
   cout<<sum<<endl;
}

反思

根据这个题的调试过程得到的反思,关于map 和unordered_map的使用,可以不用看(未完成)