AtCoder Beginner Contest 146

发布时间 2023-03-26 16:13:02作者: Sakana~

AtCoder Beginner Contest 146

https://atcoder.jp/contests/abc146

C - Buy an Integer

这个O(1)推式子的做法不知道为什么WA:https://atcoder.jp/contests/abc146/submissions/40056317
有一个点一直过不了,最后换了二分的做法过了。

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int main () {
    ll a, b, x;
    cin >> a >> b >> x;
    ll l = 0, r = 1e9;
    while (l < r) {
        ll mid = l + r + 1 >> 1;
        if (mid * a + (to_string(mid).size ()) * b <= x)   l = mid;
        else    r = mid - 1;
    }
    cout << r << endl;
}

D - Coloring Edges on Tree

原理

贪心涂色。
随便找一个点开始染色。首先k可以直接确定,就是点的最大度数。然后因为是个树形结构,从上往下染色的时候只需要保证染的颜色不和该点与父亲连的边颜色相同即可。
\(color_u\) 记录 \(u\) 连向父亲边的颜色,树形结构保证 \(color_u\) 只有一一种可能,然后给 \(u\) 的儿子染色的时候,只需要从 \(1\)\(k\) 这样染色,遇到 \(color_u\) 就跳过即可。

实现

原理很简单,但是实现的时候卡了很久,主要是dfs的时候不太清楚在哪里更新颜色,以及在哪里深入。倒腾出来之后加深了对dfs的理解,就是一定要牢牢抓住"回溯"的概念,即dfs不是一个一直向下的过程,而是会往上更新的,可以想象线段树的那个图。因此更新和递归不能分开来写(最开始就是在这里纠结了好久)。
说白了还是基础不牢!!一直没有真正理解dfs。之前有人和我说把它当成栈来看,可以试试这种方法。

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
vector<int> v[N];
int n, d[N], color[N], e[N], maxn;
bool root[N];

void dfs (int u, int fa, int dx) {
    for (auto j : v[u]) {
        if (j == fa)    continue;
        if (dx == color[u])     dx ++;
        color[j] = dx ++;
        dfs (j, u, 1); //一定要先更新完当前分支的颜色
        //更新和递归不能分开来写,不然回溯的时候找不到路
    }
}

int main () {
    cin >> n;
    for (int i = 1; i <= n; i++)    root[i] = true;
    for (int i = 1; i < n; i++) {
        int a, b;
        cin >> a >> b;
        e[i] = b; //一个点只能有一个父亲
        v[a].push_back (b), v[b].push_back (a);
        d[b] ++, d[a] ++;
        maxn = max (maxn, max (d[a], d[b]));
        root[b] = false;
    }
    cout << maxn << endl;
    for (int i = 1; i <= n; i++) {
        if (root[i]) {
            // cout << i << endl;
            dfs (i, -1, 1);
            break;
        }
    } 
    for (int i = 1; i < n; i++)     cout << color[e[i]] << endl;
}

//只需要记录一下父亲边是什么就好了

E - Rem of Sum is Num

先进行柿子转化:

\[sum_{l,r} \,mod\,k=len\rightarrow k|(sum_{l,r}-len) \]

\(sum-len\) 的更新有如下规律:

\[sum-len \rightarrow sum+a_i-len-1 = sum-len+(a_i-1) \]

所以等价于求 \(a_i-1\) 的前缀和。然后记录前面与其相等的余数有多少个即可(相减等于0,符合条件)。注意区间长度不能超过 \(k\),详见代码。

(一开始跑偏了,楞是想着预处理出来所有 \(sum-len\),甚至还把 \(k\) 质因数分解了,其实没必要这么复杂,多想想能不能用STL)

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N = 2e5 + 5;
ll a[N], s[N];
ll n, k, ans;
map<ll, int> mp;

int main () {
    cin >> n >> k;
    for (int i = 1; i <= n; i++)    cin >> a[i], s[i] = (s[i-1] + a[i] - 1) % k;
    mp[0] = 1;
    for (int i = 1; i <= n; i++) {
        // cout << s[i] << ' ';
        if (i >= k)     mp[s[i-k]] --; //超出范围
        ans += mp[s[i]];
        mp[s[i]] ++;
    }    
    cout << ans << endl;
}

//滑动窗口
//s[l,r] % k = len
// k | (s - len)
//s-len -> s+a[i]-len-1 = s-len+a[i]-1, 所以等价于求a[i]-1的前缀和

F - Sugoroku

贪心 + 一步小小的转化
首先如果最大连续1的长度 \(\geq m\),一定到不了。
每一步都走到能走的最远一定最优:

如上图,上下两种策略是等价的,而选择上面那种策略的话,第二步能往后走到的范围就比下面哪种策略大。

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
int n, m;
string s;
vector<int> v;

int main () {
    cin >> n >> m >> s;
    reverse (s.begin (), s.end ());
    int len = 0;
    for (int i = 0; i <= n; i++) {
        if (s[i] == '0')    len = 0;
        else    len ++;
        if (len >= m) {
            cout << -1;
            return 0;
        }
    }
    int l = 0;
    while (l < n) {
        int dx = m;
        while (s[l + dx] == '1')     dx --;
        dx = min(dx, n - l);
        v.push_back (dx);
        l += dx;
    }
    reverse (v.begin (), v.end ());
    for (auto i : v)    cout << i << ' ';
}

//贪心肯定能到达,关键在于字典序
//逆向思考,从起点到终点的路是一样的,先翻转s, 再逆向输出操作序列