20250729

T1

涂抹油漆

两个不交矩形一定有一条分界线使得一个完全在一边,另一个完全在另一边。按各种方向枚举分界线,暴力求出分界线的扩张对范围扩大的那一半的影响,只需要枚举与之平行的另一边界再垂直地枚举算一遍最大子段和即可。因此对于每条分界线可以求出其两边的答案,加起来即可。

代码
#include <iostream>
#include <string.h>
using namespace std;
const int N = 501;
int n, K, ini;
int a[505][505];
int pre[505][505];
int fu[505], fd[505];
int Solve() {
    memset(pre, 0, sizeof pre);
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) 
            pre[i][j] = a[i][j] + pre[i][j - 1];
    }
    memset(fd, 0, sizeof fd);
    memset(fu, 0, sizeof fu);
    for (int i = 1; i <= N; i++) {
        for (int k = 1; k <= i; k++) {
            int mn = 0, cur = 0;
            for (int j = 1; j <= N; j++) {
                cur += pre[j][i] - pre[j][k - 1];
                fd[i] = max(fd[i], cur - mn);
                mn = min(mn, cur);
            }
        }
    }
    for (int i = 1; i <= N; i++) {
        for (int k = i; k <= N; k++) {
            int mn = 0, cur = 0;
            for (int j = 1; j <= N; j++) {
                cur += pre[j][k] - pre[j][i - 1];
                fu[i] = max(fu[i], cur - mn);
                mn = min(mn, cur);
            }
        }
    }
    int ret = 0;
    for (int i = 1; i <= N; i++) fd[i] = max(fd[i], fd[i - 1]);
    for (int i = N; i; i--) fu[i] = max(fu[i], fu[i + 1]);
    for (int i = 0; i <= N; i++) ret = max(ret, fd[i] + fu[i + 1]);
    return ret;
}
int main() {
    freopen("paint.in", "r", stdin);
    freopen("paint.out", "w", stdout);
    cin >> n >> K;
    for (int i = 1; i <= n; i++) {
        int l, r, u, d;
        cin >> l >> d >> r >> u;
        ++l, ++r, ++u, ++d;
        a[l][d]++, a[r][d]--, a[l][u]--, a[r][u]++;
    }
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) 
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
    }
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            if (a[i][j] == K) 
                a[i][j] = -1, ++ini;
            else if (a[i][j] == K - 1) 
                a[i][j] = 1;
            else 
                a[i][j] = 0;
        }
    }
    int ans = Solve();
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j < i; j++) 
            swap(a[i][j], a[j][i]);
    }
    cout << ini + max(ans, Solve()) << "\n";
    return 0;
}

T2

数数

数位 dp,里面 dp 套 dp 记下当前 LIS dp 的状态数组,由于差分只有 01 因此状压即可。由于平方依赖一次依赖个数,因此三个都要算。战役都是平凡的。

一个数数位里的最长上升子序列可以包含 0!!!最长的 LIS 长度是 10 而不是 9!!!

代码
#include <iostream>
#include <string.h>
#include <cassert>
#include <random>
#include <array>
#define int long long
using namespace std;
const int P = 1000000007;
random_device rd;
mt19937 mtrand(rd());
int s, p;
array<int, 3> dp[20][1105];
bool vis[20][1105];
int d[20], dcnt;
int f[11];
int pw[45];
int work(int S, int x) {
    ++x;
    if (!x) 
        return S;
    memset(f, 0, sizeof f);
    for (int i = 0; i <= 9; i++) f[i + 1] = f[i] + ((S >> i) & 1);
    f[x] = f[x - 1] + 1;
    for (int i = x + 1; i <= 10; i++) f[i] = max(f[i], f[i - 1]);
    S = 0;
    for (int i = 0; i <= 9; i++) S |= ((f[i + 1] - f[i]) << i);
    return S;
}
array<int, 3> dfs(int x, int S, bool lim, bool ld) {
    if (x == -1) {
        if (__builtin_popcountll(S) == s) 
            return array<int, 3> { 1, 0, 0 };
        else 
            return array<int, 3> { 0, 0, 0 };
    }
    if (vis[x][S] && !lim) 
        return dp[x][S];
    array<int, 3> ret{ 0, 0, 0 };
    for (int i = 0; i <= (lim ? d[x] : 9); i++) {
        int T = work(S, i - (i == 0 && ld));
        array<int, 3> t = dfs(x - 1, T, lim & (i == d[x]), ld & (i == 0));
        int q = i * pw[x] % P;
        ret[0] += t[0] % P;
        ret[1] += (t[1] + t[0] * q % P) % P;
        ret[2] += (t[2] + 2 * t[1] * q % P + q * q % P * t[0] % P) % P;
    }
    ret[0] %= P, ret[1] %= P, ret[2] %= P;
    if (!lim) 
        vis[x][S] = 1, dp[x][S] = ret;
    return ret;
}
int calc(int x) {
    memset(vis, 0, sizeof vis);
    dcnt = -1;
    while (x) d[++dcnt] = x % 10, x /= 10;
    return dfs(dcnt, 0, 1, 1)[p];
}
signed main() {
    freopen("count.in", "r", stdin);
    freopen("count.out", "w", stdout);
    pw[0] = 1;
    for (int i = 1; i < 45; i++) pw[i] = pw[i - 1] * 10 % P;
    int tc;
    cin >> tc;
    while (tc--) {
        int l, r, aaa;
        cin >> l >> r >> s >> p;
        cout << (P + calc(r) - calc(l - 1)) % P << "\n";
    }
    return 0;
}

T3

升升降降

本质不同的 \(w\) 只有 \(\mathcal{O}(n)\) 种,枚举并判断。考虑相当于每次把一些操作删掉,然后问你当前状态是否合法以及答案。那么现在需要维护操作序列。对于一段操作序列,考虑让 \([0, m]\) 里的数经过它。显然小的一些东西要被缩成一个,大的一些东西也要缩成一个,也就是说只有一段区间的数能够进去之后完好无损(在这一段操作中既没有被 chkmin 过也没有被 chkmax 过)地出来。我们发现如果要合并两个操作序列的话,只需要知道这两个序列分别的 进去之后能够完好无损地出来 的数的区间,以及这些东西出来之后变成了什么区间。于是这样我们就可以轻松合并两个操作序列,于是直接上线段树即可。剩下的都是平凡的。

代码
#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m, V;
pair<char, int> a[100005];
int t[100005], p[100005];
struct node {
    int xl, xr, yl, yr;
} T[400005];
node operator+(node x, node y) {
    if (x.yl > y.xr) {
        y.xl = y.xr = m;
        y.yl = y.yr;
        return y;
    }
    if (x.yr < y.xl) {
        y.xl = y.xr = m;
        y.yr = y.yl;
        return y;
    }
    if (x.yr > y.xr) {
        int t = x.yr - y.xr;
        x.xr -= t, x.yr -= t;
    }
    if (x.yl < y.xl) {
        int t = y.xl - x.yl;
        x.xl += t, x.yl += t;
    }
    x.yl += (y.yl - y.xl);
    x.yr += (y.yl - y.xl);
    return x;
}
void Build(int o, int l, int r) {
    T[o] = (node) { 0, m, 0, m };
    if (l == r) 
        return;
    int mid = (l + r) >> 1;
    Build(o << 1, l, mid);
    Build(o << 1 | 1, mid + 1, r);
}
void Change(int o, int l, int r, int x, node y) {
    if (l == r) 
        return T[o] = y, void();
    int mid = (l + r) >> 1;
    if (x <= mid) 
        Change(o << 1, l, mid, x, y);
    else 
        Change(o << 1 | 1, mid + 1, r, x, y);
    T[o] = T[o << 1] + T[o << 1 | 1];
}
int chk() {
    node x = T[1];
    if (x.yl < V && V < x.yr) 
        return 1;
    else if (x.yl == V) 
        return x.xl + 1;
    else if (x.yr == V) 
        return m - x.xr + 1;
    else 
        return -1;
}
int main() {
    freopen("c.in", "r", stdin);
    freopen("c.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m >> V;
    Build(1, 1, n);
    for (int i = 1; i <= n; i++) cin >> a[i].first >> a[i].second;
    sort(a + 1, a + n + 1, [](pair<char, int> x, pair<char, int> y) { return x.second < y.second; });
    for (int i = 1; i <= n; i++) {
        if (a[i].first == '+') 
            Change(1, 1, n, i, (node) { 0, m - 1, 1, m });
        else 
            Change(1, 1, n, i, (node) { 1, m, 0, m - 1 });
    }
    a[0].second = inf;
    for (int i = 1; i <= n; i++) t[i] = a[i].second - a[i - 1].second, p[i] = i;
    sort(p + 1, p + n + 1, [](int x, int y) { return t[x] < t[y]; });
    if (chk() != -1) {
        cout << "infinity\n";
        return 0;
    }
    for (int i = n; i; i--) {
        Change(1, 1, n, p[i], (node) { 0, m, 0, m });
        if (t[p[i]] == t[p[i - 1]]) 
            continue;
        int x = chk();
        if (x != -1) {
            cout << t[p[i]] << " " << x << "\n";
            return 0;
        }
    }
    return 0;
}

T4

蜘蛛爬树

首先变成选择一个点 \(x\) 使得 \(dist(s, x) + dist(x, t) + a_x \times v\) 最小,其中 \(v\)\(s\)\(t\) 中间差了几棵树。考虑把贡献分成 LCA 子树内和子树外分别计算。子树外的容易点分治李超树 \(2\log\),考虑子树内。重剖一下,对于重链上的每个点,维护其(所有轻子树中的所有点和它自己)这些直线构成的李超树,然后树剖线段树套李超树即可 \(3\log\) 维护这一部分答案。还有一部分是这个询问经过的所有重链在询问链种最深点的重儿子产生的贡献。这个直接树上李超树合并即可 \(2\log\) 维护。

对于 \(3\log\) 部分,考虑重剖的时候除了最后一条重链,其他重链交询问链都是自己的一段前缀,因此对于这些重链,离线扫描每个前缀,扫到的时候维护出到这个前缀的李超树,然后把挂在这个点上的询问都回答一遍。对于最后一条重链,我们还是线段树套李超树。容易发现这样就变成 \(2\log\) 了,因为需要线段树套李超树做的区间询问从 \(q\log\) 个变成了 \(q\) 个,然后就做完了。

代码
#include <iostream>
#include <string.h>
#include <vector>
#include <array>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f, N = 1000000005;
inline void Cmin(int &x, int y) { x = min(x, y); }
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
char buf[1<<21], *p1, *p2, ch;
long long read() {
    long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
    while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
    while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
    return ret * (neg ? -1 : 1);
}
struct Line {
    int k, b;
    Line(int x = 0, int y = 0) { k = x, b = y; }
    inline int operator()(int x) { return k * x + b; }
};
struct node {
    Line x;
    int l, r;
} T[2000005];
struct LiChao_Segment_Tree {
    int ncnt;
    void clear() { ncnt = 0; }
    int New(Line x) {
        T[++ncnt] = (node) { x, 0, 0 };
        return ncnt;
    }
    void Insert(int &o, int l, int r, Line x) {
        if (!o) 
            return o = New(x), void();
        int mid = (l + r) >> 1;
        if (T[o].x(mid) > x(mid)) 
            swap(T[o].x, x);
        int L = T[o].x(l), R = T[o].x(r), xl = x(l), xr = x(r);
        if (L > xl) 
            Insert(T[o].l, l, mid, x);
        if (R > xr) 
            Insert(T[o].r, mid + 1, r, x);
    }
    int Query(int o, int l, int r, int x) {
        if (!o) 
            return inf;
        if (l == r) 
            return T[o].x(x);
        int mid = (l + r) >> 1, ret = T[o].x(x);
        if (x <= mid) 
            ret = min(ret, Query(T[o].l, l, mid, x));
        else 
            ret = min(ret, Query(T[o].r, mid + 1, r, x));
        return ret;
    }
    int Merge(int p, int q, int l = 0, int r = N) {
        if (!p || !q) 
            return p | q;
        if (l == r) 
            return (T[p].x(l) < T[q].x(r)) ? p : q;
        Insert(p, l, r, T[q].x);
        int mid = (l + r) >> 1;
        T[p].l = Merge(T[p].l, T[q].l, l, mid);
        T[p].r = Merge(T[p].r, T[q].r, mid + 1, r);
        return p;
    }
} seg;
long long n, m, Q;
long long a[200005];
int head[200005], nxt[400005], to[400005], ew[400005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int _dfn[200005], dfn[200005], top[200005], son[200005], f[200005], sz[200005], dfncnt;
int dist[200005], dep[200005], len[200005];
void dfs1(int x, int fa, int d) {
    dep[x] = d;
    f[x] = fa;
    sz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dist[v] = dist[x] + ew[i];
            dfs1(v, x, d + 1);
            sz[x] += sz[v];
            if (sz[v] > sz[son[x]]) 
                son[x] = v;
        }
    }
}
void dfs2(int x, int t) {
    top[x] = t;
    _dfn[dfn[x] = ++dfncnt] = x;
    if (!son[x]) {
        len[top[x]] = dep[x] - dep[top[x]] + 1;
        return;
    }
    dfs2(son[x], t);
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != f[x] && v != son[x]) 
            dfs2(v, v);
    }
}
int LCA(int x, int y) {
    while (top[x] ^ top[y]) (dep[top[x]] < dep[top[y]]) ? (y = f[top[y]]) : (x = f[top[x]]);
    return (dep[x] < dep[y] ? x : y);
}
int ans[200005];
namespace P_DC {
    int rt, msz, all, trt;
    vector<array<int, 3> > vec[200005], tmp;
    int sz[200005];
    bool mark[200005];
    void getroot(int x, int fa, int d = -inf) {
        if (d >= 0) {
            seg.Insert(trt, 0, N, Line(a[x], d * 2));
            for (auto v : vec[x]) {
                v[1] += d * 2;
                tmp.emplace_back(v);
                v[1] -= d * 2;
            }
        }
        int mx = 0;
        sz[x] = 1;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (!mark[v] && v != fa) {
                getroot(v, x, (d >= 0 ? d + ew[i] : d));
                sz[x] += sz[v];
                mx = max(mx, sz[v]);
            }
        }
        mx = max(mx, all - sz[x]);
        if (mx < msz) 
            rt = x, msz = mx;
    }
    void dfs(int x) {
        mark[x] = 1;
        seg.clear();
        trt = 0;
        getroot(x, 0, 0);
        for (auto v : tmp) Cmin(ans[v[0]], seg.Query(trt, 0, N, v[2]) + v[1]);
        tmp.clear();
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (!mark[v]) {
                all = sz[v], msz = n + 1;
                getroot(v, x);
                dfs(rt);
            }
        }
    }
    void work() {
        all = msz = n;
        getroot(1, 0);
        dfs(rt);
    }
}
int rt[200005];
void Get(int x, int p) {
    rt[p] = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != f[x] && v != son[x]) {
            for (int j = dfn[v]; j < dfn[v] + sz[v]; j++) {
                int w = _dfn[j];
                seg.Insert(rt[p], 0, N, Line(a[w], 2 * (dist[w] - dist[x])));
            }
        }
    }
    seg.Insert(rt[p], 0, N, Line(a[x], 0));
}
struct Segment_Tree {
    vector<array<int, 3> > vec[800005];
    int rt[800005];
    void Clear(int x) { for (int i = 0; i <= x * 4; i++) vec[i].clear(); }
    void Add(int o, int l, int r, int L, int R, array<int, 3> v) {
        if (L <= l && r <= R) 
            return vec[o].emplace_back(v);
        int mid = (l + r) >> 1;
        if (L <= mid) 
            Add(o << 1, l, mid, L, R, v);
        if (R > mid) 
            Add(o << 1 | 1, mid + 1, r, L, R, v);
    }
    void Answer(int o, int l, int r) {
        if (l == r) 
            rt[o] = ::rt[l];
        else {
            int mid = (l + r) >> 1;
            Answer(o << 1, l, mid);
            Answer(o << 1 | 1, mid + 1, r);
            rt[o] = seg.Merge(rt[o << 1], rt[o << 1 | 1]);
        }
        for (auto v : vec[o]) Cmin(ans[v[0]], seg.Query(rt[o], 0, N, v[2]) + v[1]);
    }
} Seg;
long long X[200005], Y[200005];
vector<array<int, 3> > vec1[200005];
vector<pair<pair<int, int>, array<int, 3> > > vec2[200005];
vector<array<int, 3> > vec3[200005];
void Query(int x, int y, array<int, 3> z) {
    while (top[x] ^ top[y]) {
        if (dep[top[x]] < dep[top[y]]) 
            swap(x, y);
        vec1[dfn[x]].emplace_back(z);
        vec3[x].emplace_back(z);
        x = f[top[x]];
    }
    (dep[x] > dep[y]) ? swap(x, y) : void();
    vec2[top[x]].emplace_back(make_pair(dfn[x], dfn[y]), z);
    vec3[y].emplace_back(z);
}
void work() {
    memset(rt, 0, sizeof rt);
    for (int x = 1; x <= n; x++) {
        if (top[x] == x) {
            seg.clear();
            for (int i = dfn[x]; i < dfn[x] + len[x]; i++) {
                Get(_dfn[i], i);
                if (i != dfn[x]) 
                    rt[i] = seg.Merge(rt[i], rt[i - 1]);
                for (auto v : vec1[i]) Cmin(ans[v[0]], seg.Query(rt[i], 0, N, v[2]) + v[1]);
            }
            Seg.Clear(len[x]);
            seg.clear();
            for (int i = dfn[x]; i < dfn[x] + len[x]; i++) Get(_dfn[i], i - dfn[x] + 1);
            for (auto v : vec2[x]) Seg.Add(1, 1, len[x], v.first.first - dfn[x] + 1, v.first.second - dfn[x] + 1, v.second);
            Seg.Answer(1, 1, len[x]);
        }
    }
}
void dfs(int x) {
    rt[x] = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != f[x]) {
            dfs(v);
            rt[x] = seg.Merge(rt[x], rt[v]);
        }
    }
    seg.Insert(rt[x], 0, N, Line(a[x], 2 * dist[x]));
    for (auto v : vec3[x]) Cmin(ans[v[0]], seg.Query(rt[x], 0, N, v[2]) + v[1] - 2 * dist[x]);
}
signed main() {
    freopen("spider.in", "r", stdin);
    freopen("spider.out", "w", stdout);
    memset(ans, 63, sizeof ans);
    n = read(), m = read(), Q = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (long long i = 1, u, v, ww; i < n; i++) {
        u = read(), v = read(), ww = read();
        add(u, v, ww);
        add(v, u, ww);
    }
    dfs1(1, 0, 1);
    dfs2(1, 1);
    for (int i = 1; i <= Q; i++) {
        X[i] = read(), Y[i] = read();
        int s = (X[i] - 1) % n + 1, t = (Y[i] - 1) % n + 1;
        int x = LCA(s, t);
        array<int, 3> tmp{ i, dist[s] + dist[t] - 2 * dist[x], abs(((X[i] - 1) / n - (Y[i] - 1) / n)) };
        Query(s, t, tmp);
        P_DC::vec[x].emplace_back(tmp);
        vec3[s].emplace_back(tmp), vec3[t].emplace_back(tmp);
    }
    P_DC::work();
    seg.clear();
    work();
    seg.clear();
    dfs(1);
    for (int i = 1; i <= Q; i++) cout << ans[i] << "\n";
    return 0;
}
posted @ 2025-07-30 23:58  forgotmyhandle  阅读(19)  评论(0)    收藏  举报