Loading

AtCoder Beginner Contest 299




A - Treasure Chest

题目大意

给定一个由' | ' ' * '和' . '组成的字符串, 并且保证一定有1个' * '和2个' | ', 检查' * '是否在两个' | '之间;

解题思路

签到题不多嗦了;
但是这里可以注意一下string的find函数; find(char c, int pos)意为从第pos个字符开始找字符c, 返回值是int, pos可以不写, 默认从开头开始找; 而这里我们用到了两个拓展的find函数: find_first_of(char c)和find_last_of(char c), 意为字符c第一次出现的位置和最后一次出现的位置;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5+10;
int n, m,idx;
signed main() {
    string s;
    cin >> n >> s;
    auto a = s.find_first_of('|');
    auto b = s.find_last_of('|');
    auto c = s.find('*');
    if (c >= a && c <= b) {
        cout << "in";
    }
    else cout << "out";
    return 0;
}




B - Trick Taking

题目大意

有n个人玩游戏, 每个人都有各自的颜色和序号; 现在给定一个颜色m, 如果有人的颜色也是m, 那么赢家就是这些人里面序号最大的; 如果没有人的颜色是m, 那么赢家就是与1号玩家颜色相同的玩家中序号最大的, 注意1号玩家也有可能是赢家

解题思路

签到题不多嗦了;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5+10;
int n, m,idx;
vector<int> v;
int r[N], c[N];
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
        if (c[i] == m) v.push_back(i);
    }
    for (int i = 1; i <= n; i++) cin >> r[i];
    int maxn = 0;
    if (v.size()) {
        for (int x : v) {
            if (r[x] > r[maxn]) maxn = x;
        }
        cout << maxn;
    }
    else {
        for (int i = 1; i <= n; i++) {
            if (c[i] == c[1]) {
                if (r[i] > r[maxn]) maxn = i;
            }
        }
        cout << maxn;
    }
}




C - Dango

题目大意

给定一个只由' o '和' - '组成的字符串, 先定义一种字符串s, s的开头或结尾其中一个必须是' - ', 并且s的长度取决于' o '的个数, 例如" oooo- "的长度为4; 现在从给定的字符串里面找到符合字符串s的要求的子串中最长的长度;

解题思路

以' - '为节点作为结算即可; 算是个签到题;

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5+10;
int n, m,idx;
signed main() {
    cin >> n;
    string s;
    cin >> s;
    int maxn = 0;
    bool f = false;
    for (int i = 0; i < n; i++) {
        if (s[i] == 'o') idx++;
        else {
            f = true;
            maxn = max(maxn, idx);
            idx = 0;
        }
    }
    maxn = max(maxn, idx);
    if (maxn == 0) f = false;
    if (f) cout << maxn;
    else cout <<-1;
}




D - Find by Query

题目大意

本题是一个交互题, 现有一个由01组成长度为n的字符串, 并且s1=0, sn=1; 现在可以最多给出20次询问, 输出" ? x ", x是1~n中的一个, 然后会给出sx的值; 现在需要我们找出一个位置p, 满足sp不等于s(p+1);

解题思路

这还是第一次遇到交互题, 看了看题解发现交互题就是你按规定样式输出后, 网站会根据你的输出, 把对应结果输入到缓冲区, 此时我们用直接用cin输入后就可以得到想要的答案;
这个题是一个二分题, 因为开头是0, 结尾是1, 所以我们先询问一个位置x, 如果为1; 则在1~x-1中一定有一个位置p使得sp=0, s(p+1)=1; 因此我们让l作为p, r作为p + 1, 修改一下二分停止的条件, 当r = l + 1时就可以停止了; 因为n是1e5级别的, 所以20次一定能找到最后答案;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, mod = 998244353;
int n, m, k, res;
signed main(){
    cin >> n;
    int l = 1, r = n;
    while(l + 1 < r){
        int mid = l + r + 1>> 1;
        cout << "? " << mid << endl;
        cin >> m;
        if(m == 1) r = mid;
        else l = mid;
    }
    cout << "! " << l;
    return 0;
}




E - Nearest Black Vertex

题目大意

给定一个无向图, 有n个点和m条边; 现在我们需要对其中的点进行黑白两个颜色的染色; 规则如下: 一是至少有一个黑点; 二是题目会给出k组限制, 每组限制包括一个点a和一个距离b, 意为距离a最近的黑点与a之间的距离必须为b; 输出形式以01序列表示, 0表示白点, 1表示黑点;

解题思路

因为n只有2000, 所以可以考虑用bfs; 对于每次限制点a和距离d, 我们都可以用一次bfs, 距离小于d的点染成白色, 等于d的的点染成黑色; 规定可以用白色覆盖黑色, 但是不能用黑色覆盖白色; 在染完后对于每个a, 我们都要检查距离他为d的点中至少有一个还是黑色;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e3 + 10, mod = 998244353;
int n, m, k, res;
vector<int> v[N], ch[N];
vector<int> node;
int f[N], p[N], d[N];
bool st[N];
void bfs(int u){
    memset(st, false, sizeof st);
    queue<PII> q;
    q.push({u, 0});
    st[u] = true;
    while(q.size()){
        PII t = q.front();
        q.pop();
        int dis = t.second + 1;
        for(int x : v[t.first]){
            if(st[x]) continue;
            st[x] = true;
            if(dis == f[u]){
                if(!p[x]) p[x] = 1;
                ch[u].push_back(x);
            }
            else if(dis < f[u]){
                p[x] = 2;
                q.push({x, dis});
            }
        }
    }
}
signed main(){
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        int a, b;
        cin >> a >> b;
        v[a].push_back(b);
        v[b].push_back(a);
    }
    cin >> k;
    for(int i = 1; i <= k; i++){
        int a , b;
        cin >> a >> b;
        f[a] = b;
        node.push_back(a);
    }
    for(int x : node){
        if(f[x] == 0){
            if(!p[x]) p[x] = 1;
            ch[x].push_back(x);
            continue;
        }
        p[x] = 2;
        bfs(x);
    }
    bool f = true;
    for(int x : node){
        bool f1 = false;
        for(int y : ch[x]){
            if(p[y] == 1){
                f1 = true;
                break;
            }
        }  
        if(!f1){
            f = false;
            break;
        }
    }
    if(f){
        cout << "Yes" << endl;
        for(int i = 1; i <= n; i++){
            if(p[i] == 2) cout << 0;
            else cout << 1  ;
        }
    }
    else cout << "No";
    return 0;
}




F - Square Subsequence

难度: ⭐⭐⭐⭐⭐

题目大意

给定一个字符串S, 问有多少种字符串T, 使得字符串TT(两个T拼起来)是字符串S的一个子序列; 注意是子序列不是子串, 不要求连续;

解题思路

很难的一道dp; 要做的第一件事是要考虑去重, 样例zzz的输出是1, 只有"z"一种; 为了去重, 对于S种连续相同的字符c, 我们只取当前最靠前的; eg: aabc ddd中字符串ab的下个字符如果是d就只能是第一个d, aabcd dd, 如果字符串abd的下个字符还是d就只能是第二个d; 所以我们可以用一个数组next[i][c]表示从第i个位置往后字符c第一次出现的位置;
做完预处理后开始考虑dp; 状态表示为dp[i][j]表示第一个T的最后一个字符的位置是i,第二个T的最后一个字符的位置是j时有多少种满足要求的字符串; 状态转移可以利用前面预处理中的next数组: dp[next[i][c]][next[j][c]] += dp[i][j]; 转移方程也不难理解, 就是在原本在i和j处结束的T1和T2分别又往后延申到了各自区间中第一个字符c的位置;
因为T2不知道起点,但是T1的起点一定是1, 所以我们可以先遍历T1的终点ed; 则T1的区间就是[1,ed], T2为[ed+1,n]; 然后我们进行初始化, 也就是找T1和T2可能的起点, 也就是T的长度为1的情况; 遍历26个字母i, 如果next[0][i] <= ed并且next[ed][i] <= n说明当前字符i满足, 于是就可以把dp[next[0][i]][next[ed][i]]初始化为1;
随后我们在两个区间中找可以延申的方案, 设i是T1当前最后字符的位置, j是T2; 如果dp[i][j]不为0, 说明存在这样的一个T, 然后我们可以看看是否可以把它延申, 方法和初始化一样, 遍历26个字母, 如果得到的位置都在范围内就进行状态转移即可;
因为我们最外面的循环时遍历T1的结束位置ed, 所以该循环汇总的时候res要加上dp[ed][i], i是T2的结束位置, 遍历ed+1到n即可;
呼~ 太不容易了...

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
int dp[N][N], nex[N][26];
signed main() {
    string s;
    cin >> s;
    n = s.size();
    s = ' ' + s;
    for (int i = 0; i < 26; i++) nex[n][i] = n + 1;
    for (int i = n - 1; i >= 0; i--) {
        for (int j = 0; j < 26; j++) {
            nex[i][j] = nex[i + 1][j];
        }
        nex[i][s[i + 1] - 'a'] = i + 1;
    }
    for (int ed = 1; ed <= n; ed++) {
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i < 26; i++) {//找当前区间中所有合法的起点
            if (nex[0][i] <= ed && nex[ed][i] <= n)
                dp[nex[0][i]][nex[ed][i]] = 1;
        }
        for (int i = 1; i <= ed; i++) {
            for (int j = ed + 1; j <= n; j++) {
                if(!dp[i][j]) continue;
                for (int k = 0; k < 26; k++) {
                    int l = nex[i][k], r = nex[j][k];
                    if (l <= ed && r <= n) {
                        dp[l][r] = (dp[l][r] + dp[i][j]) % mod;
                    }
                }
            }
        }
        for (int i = ed + 1; i <= n; i++){
            res = (res + dp[ed][i]) % mod;
        }
    }
    cout << res;
    return 0;
}
posted @ 2023-06-24 15:38  mostimali  阅读(40)  评论(0)    收藏  举报