首页 > 其他分享 >Codeforces Round #848 (Div. 2)(B,C,D)

Codeforces Round #848 (Div. 2)(B,C,D)

时间:2023-02-07 22:34:12浏览次数:64  
标签:字符 int 位置 pos tot Codeforces Div 848 我们

Codeforces Round #848 (Div. 2)(B,C,D)

哎呀,最近不知道怎么回事,题目老是没看懂,还是心不在此,太烦了,好几天都只是一道题了

B

B

这道题到看答案才发现我理解错了,我怎么说越想越复杂,我就说第二道题怎么会这么复杂

其实题目的意思是要求我们每一个都满足条件才是\(no good\),我以为是只有一个就是\(no good\),看得不仔细

现在我看完了题解,发现我的题都读错了,真离谱

这道题大意是

给你一个长度为\(n\)的排序,还有一个长度为\(m\)的数组,还有一个\(d\),我们对于这个数组是\(no good\)有以下的条件

即 对于\(1\)到\(m-1\)都需要满足\(pos(a_i)<pos(a_{i+1})<=pos(a_i)+d\)

我们要求的是要这个数组变成\(good\),的最小移动数量

移动是什么?

移动可以改变排序\(p\)里面相邻的两个数(注意是改变\(p\)里面的数的位置,而不是\(a\)里面的数的位置)

所以我们只有一个不满足以上\(no good\)的条件,那么这个就是\(good\)的,那么我们不需要移动,所以答案就是\(0\)

如果每一个都满足\(no good\)的条件,那么我们要怎么让这一个数不满足$ no good$的条件

对于此时\(pos(a_i)<pos(a_{i+1})<=pos(a_i)+d\),我们要让这一对不满足这个条件,有两种移动方法

第一种,让\(pos(a_i)>pos(a_{i+1})\),此时,我们需要移动的是把\(a_i\)和\(a_{i+1}\)的位置转换一下

第二种,是让\(pos(a_{i+1})>pos(a_i)+d\),我们要让这两个数的位置差大于\(d\),我们还要考虑我们可不可以存在这两个数之间的距离大于\(d\),我们知道对于长度为\(n\)的排序,两个数之间的最大差为\(n-1\),所以,要想进行第二种移动,我们要满足\(n-1>d\)

然后我们取较小的那一个移动次数

#include <iostream>
#include <string>
using namespace std;
const int maxn=1e5+10;
int t,n,d,m;
int pos[maxn],k;
int a[maxn];
void solve()
{
    cin>>n>>m>>d;
    for (int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        pos[x]=i;
    }
    int ans=1e9;
    for (int i=1;i<=m;i++)
    {
        cin>>a[i];
    }
    for (int i=1;i<m;i++)
    {
        if (pos[a[i]]<pos[a[i+1]]&&pos[a[i+1]]<=pos[a[i]]+d)
        {
             int c1=pos[a[i+1]]-pos[a[i]];//交换两个位置,让pos[a[i]]>pos[a[i+1]]
             int c2=1e9;
             if (d<n-1)//n-1是这两个数的最远距离,n-1,只有两点距离大于d才可以满足这第二个情况,pos[a[i+1]]>pos[a[i]]+d,那么这个d只能小于n-1才可
             {
                c2=pos[a[i]]+d+1-pos[a[i+1]];
             }
            int c=min(c1,c2);
            ans=min(ans,c);
        }
        else 
        {
            cout<<0<<'\n';
            return ;
        }
    }
    cout<<ans<<'\n';
    return ;
}
signed main ()
{
    cin>>t;
    while (t--)
    {
        solve();
    }
    system ("pause");
    return 0;
}

C

C

这个题大意是给你两个字符串\(a\)和\(b\),我们需要尽量的让\(l,r\)的对数尽量多

\(l,r\)需要满足\(a\)的\(l\)和\(r\)这一个区间的字符和\(b\)同样位置的字符一样的

我们可以实行任意次操作

把\(a\)的\(pos\)位置的\(x\)变成\(y\),把\(x\)加进去一个集合,我们要保证这个集合里面的字符最多有\(k\)种,也就说说对于每次改变,我们一定要改变\(k\)个位置\(pos\),但是我们要保证这\(k\)个位置的字符的种类不可以大于\(k\)种

对于这个限制,我们首先可以把所有需要改变的字符\(a_i\)不等于\(b_i\)的\(a_i\)的种类记下来,\(cnt\)记录下来

然后我们可以看到题目中那个加粗字,\(a\)和\(b\)的不同字符种类不会超过\(10\)个,那么对于这些对于我们可以取的数量\(min(cnt,k)\),我们可以用二进制的形式把状态压缩,列举每一个状态

每一个状态都代表着其中的一类字符的改变还是保留的情况

对于这一类的字符,如果需要是改变成\(b\)里面的那一个字符,那么所有需要改变的都要改变,只有这样,才可以让\(l,r\)的数量最大

那么怎么求\(l,r\)的数量

对于如果有一段长度为\(tot\)的字符和\(b\)是一样的,那么我们可以有\(tot*(tot+1)/2\)种不同的\(l,r\),这里面的不同\(l,r\),里面的\(l\)都是这一段字符的第一个字符的位置,\(r\)就是后面的\(l,l+1,l+2,...,l+tot-1\),有\(tot\)个,对于\(l\)取这一段字符的第二个位置,\(r\)取值可为\(l+1,l+2,...l+tot-1\),有\(cnt-1\)个,由此可以得出

对于不同的\(l\),有\(tot,tot-1,tot-2...1\)个,那么这个数量和我们可以用前缀和求

然后对于不同的状态,我们选取那个可以得到最大的对数的那一个状态

#include <iostream>
#include <string>
#include <vector>
using namespace std;
#define int long long 
int t,n,k;
string a,b;
void solve()
{
    cin>>n>>k;
    cin>>a>>b;
    vector<int>vis(30,0);
    vector<int>code(30,0);
    int cnt=0;
    for (int i=0;i<n;i++)
    {
        vis[a[i]-'a']=1;
    }
    for (int i=0;i<26;i++)
    {
        if (vis[i])
        {
            code[cnt++]=i;
        }
    }
    k=min(k,cnt);
    int M=1<<cnt;
    int ans=0;
    for (int i=0;i<M;i++)
    {
        vector<bool>use(26,false);
        int tot=0;
        for (int j=0;j<cnt;j++)
        {
            if ((i>>j)&1)
            {
                use[code[j]]=true;
                tot++;
            }
        }
        if (tot==k)
        {
            string tmp=a;
            for (int j=0;j<n;j++)
            {
                if (use[tmp[j]-'a'])
                {
                    tmp[j]=b[j];
                }
            }
            int tot=0;
            int sum=0;
            for (int j=0;j<n;j++)
            {
                if (tmp[j]==b[j])
                {
                    tot++;
                }
                else 
                {
                    sum+=tot*(tot+1ll)/2;
                    tot=0;
                }
                if (j==n-1)
                {
                    sum+=tot*(tot+1ll)/2;
                }
            }
            ans=max(ans,sum);
        }
    }
    cout<<ans<<'\n';
    return ;
}
signed main ()
{
    cin>>t;
    while (t--)
    {
        solve();
    }
    system ("pause");
    return 0;
}

D

D

这个题大意是我们有两种\(01\)字符串,我们最后要把\(a\)字符变成\(b\)字符,对于每一个位置的,都有平等的概率可以把\(a\)里面的这一个位置的字符翻转,\(0\)变成\(1\),\(1\)变成\(0\)

问最后把\(a\)变成\(b\)的概率

我们可以定义\(f[i]\)为此时由\(i-1\)个相同的位置,变成\(i\)个相同的位置的概率

状态转移是怎么转移的

\[f[i+1]=\frac{n-i}{n}+\frac{i}{n}\times(1+f[i]+f[i+1]) \]

这个是怎么来的呢

对于此时已经有\(i\)个相同的位置,

如果这一次我们直接选择那些不相同的位置进行翻转,那么已经有\(i+1\)个相同的位置

如果这一次我们选择那些相同的位置的字符翻转,那么此时相同的位置就变成了了\(i-1\)个,那么我们要把\(i-1\)变成\(i\)个,这一步的概率应该为\(f[i]\),然后再又要把此时已经有的\(i\)个相同位置变成\(i+1\)个相同位置,这一步的概率为\(f[i+1]\)

但是我们最好是把未知数放在同一边,所以我们可以在纸上进行变换

可以得到以下这个方程

\[f[i+1]=(1+\frac{i}{n-i})\times(f[i]+1) \]

然后我们从原来有\(cnt\)个相同的变成\(n\)个相同的,需要在这一系列的变化的概率相加

#include <iostream>
#include <string>
using namespace std;
#define int long long 
const int mod=998244353;
const int maxn=1e6+10;
int t,n;
string a,b;
int f[maxn];
int ksm(int x,int p)
{
    int res=1;
    while (p)
    {
        if (p&1)
        {
            res=(res*x)%mod;
        }
        x=(x*x)%mod;
        p>>=1;
    }
    return res%mod;
}
int inv(int x)
{
    return ksm(x,mod-2);
}
void solve()
{
    cin>>n;
    cin>>a>>b;
    int cnt=0;
    for (int i=0;i<n;i++)
    {
        if (a[i]==b[i])
        {
            cnt++;
        }
    }
    f[1]=1;
    for (int i=1;i<n;i++)
    {
        f[i+1]=(1+i*inv(n-i)%mod*(f[i]+1))%mod;
    }
    int ans=0;
    for (int i=cnt+1;i<=n;i++)
    {
        ans=(ans+f[i])%mod;
    }
    ans%=mod;
    cout<<ans<<'\n';
    return ;
}
signed main ()
{
    cin>>t;
    while (t--)
    {
        solve();
    }
    system ("pause");
    return 0;
}

标签:字符,int,位置,pos,tot,Codeforces,Div,848,我们
From: https://www.cnblogs.com/righting/p/17100040.html

相关文章