【解题报告】 Educational Codeforces Round 40

题目链接


A. Diagonal Walking(Codeforces 954A)

题意

给出一个仅含 R R U U 的字符串。问如何进行一系列操作(每次操作可以将连续的 RU R U UR U R 替换 D D ),使得最后得到的字符串长度最小。

思路

本题的入手点是,先贪心地提出一个算法,再看看有没有更优的算法。
显然,我们可以提出这样的贪心算法:从左到右依次考虑字符串 s s 中相邻的字符对,一旦出现 RU R U 或者 UR U R 的组合就将其替换成 D D
那么,这个算法对不对呢?假如我们不这么贪心(看见可以替换的就立即替换),是否会有更优解?设字符串 s="RUR......." s =" R U R . . . . . . . " ,其中省略号表示任意长度的任意字符。我们从左到右依次考虑相邻的字符对,第一个字符对是 RU R U ,如果我不立即将 RU R U 替换成 D D ,而把 U U 留给右侧的 R R ,那么结果也没有更优,反倒是 U U U U 右侧的 R R 丧失了脱单机会。所以,不贪心是不会得到更好的解的。

代码

#include 
using namespace std;

string s;
int n, cnt;

// 判断是否需要替换
bool ok(int i) {
    if(s[i] == 'R' && s[i + 1] == 'U') {
        return true;
    }
    if(s[i] == 'U' && s[i + 1] == 'R') {
        return true;
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> s;
    // 枚举相邻字符对
    for(int i = 0; i < n - 1; i++) {
        if(ok(i)) {
            cnt ++;
            i ++;
        }
    }
    cout << n - cnt << endl;
    return 0;
}

B. String Typing(Codeforces 954B)

题意

我们将字符串 s s 打印到屏幕上。也就是说,初始状态屏幕上有一个空串 t t 。然后每次执行以下两种操作中的一种。
- 在 t t 的尾部加入某个小写英文字母
- 将 t t 拷贝一个副本 p p ,将 p p 加入 t t 的末尾(最多只能做一次)
给定 s s ,求完成目标的最少操作数。

思路

本题的入手点在于,将问题转化为决策问题。
这个问题本质上是决策问题。正如题目介绍,我们最多可以做一次拷贝操作。这个操作完成后可能会有最优解,也可能没有。因此我们要考虑是否要做这个操作,以及这个操作在什么时候做。这就是个决策问题了,考虑到数据规模不是很大,枚举即可。
假设我们在 t t 的长度已经为 i i 的情况下做拷贝操作(此时t的各个字符是确定的,因为在拷贝之前只能做第一种操作)。如果此时做拷贝操作合法(不会得到不是 s s 的字符串),那么我们可以用 O(1) O ( 1 ) 的复杂度算出操作数,从而更新最优解。所以要求最优解就要枚举 i i 。注意,最优解的初始值应该是 s s 的长度,对应不做拷贝操作的情况。
算法整体的时间复杂度为 O(n2) O ( n 2 ) 。(除了枚举 i i O(n) O ( n ) 复杂度外,判断拷贝是否合法还需要 O(n) O ( n ) 的复杂度)

代码

#include 
using namespace std;

string s;
int n, ans;

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> s;
    ans = n;
    // 枚举在已经打印了多少字符的情况下做拷贝操作
    for(int i = 0; i < n / 2; i++) {
        if(s.substr(0, i + 1) == s.substr(i + 1, i + 1)) {
            ans = min(ans, (i + 1) + 1 + n - 2 * (i + 1));
        }
    }
    cout << ans << endl;
    return 0;
}

C. Matrix Walk(Codeforces 954C)

题意

有一个 x×y x × y 的矩阵,矩阵的数是按照电话按键的方式排列的。现在主角从矩阵某个位置开始连续走 n n 步(每步只能向上下左右四个方向走)。将经过的矩阵元素记录下来形成一个序列 a a 。现在给出这个 n n 和这个序列 a a ,问可能的 x x y y 是多少。

思路

本题的入手点在于按照先特殊后一般的顺序思考问题。
首先,假设矩阵只有一行。那么主角一定只能左右走。也就是说,序列中的数都是连续变化的。只会从元素 3 3 走到元素 4 4 ,从元素 15 15 走到元素 14 14 ,不会从元素 15 15 走到元素 10 10 等等。根据这个特殊情况,我们可以将所有情况分为 3 3 类:
1. 当序列中有相邻的数的数值相同的情况:相当于主角在原地踏步,这是不满足题意的。输出 NO N O
2. 当序列中所有相邻的数都是连续的,也就是说对于所有 i[2,n] i ∈ [ 2 , n ] ,都有 abs(a[i]a[i1])=1 a b s ( a [ i ] − a [ i − 1 ] ) = 1 的情况: x=1y=maxi{a[i]} x = 1 , y = max i { a [ i ] } 肯定满足条件
3. 剩下的情况必然涉及主角的上下移动,这将会告诉我们 y y 值是多少,也就是说在有解的情况下,当 abs(a[i],a[i1])>1 a b s ( a [ i ] , a [ i − 1 ] ) > 1 对某个 i i 成立时, abs(a[i],a[i1]) a b s ( a [ i ] , a [ i − 1 ] ) 就等于 y y 值(如果不明白的话,画一个小矩阵,上下走走看就明白啦~)。 x x 值可以直接设置为 109 10 9 ,也可以根据 y y 值和序列中最大的值算出来。有了 x x y y 值后就不难判断这是否是解了(只要遍历序列,检查是否有异常的移动即可)

代码

#include 
using namespace std;

const int maxn = 2e5 + 10;
int n, mx, mn, r, c, a[maxn];

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n;
    mx = 0;
    mn = 2e9;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        // 预处理最大值和最小值
        mx = max(mx, a[i]);
        mn = min(mn, a[i]);
    }
    bool one = true;
    for(int i = 1; i <= n - 1; i++) {
        if(abs(a[i] - a[i + 1]) == 0) {
            cout << "NO" << endl;
            return 0;
        }
        if(abs(a[i] - a[i + 1]) > 1) {
            // 发现行间移动,计算y
            c = abs(a[i] - a[i + 1]);
            one = false;
        }
    }
    // 只有一行的情况
    if(true == one) {
        cout << "YES" << endl;
        cout << 1 << ' ' << mx << endl;
        return 0;
    }
    // 计算出x
    r = mx / c + (mx % c > 0);
    for(int i = 1; i <= n - 1; i++) {
        int d = abs(a[i] - a[i + 1]);
        if(d == 1) {
            // 非法情况:左边界向左移动
            if(a[i] % c == 0 && a[i] + 1 == a[i + 1]) {
                cout << "NO" << endl;
                return 0;
            }
            // 非法情况:在右边界向右移动
            if(a[i] % c == 1 && a[i] - 1 == a[i + 1]) {
                cout << "NO" << endl;
                return 0;
            }
        }
        // 非法情况:跨行移动距离与计算不符
        if(d > 1 && d != c) {
            cout << "NO" << endl;
            return 0;
        }
    }
    cout << "YES" << endl;
    cout << r << ' ' << c << endl;
    return 0;
}

D. Array Division(Codeforces 808D)

题意

有一个无向连通图,其中不含重边或者自环。给出两点 s,t s , t ,问有多少对 u,v u , v ,使得如果将 u,v u , v 连上无向边,则从 s s t t 的最短路(边的长度都为 1 1 )不会变短(同时保持图中不含重边和自环)。

思路

本题的入手点在于考察什么情况下加入无向边会使s到t的最短路变短,以及通过枚举来加强条件。
显然,在 s s t t 的最短路径上,随便选取不相邻的,还不存在边的点对,就是加边后会缩短最短路径的点对。不过,这个限制条件还是有点弱(不足以确定是具体是哪些点对),看来需要手动加强限制。考虑枚举点 u u 和点 v v ,此时复杂度已经达到了 O(n2) O ( n 2 ) 。那么,是否能够在常数时间内判断出在点 u,v u , v 间加边会不会让 s s t t 的最短路不变短呢?
既然要在常数时间内做一个这么强的判断,那么肯定要将一些信息预处理出来。于是预处理出两个数组 ds,dtds[i] d s , d t , d s [ i ] 表示从 s s 到点 i i 的最短路径长度, dt[i] d t [ i ] 表示从 t t 到点 i i 的最短路径长度。同时也与处理除了 s s t t 的最短路径长度 md m d 。(用 BFS B F S 都能预处理出来)。这样在枚举到 u,v u , v 的时候,就可以算出两种最短路的长度:
1. 途经 s>u>v>t s − > u − > v − > t 的最短路径长度: md0=ds[u]+1+dt[v] m d 0 = d s [ u ] + 1 + d t [ v ]
2. 途径 s>v>u>t s − > v − > u − > t 的最短路径长度: md1=ds[v]+1+dt[u] m d 1 = d s [ v ] + 1 + d t [ u ]
通过比较 md m d min(md0,md1) min ( m d 0 , m d 1 ) 的大小就可以知道 u,v u , v 是否是满足要求的点对了。

代码

#include 
using namespace std;

const int maxn = 1010;
bool vis[maxn];
int n, m, s, t, u, v, ans;
int G[maxn][maxn], d[2][maxn];

// BFS计算最短路
// 处理从以s为起点的数组d[idx][]
void BFS(int s, int idx) {
    queue <int> que;
    que.push(s);
    d[idx][s] = 0;
    memset(vis, 0, sizeof(vis));
    vis[s] = true;
    while(!que.empty()) {
        int u = que.front();
        que.pop();
        for(int v = 1; v <= n; v++) {
            if(!G[u][v] || vis[v]) {
                continue;
            }
            que.push(v);
            d[idx][v] = d[idx][u] + 1;
            vis[v] = true;
        }
    }
}

// 判断点对是否满足要求
bool ok(int u, int v) {
    if(d[0][u] + 1 + d[1][v] < d[0][t]) {
        return false;
    }
    if(d[0][v] + 1 + d[1][u] < d[0][t]) {
        return false;
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> m >> s >> t;
    for(int i = 1; i <= m; i++) {
        cin >> u >> v;
        G[u][v] = G[v][u] = 1;
    }
    // 预处理
    BFS(s, 0);
    BFS(t, 1);
    // 枚举点对
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n; j++) {
            if(!G[i][j] && ok(i, j)) {
                ans ++;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

E. Water Taps(Codeforces 954E)

题意

n n 个水龙头,它们流出的水都会汇集到一个水缸里。每个水龙头有水流的水温和流量的上限两个属性。问在控制水缸温度恒为 T T 的情况下, n n 个水龙头的总流速最高可以是多少?

思路

本题的入手点是,发现答案的单调性。
首先,这题的数学关系题目已经给得很清楚了,让我们来梳理一下。题目的限制条件是:

i=1nxitii=1nxi=T ∑ i = 1 n x i t i ∑ i = 1 n x i = T

xi[0,ai] x i ∈ [ 0 , a i ]

我们要最大化的目标是( y y 是我自己引入的符号,目的是方便描述):

maxy=i=1nxi max y = ∑ i = 1 n x i

首先观察一下限制条件中的分式,发现它是不好化简的(至少我是这么认为的)。用几何观点的话,分子是点积的形式,但也不太好利用。
既然限制条件无法直接利用,不如反过来思考。如果限制条件满足了,会发生什么。考虑现在有一组解 (x1,x2,...,xn) ( x 1 , x 2 , . . . , x n ) 能够满足限制条件,我们怎样利用这组解来构造另一组解?答案是,我们可以将某个水温高的水龙头关小一点,再将某个水温低的水龙头开大,开大到到能够弥补前者关小所带来的的温度损失为止。调整结束后,水龙头的总流速将会增加
这恐怕是目前得到的最重要的信息了。因为这意味着, 如果已经有一组解,那么可能能够在增大总流速的情况下保持水缸中温度不变。但是,不能无穷地变大,因为每个 xi x i 都有 ai a i 这个上限。相反地,如果已经有一组解,那么一定能够在减小总流速的情况下保持水缸中的温度不变,直至流速无穷小 (分析方法同前一个结论一样,但是方向相反)。
总流速 y y 的这个性质使得它具有单调性,也就是说可以用二分查找来得到最优解(当然,或者无解)。于是我们可以二分 y y ,同时判断 y y 是否满足条件,也就是说判断总流速为 y y 时是否可以加权平均出 T T 的水温。
判断 y y 是否满足条件就很容易了。可以计算出能通过流速 y y 构造出的最高温度 ub u b ,以及能通过流速 y y 构造出的最低温度 lb l b (这需要实现对水龙头排序)。判断是否满足 lbyub l b ≤ y ≤ u b 即可。

代码

#include 
using namespace std;

struct Tap {
    int a, t;
    // 重载小于号,便于排序
    bool operator < (const Tap& o) const {
        return t > o.t;
    }
};

const int maxn = 2e5 + 10;
const double eps = 1e-10;
Tap taps[maxn];
int n, T, a, t;
double l, r;

// 考虑精度问题
int cmp(double x) {
    if(x < -eps) {
        return -1;
    }
    return x > eps;
}

// 判断水龙头的综流速为sum时是否能够保持水的温度为T
bool ok(double sum) {
    double s = sum;
    double p = sum * T;
    double lb = 0, ub = 0;
    // 计算上界ub
    for(int i = 1; i <= n; i++) {
        double a = taps[i].a;
        double t = taps[i].t;
        if(cmp(sum - a) >= 0) {
            sum -= a;
            ub += a * t;
        }
        else {
            ub += sum * t;
            break;
        }
    }
    sum = s;
    // 计算下界lb
    for(int i = n; i >= 1; i--) {
        double a = taps[i].a;
        double t = taps[i].t;
        if(cmp(sum - a) >= 0) {
            sum -= a;
            lb += a * t;
        }
        else {
            lb += sum * t;
            break;
        }
    }
    return cmp(ub - p) >= 0 && cmp(p - lb) >= 0;
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> T;
    for(int i = 1; i <= n; i++) {
        cin >> taps[i].a;
        r += taps[i].a;
    }
    for(int i = 1; i <= n; i++) {
        cin >> taps[i].t;
    }
    sort(taps + 1, taps + n + 1);
    l = eps;
    // 二分答案
    for(int i = 1; i <= 100; i++) {
        double mid = (l + r) / 2;
        if(true == ok(mid)) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    cout << setprecision(15) << fixed;
    cout << l << endl;
    return 0;
}

F. Runner’s Problem(Codeforces 954F)

题意

有一个 3×m 3 × m 的网格,网格中有一些障碍,其中第 k k 个障碍 (ak,lk,rk) ( a k , l k , r k ) 表示第 ak a k 行的第 lk l k 列到第 rk r k 列不能通过。问在每步只能从 (i,j) ( i , j ) 跑向 (i,j+1),(i1,j+1) ( i , j + 1 ) , ( i − 1 , j + 1 ) (i+1,j+1) ( i + 1 , j + 1 ) 的情况下,从 (2,1) ( 2 , 1 ) 跑到 (2,m) ( 2 , m ) 的方法数有多少种。

思路

本题的入手点是,先简化问题,再逐步解决更复杂的问题。
这里的 m m 比较大,达到了 1018 10 18 。那么可能的情况是,我们可以构造出跟 m m 有关的公式或者或者跟 m m 有关的递推式(然后必然要用矩阵快速幂来加速递推)来计算答案。先有这个心理准备就行了。接下来先要无视这个数据规模,同时也无视障碍。假设 m=2 m = 2 ,并且网格中没有任何障碍。我们令 d[i][j] d [ i ] [ j ] 表示到第 i i 行第 j j 列的方法数有多少种。接着可以得到 d d 计算结果如下(此处省略取模过程):

d[1][1]=0 d [ 1 ] [ 1 ] = 0
d[2][1]=1 d [ 2 ] [ 1 ] = 1
d[3][1]=0 d [ 3 ] [ 1 ] = 0

d[1][2]=d[1][1]+d[2][1]=1 d [ 1 ] [ 2 ] = d [ 1 ] [ 1 ] + d [ 2 ] [ 1 ] = 1
d[2][1]=d[1][1]+d[2][1]+d[3][1]=1 d [ 2 ] [ 1 ] = d [ 1 ] [ 1 ] + d [ 2 ] [ 1 ] + d [ 3 ] [ 1 ] = 1
d[3][1]=d[2][1]+d[3][1]=1 d [ 3 ] [ 1 ] = d [ 2 ] [ 1 ] + d [ 3 ] [ 1 ] = 1

这里的确发现了递推关系。写成矩阵的形式有:

d[1][i+1]d[2][i+1]d[3][i+1]=110111011×d[1][i]d[2][i]d[3][i] ( d [ 1 ] [ i + 1 ] d [ 2 ] [ i + 1 ] d [ 3 ] [ i + 1 ] ) = ( 1 1 0 1 1 1 0 1 1 ) × ( d [ 1 ] [ i ] d [ 2 ] [ i ] d [ 3 ] [ i ] )

于是借助矩阵快速幂技术,我们可以算出对于 m1018 m ≤ 10 18 的无障碍的网格的答案。现在还剩下的问题就是,如何应付障碍。其实很简单,在一段区间内,只要障碍形式的变化相同,就可以用矩阵表示转移,就可以用矩阵快速幂技术。例如网格的某列:

0 0
0 0
0 0

这就是一种障碍形式,当这列网格的右侧紧接着一列不一样的障碍形式的时候,我们就说障碍形式发生了变化,例如:

00 00
01 01
00 00

于是我们需要将网格划分成几段,使得每段中的障碍形式变化相同。例如,下面的网格( 0 0 表示空位, 1 1 表示障碍):

0010001110 0010001110
0001111110 0001111110
0010000000 0010000000

离散化后变成:

00 1 000 111 0 00   1   000   111   0
00 0 111 111 0 00   0   111   111   0
00 1 000 000 0 00   1   000   000   0

因为段内的障碍形式变化相同,一段与一段之间最多只有一次障碍形式变化。所以在每一段内用矩阵快速幂做递推,在段与段之间用矩阵快速幂做递推就可以了。

代码

#include 
using namespace std;

// 矩阵类
template <class T>
struct matrix {
    vector < vector > a;
    int n, m;
    matrix(int n = 1, int m = 1): n(n), m(m) {
        assert(n > 0 && m > 0);
        a = vector < vector > (n);
        for(int i = 0; i < n; i++) {
            a[i] = vector  (m);
        }
    }
    matrix& operator << (const string& s) {
        stringstream ss(s);
        T x;
        for(int i = 0; i < n * m && ss >> x; i++) {
            a[i / m][i % m] = x;
        }
    }
    T& operator () (int x, int y) {
        return a[x][y];
    }
    // 矩阵模乘
    matrix modMul(matrix &b, T mod) const {
        assert(m == b.n);
        matrix  res(n, b.m);
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < b.m; j++) {
                for(int k = 0; k < m; k++) {
                    T tmp = a[i][k] * b.a[k][j] % mod;
                    res.a[i][j] = (res.a[i][j] + tmp) % mod;
                }
            }
        }
        return res;
    }
    // 矩阵快速幂
    matrix modPow(T e, T mod) const {
        assert(n == m);
        matrix  a = *this, res(n, n);
        for(int i = 0; i < n; i++) {
            res.a[i][i] = 1;
        }
        for(; e > 0; e >>= 1) {
            if(e & 1) {
                res = res.modMul(a, mod);
            }
            a = a.modMul(a, mod);
        }
        return res;
    }
    inline void set(int r, int c, string s, T x = 0) {
        assert(!(r < 0 && c < 0) && !(r >= 0 && c >= 0));
        stringstream ss(s);
        for(int i = 0; ((c < 0 && i < m) || c >= 0 && i < n) && ss >> x; i++) {
            c < 0 ? a[r][i] = x : a[i][c] = x;
        }
    }
};

typedef long long ll;
typedef matrix  mat;
const int maxn = 1e4 + 5;
const ll mod = 1e9 + 7;
map int> mp;
int n, nn, a[maxn], s[4][4 * maxn];
ll m, b[4 * maxn], l[maxn], r[maxn];

int main() {
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> m;
    // b用来存储所有将要被离散化的坐标
    for(int i = 1; i <= n; i++) {
        cin >> a[i] >> l[i] >> r[i];
        b[i] = l[i] - 1;
        b[n + i] = l[i];
        b[2 * n + i] = r[i];
        b[3 * n + i] = r[i] + 1;
    }
    // 必须算上边界,不然会出错
    b[4 * n + 1] = 1;
    b[4 * n + 2] = m;
    // 排序并离散化
    sort(b + 1, b + 4 * n + 3);
    nn = unique(b + 1, b + 4 * n + 3) - b - 1;
    // 计算原值到离散值的映射
    for(int i = 1; i <= nn; i++) {
        mp[b[i]] = i;
    }
    // 预处理前缀和数组以快速查找障碍形式
    for(int i = 1; i <= n; i++) {
        int id = a[i];
        int lb = mp[l[i]];
        int ub = mp[r[i]];
        s[id][lb] ++;
        s[id][ub + 1] --;
    }
    for(int i = 1; i <= 3; i++) {
        for(int j = 1; j <= nn; j++) {
            s[i][j] += s[i][j - 1];
        }
    }
    mat A(3, 3), ans(3, 1);
    A << "1 1 0 1 1 1 0 1 1";
    ans << "0 1 0";
    // 分段快速幂
    for(int i = 2; i <= nn; i++) {
        mat B = A;
        for(int j = 1; j <= 3; j++) {
            if(s[j][i] > 0) {
                B.set(j - 1, -1, "0 0 0");
            }
            if(s[j][i - 1] > 0) {
                B.set(-1, j - 1, "0 0 0");
            }
        }
        ans = B.modPow(b[i] - b[i - 1], mod).modMul(ans, mod);
    }
    cout << ans(1, 0) << endl;
    return 0;
}

G. Castle Defense(Codeforces 954G)

题意

城墙上有 n n 个连成一排的区域,每个区域中有一些弓箭手。弓箭手们都有 r r 的防御半径,也就是说,弓箭手能够防守到向左或向右 r r 个区域加上自己所处区域的范围。每个区域的防御等级为能够防守到该区域的弓箭手数量的总和,而城墙的防御等级为各区域防御等级的最小值。现在我们共有 k k 名备用弓箭手可以增援这 n n 个区域。问增援后城墙的防御等级的最大值能达到多少。

思路

本题的入手点是,发现答案的单调性。

显然,有些低防御等级能够达到,高防御等级未必能够达到。这使得我们可以二分防御等级,将问题转化为判定某个防御等级是否能达到。

判定的算法为:从左到右遍历各区域,只要有区域i防御等级没有达到,就立即在相应的位置上布置弓箭手。“相应的位置”指防御半径能够够得到 i i 的最右侧的区域。
(不知道为什么这题会被放在 G G 题的位置)

代码

#include 
using namespace std;

typedef long long ll;
const int maxn = 5e5 + 10;
int n, r, a[maxn];
ll k, sum[maxn], b[maxn], c[maxn];

// 判断城墙防御等级是否能达到mn
bool ok(ll mn) {
    ll tot = k;
    memset(c, 0, sizeof(c));
    for(int i = 1; i <= n; i++) {
        c[i] += c[i - 1];
        ll cur = b[i] + c[i];
        if(cur < mn) {
            ll need = mn - cur;
            if(tot < need) {
                return false;
            }
            tot -= need;
            c[i] += need;
            c[min(n + 1, i + 2 * r + 1)] -= need;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> r >> k;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    // b[i]表示第i个区域的防御等级
    for(int i = 1; i <= n; i++) {
        int ub = min(n, i + r);
        int lb = max(1, i - r);
        b[i] = sum[ub] - sum[lb - 1];
    }
    // 二分答案
    ll lb = 0;
    ll ub = LLONG_MAX;
    while(ub - lb > 1) {
        ll mid = (lb + ub) / 2;
        if(ok(mid)) {
            lb = mid;
        }
        else {
            ub = mid;
        }
    }
    cout << lb << endl;
    return 0;
}

(其它题目略)

你可能感兴趣的:(Codeforces)