abc377

A. Rearranging ABC

  • 题意:求给定的字符串是不是 ABC

  • 思路:直接判断。

void solve(){
    cin>>s;

    for(auto x:s){
        S[x]++;
    }    

    if(S['A']==1&&S['B']==1&&S['C']==1){
        cout<<"Yes"<<endl;
    }else{
        cout<<"No"<<endl;
    }

}

B. Avoid Rook Attack

  • 题意:一个棋子在图中记为 *,跟它同一行或同一列不能放 *,问还有几个方格能放棋子。
  • 思路:因为 $n$ 很小,直接暴力枚举整张图,把能被覆盖到的标记,此时剩下的就是答案。
char s[10][10];
int n=8;
bool vis[10][10];

void solve(){
    vector<PII>t;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>s[i][j];
            if(s[i][j]=='#'){
                t.push_back({i,j});
            }
        }
    }

    for(auto [x,y]:t){
        for(int i=1;i<=n;i++){
            vis[x][i]=1;
            vis[i][y]=1;
        }
    }

    int ans=0;

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            ans+=(vis[i][j]==0);
        }
    }

    cout<<ans<<endl;
}

C. Avoid Knight Attack

  • 题意:一个棋子可以像中国象棋的 “马” 那样走 “日” 覆盖,问图中没有被棋子覆盖的地方的方格数。

  • 思路:直接用 set 去维护每个棋子能覆盖到的点,因为 set 自带去重功能,最后直接把棋盘大小减去 set 的元素的个数就是答案。

int n,m;
int dx[]={1,2,2,1,-1,-2,-2,-1};
int dy[]={2,1,-1,-2,-2,-1,1,2};

void solve(){
    cin>>n>>m;

    set<PII>S;

    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        for(int j=0;j<8;j++){
            int x=a+dx[j],y=b+dy[j];
            if(x<1||y<1||x>n||y>n)continue;
            S.insert({x,y});
        }
        S.insert({a,b});
    }

    cout<<((long long)n*n-(long long)S.size())<<endl;

}

D. Many Segments 2

  • 题意:给定 $n$ 个区间,求出区间 $[l,r]$ 不完全包含区间 $[L_i,R_i]$ 的区间个数。

  • 思路:因为数据规模比较大,对于这种求个数的问题可以考虑贡献法。然后我们又发现了:如果固定 $l$ 的话,它的答案是取决于所有 $L_i>=l$ 的区间中 $R_i$ 最小的。此时我们用双指针来做,此时从大到小枚举 $l$ 的时候(为什么从大到小,因为你更新最小值的时候肯定是从大到小来维护的),$L_i>=l$ 的区间会逐渐变多,此时统计右端点即可,然后每次更新一下最小值即可。

void solve() {
    cin>>n>>m;

    FOR(i,1,n){
        int x,y;
        cin>>x>>y;
        w[i]={x,y};
    }

    sort(w+1,w+1+n);

    int lst=m+1;
    int ans=0;
    int j=n;

    FORD(i,1,m){
        while(j>=1&&w[j].fi>=i){
            lst=min(lst,w[j].se);
            j--;
        }
        ans+=(lst-i);
    }
    cout<<ans<<endl;

}

E. Permute K times 2

  • 题意:给定排列,然后每次操作将 $P_i$ 更新为 $P_{P_i}$,问第 $k$ 次操作 $P$ 的结果。

  • 思路:对于这种题,可以考虑结合图来做,也就是画图。通过画图,可以很容易知道最后肯定会形成一个环,具体字怎么画呢?就是将 $P_i$ 与 $P_{P_i}$ 连边。

  • 通过手算可以知道:它的第一个数在 $5$ 次操作的值是 $5,2,4,6,5$,解释一下就是:操作一次从 $5\rightarrow 2$,也就是走一步;然后第二次操作从 $2\rightarrow 4$,在图上走两步。以此类推,若要一个数按题目要求变换 $k$ 次,那么它的值将变为它向箭头方向走 $k-1$ 步的值。

void dfs(int u){
    vis[u]=1;
    a[cnt].push_back(u);
    for(auto j:g[u]){
        if(vis[j])continue;
        dfs(j);
    }
}

int qmi(int a,int b,int p){
    int res=1;
    while(b){
        if(b&1)res=res*a%p;
        a=a*a%p;
        b/=2;
    }
    return res;
}

void solve(){
    cin>>n>>k;

    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        g[i].emplace_back(x);
    }

    for(int i=1;i<=n;i++){
        if(!vis[i])cnt++,dfs(i);
    }

    for(int i=1;i<=cnt;i++){
        int s=qmi(2,k,(int)a[i].size());
        for(int j=0;j<a[i].size();j++){
            w[a[i][j]]=a[i][(j+s)%(int)a[i].size()];
        }
    }

    for(int i=1;i<=n;i++)cout<<w[i]<<' ';

}

F. Avoid Queen Attack

  • 题意:对于一个棋子,它可以覆盖的范围类似 “米” 字形。给定一张图,问最后可以放棋子的格子数。

  • 思路:如果是十字的话还好做,但这道题有对角线,因此对于对角线,我们可以取个巧,就是让 $x+y$ 作为 / 形对角线,让 $x-y$ 最为 \ 形对角线,那么对于 / 对角线的取值范围就是 $[n+1,2n]$,对于 \ 对角线的取值范围就是 $[0,n-1]$ 。此时我们可以先去让 \ 形对角线满足条件,因为 / \ 会有交点。这样的话我们可以先把十字形的先去掉,也就是先把十字形覆盖的格数算出来,从剩下的个数进行对角线的选择。然后根据 * 的横坐标、纵坐标和 \ 形对角线,此时要看 \ 形对角线能覆盖到哪里,可以算:(比如现在是已知横坐标和对角线去求 $y$ 就是 $d=x-y$ => $y=x-d$,然后最后的答案假如对于某个 \ 对角线,是把对角线长度 $n-d$ 减去被对角线覆盖到的格子数(已经被覆盖到的格子数))。当开始枚举另一边的时候得注意:中间重复部分,此时可以列出方程 $a=x-y,b=x+y$ 解得:$x=(a+b)/2,y=(b-a)/2$,注意 $x,y$ 均为整数,如果能整除的话说明有交点,反之没有。

void solve(){
    cin>>n>>m;

    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        ht.insert(a);
        vt.insert(b);
        zhu.insert(a-b);
        fu.insert(a+b);
    }

    int ans=(n-ht.size())*(n-vt.size());

    for(auto d:zhu){
        set<int>cs;
        for(auto x:ht){
            int y=x-d;
            if(y>=1&&y<=n){
                cs.insert(x);
            }
        }

        for(auto y:vt){
            int x=d+y;
            if(x>=1&&x<=n){
                cs.insert(x);
            }
        }
        int num=cs.size();
        int len_d=n-abs(d);
        ans-=(len_d-num);
    }

    for(auto e:fu){
        set<int>cs;
        for(auto x:ht){
            int y=e-x;
            if(y>=1&&y<=n){
                cs.insert(x);
            }
        }

        for(auto y:vt){
            int x=e-y;
            if(x>=1&&x<=n){
                cs.insert(x);
            }
        }

        for(auto d:zhu){
            int x=(d+e)/2;
            int y=(e-d)/2;
            if((e+d)%2==0&&x>=1&&x<=n&&y>=1&&y<=n){
                cs.insert(x);
            }
        }

        int num=cs.size();
        int len_d=n-abs(e-(n+1));
        ans-=(len_d-num);
    }

    cout<<ans<<endl;

}

G. Edit to Match

  • 题意:给你 $n$ 个字符串,记 $s_0$ 为空,对于每个字符串 $i\in[1,n]$,求通过删除/添加后缀字母的操作,最少需要操作多少次使得 $s_i=s_k,k\in[0,i-1]$。

  • 思路:这就是很典型的字符串匹配,对于字符串的前缀匹配,可以考虑字典树。将每个数插入到类似于 trie 树的东西上,再用 $en_i$ 数组记录离这个节点最近的结尾字符,代价即为:$en_p+n-i+1$,取最小值即可。其中 $en_i$ 就是 cnt 数组。

void insert(){
    int p=0,n=s.size();
    ans=n;
    for(int i=0;i<n;i++){
        int u=s[i]-'a';
        if(tr[p][u]==0)tr[p][u]=++idx;
        p=tr[p][u];
        ans=min(ans,cnt[p]+n-i-1);
        cnt[p]=min(cnt[p],n-i-1);
    }
    cout<<ans<<endl;
}

void solve(){
    cin>>n;
    memset(cnt,0x3f,sizeof cnt);
    cnt[0]=0;
    for(int i=1;i<=n;i++){
        cin>>s;
        insert();
    }
}
posted @ 2025-03-13 00:08  shuying6  阅读(30)  评论(0)    收藏  举报