D - Cross Explosion

考察对Set的使用,尤其是prev函数

void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<bool>> st(n + 1, vector<bool>(m + 1, false));
    vector<set<int>> u(m + 2), d(m + 2), l(n + 2), r(n + 2);
    for (int i = 0; i <= n + 1; i ++ )
        for (int j = 0; j <= m + 1; j ++ ){
            l[i].insert(j), r[i].insert(j);
            u[j].insert(i), d[j].insert(i);
        }

    auto gao = [&](int x, int y){ //爆破函数
        st[x][y] = true;
        l[x].erase(y);
        r[x].erase(y);
        u[y].erase(x);
        d[y].erase(x);
    };

    int q;
    cin >> q;
    int cnt = 0;
    while (q -- ){
        int x, y;
        cin >> x >> y;
        if (!st[x][y]){
            gao(x, y);
        }else{
            //4个方向
            //
            auto ir = r[x].upper_bound(y),
                il = prev(l[x].upper_bound(y)),
                iu = prev(u[y].upper_bound(x)),
                id = d[y].upper_bound(x);
            if (*ir >= 1 && *ir <= m) {
                gao(x, *ir);
            }
            if (*il >= 1 && *il <= m){
                gao(x, *il);
            }
            if (*iu >= 1 && *iu <= n){
                gao(*iu, y);
            }
            if (*id >= 1 && *id <= n){
                gao(*id, y);
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            ans += !st[i][j];
    cout << ans;
}

E - Avoid K Partition

考虑dp
dp[i]表示只考虑前i个并以第i个结尾的方案数
可以枚举前一个分界点来进行N^2的暴力转移
if ()
dp[i] += dp[j] (j < i && sum[i] - sum[j] != K)
统计满足 $sum[j] != sum[i] - K$ 太难,因为左边是变量
不妨反向统计 $sum[i] - sum[j] = K$
那么 $sum[j] = sum[i] - K$
然后用总数减去不合法j的方案数。
map记录sum[i]
思考:典型的01分类讨论,DP优化题目,记得初始化空集

#define int long long
const int mod = 998244353;

void solve(){
    int n, K;
    cin >> n >> K;
    int f[n+1] = {};
    int S = 1, sum = 0; // 
    map<int, int> mp;
    mp[0] = 1;
    for (int i = 1; i <= n; i ++ ){
        int x;
        cin >> x;
        sum += x;
        f[i] = ((S - mp[sum - K]) % mod + mod) % mod;
        S += f[i], S %= mod;
        mp[sum] = (mp[sum] + f[i]) % mod;
    }
    cout << f[n];
}

F - Dividing Game

注意到是求最小的最大值 $x$,经典二进制搜索
对于每次搜索
枚举 $i:1-n$ 作为起点的可行性。由于是环,所以经典做法——数组加倍数组拉开环。
对于起点 $i$:
如何走一步到 $j$ 使得总操作尽量可行,经典贪心思路: $找到满足[i, j] \geq x 的最小j$
因为 $k$ 人,所以进行 $k$ 次操作就能判断是否可行。
但是时间上是 $N^2$,需要优化。
思考优化走的操作,经典倍增—— $f[i][j]代表i进行2^j分割能到达的位置$
时间就从 $k 优化到 logk$
真正难点
思考不作为分割点的数目如何求,只要检查起点可行的数目,在用总数相减即可。
结论:如果一个位置作为起点不会被切割,也不会在其他位置作为起点的时候被切割,画图感性理解一下。

#define int long long
const int N = 2e5 + 10, inf = 1e9;

int n, k;
int a[N * 2], f[N * 2][31];

int check(int x)
{
    int q = 0, r = 0;
    for (int i = 1; i <= n * 2; i++)
    {
        while (q < x && r < n * 2){
            q += a[++r];
        }
        if (q >= x){
            f[i][0] = r;
        }
        else{
            f[i][0] = inf;
        }
        q -= a[i];
    }
    // 倍增
    for (int j = 1; j <= 30; j++)
        for (int i = 1; i <= n * 2; i++)
            if (f[i][j - 1] + 1 <= n * 2 && f[f[i][j - 1] + 1][j - 1] <= n * 2)
                f[i][j] = f[f[i][j - 1] + 1][j - 1];
            else
                f[i][j] = inf;
    int res = 0;
    // 求方案数
    for (int i = 1; i <= n; i++)
    {
        int px = i;
        for (int j = 30; j >= 0; j -- )
        {
            if (k >> j & 1){
                px = f[px][j] + 1;
            }
            if (px > i + n)
                break;
        }
        if (px <= i + n){
            ++ res;
        }
    }
    return res;
}

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[i + n] = a[i];
    }
    int l = 0, r = 2e9;
    while (l < r){
        int mid = (l + r + 1) / 2;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l << ' ' << n - check(l) ;
}