第十八届浙大城市学院程序设计竞赛(同步赛)赛后补题(F、G、I)

比赛传送门

官方题解传送门

F-Palindrome

解题思路:

  本题需要我们删除字符串中的两个字符,看是否能够将现有的字符串修改成回文串。
  那么对于字符串来说,我们先不考虑删除次数的限制,我们要想把一个字符串变成一个回文串,我们需要的是每次删除首次出现不同的位置,于是我们通过枚举第一个出现不同字符的位置,虽然将其删除后的字符串继续删除首个出现不同的位置。
  对于本题来说,我们只需要枚举两次这样的不同位置即可,将操作次数限制到\(2\)次即可。

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

using namespace std;

int k;
string str;

bool dfs(int l,int r,int k)
{
    if(l>r) return true;
    if(str[l]!=str[r])
    {
        if(k==0) return false;
        else if(k==1) return dfs(l+1,r,k-1)||dfs(l,r-1,k-1);
        else return dfs(l+1,r,k-1)||dfs(l,r-1,k-1)||dfs(l+1,r-1,k-2);
    }
    return dfs(l+1,r-1,k);
    
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int t;
    cin>>t;
    while(t--)
    {
        cin>>k>>str;
        if(dfs(0,str.size()-1,2)) puts("Yes");
        else puts("No");
    }
    return 0;
}

G-Permutation

解题思路:

  对于本题来说,我们进行操作一和二的先后顺序相互之间是没有影响的,在知道解决办法的情况下,需要进行操作二的时候我们完全可以提前知道该修改哪些值。于是我们可以假设我们先进行操作一,再进行操作二。
  假设我们的操作一进行了\(k\)次,那么问题就可以转化成,在\(a\)的后缀\(a[1+k,n]\)\(b\)\(b[1,n-k]\)中有多少个位置(相对位置,比如\(a[1+k]\)\(b[1]\))上的数是不一样的。即我们接下来需要进行操作二的次数、枚举\(k\),问题就可以转化成所有等长的\(a\)后缀和\(b\)前缀相对位置上不同的个数。
  这里假设\(a[i]=b[j]=x\),当且仅当相对位置相同,即\(i-(k+1)=j-1\)时,才能让我们少进行一次操作二。本算法的时间复杂度为\(O(N)\)

代码与解释

  第一个\(for\)循环我们记录\(a\)数组中各数值的下标,得到\(pos\)数组,\(cnt[i]\)表示移动\(i\)次后\(a[1+i,n]\)\(b[1,n-i]\)中有多少个相对位置相同的数,第二个for循环我们去找\(b[i]\)这个值在\(a\)数组中的位置,如果\(pos[b[i]]\)\(i\)要小,那么说明\(a[pos[b[i]]]\)这个数就没法前移,只有当\(pos[b[i]]\ge i\)的时候,\(a[pos[b[i]]]\)才能前移到当前位置,表示与\(b[i]\)相对位置相同,然后我们通过\(cnt[pos[b[i]]-i]\)的方式表明移动\(pos[b[i]]-i\)次,\(a,b\)中有多少个相对位置相同的数。我们用\(n-i-cnt[i]\)表示\(i+1\sim n\)中需要修改的个数,然后我们再加上移动的次数\(i\),即\(n-i-cnt[i]+i\),化简得到\(n-cnt[i]\),然后取最小值即可。

#include<iostream>
#include<cstdio>
#include<cmath>

using namespace std;

const int N=1e6+10;

int n;
int a[N],b[N],cnt[N],pos[N];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        pos[a[i]]=i;
        cnt[i]=0;
    }
    
    for(int i=0;i<n;i++)
    {
        scanf("%d",&b[i]);
        if(pos[b[i]]>=i)
        {
            cnt[pos[b[i]]-i]++;
        }
    }
    
    int res=0;
    for(int i=0;i<n;i++)
    {
        res=max(res,cnt[i]);
    }
    printf("%d\n",n-res);
    
    return 0;
}

I-String

解题思路:

  本题我们需要进行三种操作,分别是
    1. 修改\(a\)中某个字符
    2. 修改\(a\)中某段区间,使其字典序右移
    3. 计算\(a\)\(b\)的最长公共前缀
  由于题目中给出的都是小写的英文字符,因此我们可以将字符串看成是由\(0-25\)的数字组成的数组。我们可以根据这一点,对于\(a\)\(b\)的差值对\(26\)取模后的值来建立一个线段树,线段树中的每个节点维护区间内不同值的个数。操作\(2\)可以通过对值\(+1\)再对\(26\)取模后得到。如果\(a,b\)的前缀相同,那么节点中差值为\(0\)的个数应该要等于长度,因此我们可以进行树上的二分,找到对应下标即公共前缀的最大长度。本算法的时间复杂度为\(O(N*logN*26)\)

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

using namespace std;

const int N=1e6+10;
const int M=N*4;//线段树开四倍空间

int n,m;
char a[N],b[N];
int tmp[26];//用来存储移动前的数值数组

struct segtree
{
    int cnt[M][26],lz[M];//cnt用来记录当前节点各数值的个数,lz[x]表示当前节点的懒标记
    
    void pushup(int x)
    {
        for(int i=0;i<26;i++)
             cnt[x][i]=cnt[x<<1][i]+cnt[x<<1|1][i];
        return;
    }
    
    void build(int x,int lx,int rx)
    {
        if(rx==lx)
        {
            cnt[x][(a[lx]-b[lx]+26)%26]=1;//取模操作
            return;
        }
        int mid=lx+rx>>1;
        build(x<<1,lx,mid);
        build(x<<1|1,mid+1,rx);
        pushup(x);//用来向上更新
    }
    //设置懒标记
    void setLazy(int x)
    {
        lz[x]++;
        for(int i=0;i<26;i++) tmp[i]=cnt[x][i];
        for(int i=0;i<26;i++) cnt[x][(i+1)%26]=tmp[i];
        return;
    }
    
    void pushdown(int x)
    {
        if(!lz[x]) return;//如果没有变化就不需要向下更新了
        int mov=lz[x];//记下需要右移的次数
        lz[x]=0;//记得清空
        //对左子树的操作
        lz[x<<1]+=mov;
        for(int i=0;i<26;i++) tmp[i]=cnt[x<<1][i];
        for(int i=0;i<26;i++) cnt[x<<1][(i+mov)%26]=tmp[i];
        //对右子树的操作
        lz[x<<1|1]+=mov;
        for(int i=0;i<26;i++) tmp[i]=cnt[x<<1|1][i];
        for(int i=0;i<26;i++) cnt[x<<1|1][(i+mov)%26]=tmp[i];
        return;
    }
    //单点修改
    void set(int i,int v,int x,int lx,int rx)
    {
        if(rx==lx)//到叶节点了
        {
            for(int i=0;i<26;i++) cnt[x][i]=0;
            cnt[x][v]++;
            return;
        }
        pushdown(x);//要先将要经过的节点的懒标记向下传
        int mid=lx+rx>>1;
        if(i<=mid) set(i,v,x<<1,lx,mid);
        else set(i,v,x<<1|1,mid+1,rx);
        pushup(x);//最后要向上去更新
    }
    //区间修改
    void modify(int l,int r,int x,int lx,int rx)
    {
        if(lx>=l&&rx<=r)
        {
            setLazy(x);//设置一下懒标记
            return;
        }
        pushdown(x);//当然,这里也要先把懒标记向下传
        int mid=lx+rx>>1;
        if(l<=mid) modify(l, r, x<<1,lx, mid);
        if(r>mid) modify(l,r,x<<1|1,mid+1,rx);
        pushup(x);
    }
    //查询操作,树上二分
    int query(int x,int lx,int rx)
    {
        if(rx==lx)
        {
            if(cnt[x][0]==1) return lx;//说明这个下标的a与b字符相同
            else return lx-1;
        }
        pushdown(x);
        int mid=lx+rx>>1;
        if(cnt[x<<1][0]==mid-lx+1) return query(x<<1|1, mid+1, rx);
        else return query(x<<1,lx,mid);
    }
    
}st;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin>>n>>m>>a+1>>b+1;
    st.build(1,1,n);
    
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int pos;
            char ch[2];
            cin>>pos>>ch;
            st.set(pos, (ch[0]-b[pos]+26)%26, 1, 1, n);
        }
        else if(op==2)
        {
            int l,r;
            cin>>l>>r;
            st.modify(l, r, 1, 1, n);
        }
        else 
        {
            cout<<st.query(1, 1, n)<<endl;
        }
    }
    
}
posted @ 2021-03-31 00:43  Daneii  阅读(66)  评论(0)    收藏  举报