20250901

T1

露营找水

对每个格子二分出以其为左上顶点的最大正方形,然后相当于正方形 checkmax。因为是正方形,所以可以直接套 ST 表维护区间 checkmax 那一套。

代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
inline void Cmax(int &x, int y) { x = max(x, y); }
int n, m, K, q;
string str[2005];
int a[2005][2005];
inline int qs(int l1, int r1, int l2, int r2) { return a[r1][r2] - a[l1 - 1][r2] - a[r1][l2 - 1] + a[l1 - 1][l2 - 1]; }
int mx[13][2005][2005];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    freopen("water.in", "r", stdin);
    freopen("water.out", "w", stdout);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> str[i], str[i] = ' ' + str[i];
        for (int j = 1; j <= m; j++) a[i][j] = a[i - 1][j] + a[i][j - 1] + (str[i][j] == '#') - a[i - 1][j - 1];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (str[i][j] == '#') continue;
            int l = 1, r = min(m - j, n - i) + 1, mid, ans = 1;
            while (l <= r) {
                mid = (l + r) >> 1;
                if (qs(i, i + mid - 1, j, j + mid - 1)) r = mid - 1;
                else l = mid + 1, ans = mid;
            }
            int k = 31 - __builtin_clz(ans);
            Cmax(mx[k][i][j], ans);
            Cmax(mx[k][i + ans - (1 << k)][j], ans);
            Cmax(mx[k][i][j + ans - (1 << k)], ans);
            Cmax(mx[k][i + ans - (1 << k)][j + ans - (1 << k)], ans);
        }
    }
    for (int i = 11; ~i; i--) {
        for (int x = 1; x + (1 << (i + 1)) - 1 <= n; x++) {
            for (int y = 1; y + (1 << (i + 1)) - 1 <= m; y++) {
                Cmax(mx[i][x][y], mx[i + 1][x][y]);
                Cmax(mx[i][x + (1 << i)][y], mx[i + 1][x][y]);
                Cmax(mx[i][x][y + (1 << i)], mx[i + 1][x][y]);
                Cmax(mx[i][x + (1 << i)][y + (1 << i)], mx[i + 1][x][y]);
            }
        }
    }
    cin >> q;
    while (q--) {
        int x, y;
        cin >> x >> y;
        cout << 1ll * mx[0][x][y] * mx[0][x][y] << "\n";
    }
    return 0;
}

T2

还原数组

由于 \(\min\) 变成 \(\max\) 有更强的原题,所以先变成 \(\max\)。求出每个位置的上界,上界相同的分别考虑。每个限制在同一个上界 \(x\) 里相当于一个区间,如果区间没有交则无解。否则在区间的交里面任选一个位置填 \(x\),剩下的任意,组合数即可。剩下没有被覆盖的位置也是组合数。

代码
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int P = 1000000007;
int fac[2000005], ifac[2000005], inv[2000005];
void Cpre(int n) {
    fac[0] = fac[1] = ifac[0] = ifac[1] = inv[0] = inv[1] = 1;
    for (int i = 2; i <= n; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        ifac[i] = ifac[i - 1] * inv[i] % P;
    }
}
inline int C(int n, int m) { return (n < 0 || m < 0 || n < m) ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P; }
int qpow(int x, int y = P - 2) {
    int ret = 1;
    while (y) {
        if (y & 1) 
            ret = ret * x % P;
        y >>= 1;
        x = x * x % P;
    }
    return ret;
}
int n, q;
struct X {
    int l, r, x;
} a[200005];
int dsu[200005], key[200005], kcnt;
int getf(int x) { return (dsu[x] == x ? x : (dsu[x] = getf(dsu[x]))); }
vector<int> vec[200005];
int f[200005];
signed main() {
    freopen("array.in", "r", stdin);
    freopen("array.out", "w", stdout);
    Cpre(200000);
    cin >> n >> q;
    for (int i = 1; i <= q; i++) cin >> a[i].l >> a[i].r >> a[i].x, a[i].x = n - a[i].x + 1;
    sort(a + 1, a + q + 1, [](X x, X y) { return x.x < y.x; });
    for (int i = 1; i <= n + 1; i++) dsu[i] = i;
    int ans = 1, res = 0;
    for (int i = 1, j = 1; i <= q; i = j) {
        kcnt = 0;
        while (j <= q && a[j].x == a[i].x) ++j;
        res += a[i].x - a[i - 1].x;
        for (int k = i; k < j; k++) {
            for (int x = getf(a[k].l); x <= a[k].r; x = getf(x)) 
                key[++kcnt] = x, dsu[x] = x + 1;
        }
        sort(key + 1, key + kcnt + 1);
        for (int k = 0; k <= kcnt + 1; k++) f[k] = 0, vec[k].clear();
        int mr = n + 1, ml = 0;
        for (int k = i; k < j; k++) {
            int tr = upper_bound(key + 1, key + kcnt + 1, a[k].r) - key - 1, tl = lower_bound(key + 1, key + kcnt + 1, a[k].l) - key;
            ml = max(ml, tl), mr = min(mr, tr);
        }
        if (ml > mr) return cout << "0\n", 0;
        ans = ans * (mr - ml + 1) % P; --res;
        ans = ans * C(res, kcnt - 1) % P * fac[kcnt - 1] % P;
        res -= (kcnt - 1);
    }
    res += n - a[q].x;
    for (int i = getf(1); i <= n; i = getf(i)) ans = ans * (res--) % P, dsu[i] = i + 1;
    cout << ans << "\n";
    return 0;
}

T3

吃薯条

鬼题,什么东西。

对每个薯条被分 \(t\) 次之后对别的东西造成的影响考虑,发现这个东西每隔一位有一个值,值依次为 \(\frac{\binom{t}{\cdots}}{2^n}\)。显然这个东西从中心往左右走很多位之后值就很小了,直接忽略。实测只需要保留 \(5 \times 10^4\) 位就够了。但是过不去,考虑再把连续相近的段缩起来,标准是 \(\frac{\max}{\min} \le D\),其中阈值 \(D\),官方题解取了 \(1.25\)。每一段内直接将所有数视为 \(\sqrt{\max\times\min}\),即这俩的几何平均数,于是每一段的贡献就可以直接前缀和维护了。对每个人遍历每一段,暴力修改最后前缀和一遍即可。

代码
#include <iostream>
#include <iomanip>
#include <vector>
#include <math.h>
using namespace std;
double L, D = 1.25, eps = 1e-9;
int N = 50000;
int n, t;
double d[2][5100005];
double C[50000005];
struct X { int l, r; double v; };
vector<X> vec;
int main() {
    freopen("fries.in", "r", stdin);
    freopen("fries.out", "w", stdout);
    cin >> n >> t >> L;
    N = min(N, t / 2);
    C[0] = -t * log(2);
    for (int i = 1; i <= t; i++) C[i] = C[i - 1] - log(i) + log(t - i + 1);
    for (int i = 0; i <= t; i++) C[i] = exp(C[i]);
    for (int i = t / 2 - N, j = i; i <= (t + 1) / 2 + N; i = j) {
        double mx = 0, mn = 1;
        if (C[i] <= eps) { ++j; continue; }
        while (j <= (t + 1) / 2 + N && C[j] > eps && max(mx, C[j]) / min(mn, C[j]) <= D) mx = max(mx, C[j]), mn = min(mn, C[j]), ++j;
        vec.push_back({ i - t / 2 + N, j - t / 2 + N - 1, sqrt(mn * mx) });
    }
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        for (auto v : vec) d[x & 1][x / 2 + v.l] += v.v, d[x & 1][x / 2 + v.r + 1] -= v.v;
    }
    int cnt = (d[0][0] >= L) + (d[1][0] >= L);
    for (int i = 1; i <= 5000000 + N * 2; i++) d[0][i] += d[0][i - 1], cnt += (d[0][i] >= L);
    for (int i = 1; i <= 5000000 + N * 2; i++) d[1][i] += d[1][i - 1], cnt += (d[1][i] >= L);
    cout << cnt << "\n";
    return 0;
}

T4

城市供电

也许可以通过部分分想到,我们考虑设 \(p, q\) 表示给出的所有数不全相同的行和列。若没有则为 \(0\),有则任意。接下来设 \(R_i\) 为各行的和,\(C_j\) 为各列的和,分类讨论:

  1. \(p = 0, q = 0\):易证所有给出的数 \(x\) 都相同,且 \(R\) 全部相同或 \(C\) 全部相同。枚举哪一边全部相同,则另一边只能是 \(\pm x\)。钦定一样的那一边全为 \(0\),枚举另一边有几个 \(-x\) 即可。合法的条件是 \(\sum R - \sum C \equiv 0 \bmod (n - m)\),因为此时可以通过调整(\(R\)\(C\) 整体加相同数)使得 \(\sum R = \sum C\),而 \(\sum R = \sum C\) 时是容易构造方案的。当然如果 \(n = m\) 就必须 \(\sum R = \sum C\) 才能合法,因为此时没法调整。

  2. \(p \neq 0, q \neq 0\):取一列 \(q\),并再取两行 \(i, j(a_{i, p} \neq a_{j, p})\),易得 \(R_i \neq R_j\),于是可以直接枚举 \(a_{i, p}, a_{j, p}\) 分别是正是负来确定 \(R_i\)\(R_j\) 的值。确定了这两个的值之后容易再确定每一列的列和,进而再通过选择两个列和不同的列算出所有行和并若合法则加以调整构造方案。

  3. 假设此时 \(p = 0\),会发现遇到的困难是所有列和都相等,没法推行和。钦定所有列和为 \(0\),每个行和只有两种取值,即 \(\pm a_{i, *}\)。那么直接 bitset 优化背包求出所有可行的 \(\sum R\),并选一个合法的调整求解即可。

代码
#include <iostream>
#include <algorithm>
#include <cassert>
#include <bitset>
#define int long long
using namespace std;
int n, m;
int a[1005][1005];
int A[1005][1005];
int R[1005], C[1005];
bool Cons(int x, int y, int q) {
    for (int j = 1; j <= m; j++) {
        if (j == q) continue;
        int x1 = R[x] - a[x][j], x2 = R[x] + a[x][j];
        int y1 = R[y] - a[y][j], y2 = R[y] + a[y][j];
        if (x1 != y1 && x2 != y2 && x1 != y2 && x2 != y1) return 0;
        C[j] = (x1 == y1 || x1 == y2 ? x1 : x2);
    }
    int p = q;
    for (int i = 1; i <= m; i++) if (C[i] != C[q]) p = i;
    for (int i = 1; i <= n; i++) {
        if (i == x || i == y) continue;
        int x1 = C[p] - a[i][p], x2 = C[p] + a[i][p];
        int y1 = C[q] - a[i][q], y2 = C[q] + a[i][q];
        if (x1 != y1 && x2 != y2 && x1 != y2 && x2 != y1) return 0;
        R[i] = (x1 == y1 || x1 == y2 ? x1 : x2);
    }
    for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (abs(R[i] - C[j]) != a[i][j]) return 0;
    int s = 0;
    for (int i = 1; i <= n; i++) s += R[i];
    for (int i = 1; i <= m; i++) s -= C[i];
    return s == 0 || (n != m && s % (n - m) == 0);
}
bitset<2000001> f, g[1005];
bool asdf;
void work(int n, int m, bool f = 1) {
    if (f == 1) {
        int s = 0;
        // sr + x * n = sc + x * m
        for (int i = 1; i <= n; i++) s += R[i];
        for (int i = 1; i <= m; i++) s -= C[i];
        if (s != 0) assert(m != n), s /= (m - n);
        for (int i = 1; i <= n; i++) R[i] += s;
        for (int j = 1; j <= m; j++) C[j] += s;
    }
    if (n == 1) for (int i = 1; i <= m; i++) A[1][i] = C[i];
    if (m == 1) for (int i = 1; i <= n; i++) A[i][1] = R[i];
    if (n == 1 || m == 1) {
        if (asdf) {
            for (int i = 1; i <= ::m; i++) {
                for (int j = 1; j <= ::n; j++) 
                    cout << A[j][i] << " ";
                cout << "\n";
            }
        } else {
            for (int i = 1; i <= ::n; i++) {
                for (int j = 1; j <= ::m; j++) cout << A[i][j] << " ";
                cout << "\n";
            }
        }
        return;
    }
    if (R[n] < C[m]) A[n][m] = R[n], A[n - 1][m] = C[m] - R[n], R[n - 1] -= A[n - 1][m];
    else A[n][m] = C[m], A[n][m - 1] = R[n] - C[m], C[m - 1] -= A[n][m - 1];
    work(n - 1, m - 1);
}
signed main() {
    freopen("power.in", "r", stdin);
    freopen("power.out", "w", stdout);
    cin >> n >> m;
    int p = 0, q = 0;
    for (int i = 1, as; i <= n; i++) {
        for (int j = as = 1; j <= m; j++) cin >> a[i][j], as &= (j == 1 || a[i][j] == a[i][j - 1]);
        !as ? (p = i) : 0;
    }
    for (int j = 1, as; j <= m; j++) {
        for (int i = as = 1; i <= n; i++) as &= (i == 1 || a[i][j] == a[i - 1][j]);
        !as ? (q = j) : 0;
    }
    if (!p && !q) {
        for (int j = 0, s; j <= m; j++) {
            s = 0;
            for (int k = 1; k <= m; k++) C[k] = (k <= j ? a[1][1] : -a[1][1]), s += C[k];
            if ((n == m && s == 0) || (n != m && s % (n - m) == 0)) return work(n, m, 1), 0;
        }
        for (int j = 1; j <= m; j++) C[j] = 0;
        for (int i = 0, s; i <= n; i++) {
            s = 0;
            for (int k = 1; k <= n; k++) R[k] = (k <= i ? a[1][1] : -a[1][1]), s += R[k];
            if ((n == m && s == 0) || (n != m && s % (n - m) == 0)) return work(n, m, 1), 0;
        }
        return cout << "51, 48\n", 0;
    } else if (p && q) {
        for (int i = 1; i <= n; i++) {
            if (a[i][q] == a[1][q]) continue;
            C[q] = 0;
            for (int x : { -1, 1 }) {
                for (int y : { -1, 1 }) {
                    R[1] = C[q] + x * a[1][q];
                    R[i] = C[q] + y * a[i][q];
                    if (Cons(1, i, q)) return work(n, m, 1), 0;
                }
            }
        }
        while (1);
        assert(0);
    } else if (p) {
        for (int i = 1; i <= max(n, m); i++) for (int j = i; j <= max(n, m); j++) swap(a[i][j], a[j][i]);
        swap(p, q), swap(n, m), asdf = 1;
    }
    // p = 0, q != 0
    f[1000000] = 1;
    for (int i = 1; i <= n; i++) g[i] = f << a[i][1], f = g[i] | f >> a[i][1];
    for (int i = 0; i <= 2000000; i++) {
        if (!((n == m && i == 1000000 && f[i]) || (n != m && f[i] && (i - 1000000) % (n - m) == 0))) continue;
        for (int j = n; j; j--) {
            if (g[j][i]) R[j] = a[j][1], i -= a[j][1];
            else R[j] = -a[j][1], i += a[j][1];
        }
        break;
    }
    work(n, m, 1);
    return 0;
}

第三次 memset 没引用 string.h 了。

posted @ 2025-09-01 21:58  forgotmyhandle  阅读(11)  评论(0)    收藏  举报