Loading

AtCoder Beginner Contest 315




B - The Middle Day

难度: ⭐

题目大意

在某颗星球上一年有n个月, 给定每个月的天数, 设一年的总天数是m, 请问第m/2(小数向上取整)天是第几个月的第几天;

解题思路

数据不大, 暴力即可;

神秘代码

#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 = 1e4 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
int f[N];
signed main(){
    cin >> n;
    int a, b;
    for (int i = 1; i <= n; i++) {
        cin >> f[i];
        f[i] += f[i - 1];
    }
    int mid = (f[n]+1) / 2;
    for (int i = 1; i <= n; i++) {
        if (f[i] >= mid) {
            b = i - 1;
            break;
        }
    }
    cout << b + 1 << ' '<<mid - f[b];
    return 0;
}




C - Flavors

难度: ⭐⭐

题目大意

给定n个卡片, 每个卡片有两个属性, 点数x和分数y; 我们要从中选出两张卡片, 如果这两张卡片的点数不同, 那么就获得y1+y2; 如果相同则获得y1+y2/2, 其中y1>=y2; 问可以获得的最大分数为多少;

解题思路

对于相同的点数我们可以只存其中最大的两个分数, 把所有点数按照各自的两个分数从大到小排序, 答案就是 取排序后第一个点数的两个分数, 或者取第一个和第二个点数各自最大分数, 取这两种情况的最大值;

神秘代码

#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 = 3e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
PII f[N];
signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int a, b;
        cin >> a >> b;
        if (b > f[a].first) {
            f[a].second = f[a].first;
            f[a].first = b;       
        }
        else if (b > f[a].second) {
            f[a].second = b;
        }
    }
    sort(f + 1, f+1 + n, greater<>());
    cout << max(f[1].first + f[1].second / 2, f[1].first + f[2].first);
    return 0;
}




D - Magical Cookies

难度: ⭐⭐⭐⭐

题目大意

给定一个由小写字母组成字符矩阵, 我们需要一直进行以下操作, 直到无法再操作;
先遍历所有的行, 如果某行所有字母均为同一种字母并且数量大于1个, 那么我们就可以标记这些字母;
再遍历所有的列, 如果某列所有字母均为同一种字母并且数量大于1个, 那么我们就可以标记这些字母;
最后我们把所有被标记字母从矩阵中去除;
问最后矩阵还剩下多少个字母;

解题思路

本题确实是没有什么好的算法, 所以只好考虑模拟; 那么我们要遍历所有字母, 并且操作完之后还要重复上述过程, 大致相当于O(n3)的复杂度, 这个是一定会超时的, 所以要考虑优化; 因为当我们删除一行时, 可能会由此出现新的满足条件的列, 所以最外层的n我们是很难优化的, 只能取考虑优化遍历; 我们遍历无非就是想看看这一行/列是否是由同一种字母组成, 那我们可以开一个数组st[i][j], 表示第i行中字母j的数量, 如果该字母的数量等于第i行的长度, 那么就说明是由同一种字母组成, 这样我们就可以把O(n2)变成O(26n);
和st[i][j]相对应的fst[i][j]表示第i列中字母j的数量; rs[i]表示当前第i行的长度为多少, cs[i]表示当前第i列的长度为多少; 首先我们先遍历行i, 如果有满足条件的字母j, 那么就可以让st[i][j]清零, 注意: 当我们把一行的字母j删除后, 那么每一列中字母j的数量都会减一, 并且所有的列的长度也会减一, 但是我们要在遍历完所有列之后才能更新, 如果遍历列之前更新, 那么一些原本满足条件的列可能就会变得不满足了, 所有我们可以先把该字母存起来; 为了增加效率我们还可以对第i行打个标记, 意思是这一行以及被删除了, 可以不用遍历了, 也方便后期找答案; 对于列也是一样的操作; 遍历完行和列后我们就可以开始更新了, 更新每一行/列中某个字母的数量, 更新行和列的长度; 最后我们遍历所有位置, 如果某个位置的行和列都没有被标记, 说明该位置的字母没有被删除, 计数即可;

神秘代码

#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 = 2e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
char g[N][N];
int st[N][N], fst[N][N];
int rs[N], cs[N];
bool ro[N], co[N];
signed main(){
    IOS;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> g[i][j];
            st[i][g[i][j] - 'a']++;
            fst[j][g[i][j] - 'a']++;
        }
    }
    for (int i = 1; i <= n; i++) rs[i] = m;
    for (int j = 1; j <= m; j++) cs[j] = n;
    while (1) {
        vector<int> rv, cv;
        bool qu = false;
        for (int i = 1; i <= n; i++) {
            if (ro[i]) continue;
            for (int j = 0; j < 26; j++) {
                if (st[i][j] == rs[i] && st[i][j] >= 2) {
                    qu = true;
                    ro[i] = true;
                    st[i][j] = 0;
                    rv.push_back(j);
                }
            }
        }
        for (int i = 1; i <= m; i++) {
            if (co[i]) continue;
            for (int j = 0;  j< 26; j++) {
                if (fst[i][j] == cs[i] && fst[i][j] >= 2) {
                    qu = true;
                    co[i] = true;
                    fst[i][j] = 0;
                    cv.push_back(j);
                }
            }
        }
        for (int x : rv) {
            for (int i = 1; i <= m; i++) {
                fst[i][x]--;
                cs[i]--;
            }
        }
        for (int x : cv) {
            for (int i = 1; i <= n; i++) {
                st[i][x]--;
                rs[i]--;
            }
        }
        if (!qu) {
            break;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (!ro[i] && !co[j]) {
                res++;
            }
           
        }
    }
    cout << res;
    return 0;
}




E - Prerequisites

难度: ⭐⭐⭐

题目大意

给定n本书, 编号为1~n, 并且每本书都有各自的前提书单, 我们需要把第i本书的书单上的书都看完才能去看第i本书, 问我们最少看多少本书才能看图书1; 按照阅读顺序先后输出书的编号, 不用输出1, 答案不唯一;

解题思路

类似于拓扑序列的思路, 我们可以把第i本书和它前提书单上的书建立一条边; 我们用数组st[i]表示第i本书是否以及被阅读, 因为要求按照阅读先后输出, 所以考虑用dfs, 我们从1开始dfs, 对于第u本书, 我们先遍历它前提清单上的所有书j, 如果j已经被阅读了就跳过, 否则就对j进行dfs; 这样我们就可以遍历完看图书1之前所有需要阅读的书了, 对于其中的每一本书, 我们只要遍历完它的前提书单就可以输出这本书了;

神秘代码

#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 = 2e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res, idx;
int C[N];
bool st[N];
vector<int> v;
int h[N], e[N], ne[N];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (st[j]) continue;
        dfs(j);
        st[j] = true;
    }
    if (u!=1) cout << u << ' ';
}
signed main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) {
        cin >> C[i];
        for (int j = 1; j <= C[i]; j++) {
            int x;
            cin >> x;
            add(i, x);
        }
    }
    dfs(1);
    return 0;
}




F - Shortcuts

难度: ⭐⭐⭐⭐

题目大意

在一个坐标系中有n个检查点, 小莫需要按顺序经过他们(小莫是按照两个点之间的最短距离走); 小莫可以选择跳过若干个除了第1个和第n个的检查点, 但是如果跳过了x个检查点, 最后会加上2x-1的额外距离惩罚; 问小莫从第1个检查点走到第n个检查点所需要的最短距离为多少;

解题思路

本题考虑用dp求解, 状态表示dp[i][j] 表示到达第i个检查点的过程中一共跳过了j个检查点; 在状态转移时我们首先要知道跳过的检查点个数j, 然后我们还需要知道第i个检查点的上一个经过的检查点h是第几个; 只要我们知道了h, 我们就知道从h到i一共跳过了多少个检查点, 然后根据j就可以知道从第1个检查点到第h个检查点一共跳过了多少检查点; 所以就得到了转移方程: dp[i][j] = min(dp[i][j], dp[h][j - (i - h - 1)] + dis(i, h));
本题需要注意的是坐标的范围是1e4, 所以我们不跳过的情况最大距离约为1e4*1e4, 所以只要我们跳过的检查点个数超过32(我没考虑具体是多少, 反正超过32肯定是不行)的话就不用考虑了, 因为惩罚太大了; 所以这样的复杂度就变成了O(n * 32 * 32); 最后我们再遍历到达第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 = 1e4 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, idx;
double dp[N][35];
double a[N], b[N];
double dis(int x, int y) {
    return sqrt((a[x] - a[y]) * (a[x] - a[y]) + (b[x] - b[y]) * (b[x] - b[y]));
}
signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= 32; j++) {
            dp[i][j] = 1e13;
        }
    }
    dp[1][0] = 0;
    for (int i = 2; i <= n; i++) {
        for (int j = 0; j<= min(i-2,32ll); j++) {
            for (int k = 0; k <= j; k++) {
                int h = i - k - 1;
                if (h < 1) continue;
                dp[i][j] = min(dp[i][j], dp[h][j - k] + dis(i, h));
            }
        }
    }
    double minn = dp[n][0];
    for (int i = 1; i <= min(n-2,32ll); i++) {
        minn = min(minn, dp[n][i] + pow(2, i - 1));
        
    }
    printf("%.20lf", minn);
    return 0;
}

posted @ 2023-11-04 00:09  mostimali  阅读(31)  评论(0编辑  收藏  举报