AtCoder Beginner Contest 345

文章讲述了两个编程问题:C-OneTimeSwap要求计算交换字符串中任意两个字符后可能得到的不同字符串数量,利用字符计数和排除重复的方法;D-Tiling讨论了如何使用瓷砖覆盖网格,通过暴力搜索和深度优先搜索策略进行判定。
摘要由CSDN通过智能技术生成

前面两道阅读理解直接跳过。

C - One Time Swap

大意

给定一个字符串,问交换任意两个字符,可以得到的不同字符串个数。

思路

N=|S|S(i,j)S的第i位和第j交换后得到的字符串。

考虑(i,j)\ne(i',j') ,但S(i,j)S(i',j')重合的情况。

  • 如果是S_i\ne S_j,那么SS(i,j)只有 第i个字符和第j个字符不同。只有S(i,j)满足这一属性,并且是由操作产生的。
  • 另一方面,如果S_i=S_j,那么SS(i,j)相同。因此,对于S_{i'}=S_{j'}的所有i'j' ,它都与 S(i',j')重合

后者可以通过简单扫描S来验证。

对于前者,我们不能枚举,否则O(n^2)超时

但我们可以统计S中每个字符的出现次数,设C_iS中第i个小写字母的出现次数,那么总共有$C_1C_2+C_1C_3+\cdots+C_1C_{26}+C_2C_3+\cdots+C_2C_{26}+C_3C_4+\cdots+C_{25}C_{26}$对。

可以直接计算,但我们也可以减去S_i 和S_j相同字符的对,即\frac{1}{2}(N^2-C_1^2-C_2^2-C_3^2-\cdots -C_{26}^2)

注意要开long long

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL cnt[26];
int main(){
    LL n, ans=0;
    bool flag = false;
    string s;
    cin >> s;
    n = s.size();
    for(int i = 0; i < n; i++) cnt[s[i] - 'a']++;
    ans = n * n;
    for(int i = 0; i < 26; i++){
        ans -= cnt[i] * cnt[i];
        if(cnt[i] > 1) flag = true;
    }
    ans /= 2;
    if(flag) ans++;
    cout << ans << endl;
    return 0;
}

D - Tiling

大意

一块地板,长h,宽w,有n块瓷砖可用,问是否可以选择若干块瓷砖,恰好铺满网格,瓷砖可旋转

思路

由于数据范围很小,我们考虑暴力搜索

C_{i,j}表示第$i$行第$j$个格子被哪一块瓷砖占用(没有被占用为-1)

为了表示哪些瓷砖可用,可以设二进制数t,第$i$位为1表示i块瓷砖可用

由于瓷砖可以旋转(可以竖着放,也可以横着放),因此搜索时,每块瓷砖有两种情况(除了正方形)。

为了方便编写,我从dfs中分离出两个函数

  • 第一个函数尝试放置瓷砖并判断是否可行
  • 第二个函数用于回溯操作(由于第一个函数会改变$C$,所以无论是否可行都要复原)。注意要判断C_{i,j}是不是当前瓷砖,否则出现重叠时,会影响其他正常放置的瓷砖。

代码

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n, h, w;
    cin >> n >> h >> w;
    vector<int> A(n), B(n);
    for(int i = 0; i < n; i++) cin >> A[i] >> B[i];

    vector<vector<int>> c(h, vector<int>(w, -1));
    bool ans = false;

    auto place_tile = [&](int x, int y, int id, bool f) -> bool {
        bool can = true;
        int a = A[id], b = B[id];
        if(f) swap(a, b);
        for(int i = x; i < x + a; i++)
            for(int j = y; j < y + b; j++){
                if(i < h && j < w){
                    if(c[i][j] == -1) c[i][j] = id;
                    else can = false;
                }else can = false;
            }
        return can;
    };

    auto do_backtrace = [&](int x, int y, int id, bool f){
        int a = A[id], b = B[id];
        if(f) swap(a, b);
        for(int i = x; i < x + a; i++)
            for(int j = y; j < y + b; j++)
                if(i < h && j < w && c[i][j] == id) c[i][j] = -1;
    };

    auto dfs = [&](auto self, int unused, int x, int y){
        while(c[x][y] >= 0){
            y++;
            if(y >= w) x++, y = 0;
            if(x >= h) break;
        }
        if(x >= h){
            ans = true;
            return;
        }

        for(int i = 0; i < n; i++){
            if(unused & (1 << i)){
                bool can = place_tile(x, y, i, false);
                if(can) self(self, unused ^ (1 << i), x, y);
                do_backtrace(x, y, i, false);
                if(A[i] != B[i]){
                    bool can = place_tile(x, y, i, true);
                    if(can) self(self, unused ^ (1 << i), x, y);
                    do_backtrace(x, y, i, true);
                }
            }
        }
    };

    dfs(dfs, (1 << n) - 1, 0, 0);
    cout << (ans? "Yes": "No") << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值