2024.09 别急记录

1. ARC070F - HonestOrUnkind

发现 \(a\leq b\)\(b\) 内部可以构造出一个好人集合,一定无解;否则有两种情况:

  • \(x\) 认为 \(y\) 是坏人,二者一好一坏,全部删去即可;
  • \(x\) 认为 \(y\) 是好人,那么 \(y\) 一定比 \(x\) 更好。

维护一个栈表示目前越来越好的人,每次取出栈顶询问新人,若为好则将新人放入栈顶;否则将栈顶弹出。最后栈顶一定是一个好人,让他询问一遍所有人即可。

点击查看代码
//AT_arc070_d
#include <bits/stdc++.h>
using namespace std;

int qry(int x, int y){
    if(x == y){
        return 1;
    }
    printf("? %d %d\n", x, y);
    fflush(stdout);
    char s[4];
    scanf("%s", s);
    return s[0] == 'Y';
}
int st[4010];

int main(){
    int n, a, b;
    scanf("%d%d", &a, &b);
    if(a <= b){
        puts("Impossible");
        fflush(stdout);
        return 0;
    }
    n = a + b;
    int tp = 0;
    for(int i = 0; i < n; ++ i){
        if(tp == 0){
            st[++tp] = i;
        } else {
            if(qry(st[tp], i)){
                st[++tp] = i;
            } else {
                -- tp;
            }
        }
    }
    int x = st[tp];
    for(int i = 0; i < n; ++ i){
        st[i] = qry(x, i);
    }
    putchar('!');
    putchar(' ');
    for(int i = 0; i < n; ++ i){
        putchar(st[i] + '0');
    }
    puts("");
    return 0;
}

2. CF843E - Maximum Flow

考虑答案(满流边)相当于一个全部由 1 边组成的割,那么这个最小割是好求的:对于 0 边连 \((u,v,\inf,0)\),1 边连 \((u,v,1,{\color{red}\inf})\) (因为可以退流)。那么跑完后与 \(s\) 连通、不连通的集合的交叉部分的边即为一组最小割。

求得最小割以后把所有 1 边加入图中跑流量 \([1,\inf)\) 的上下界流,之后将除了这个最小割集以外的边容量设为流量 \(+1\) 即可。由于第一步求得的是一个割,所以这个构造是正确的。

点击查看代码
//CF843E
#include <bits/stdc++.h>
using namespace std;

const int N = 110, M = 1e5 + 10, inf = 1000;
int n, m, s, t, mc[M], deg[N];
struct edge{
    int u, v, g;
} E[M];
int hd[N], now[N], dep[N], ln[M], eg[M], nx[M], tot = 1;

void adg(int u, int v, int w){
    eg[++tot] = v;
    ln[tot] = w;
    nx[tot] = hd[u];
    hd[u] = tot;
}
void add(int u, int v, int w, int ww){
    adg(u, v, w);
    adg(v, u, ww);
}
bool bfs(int s, int t){
    memset(dep, 0, sizeof(dep));
    memcpy(now, hd, sizeof(hd));
    queue<int> q;
    dep[s] = 1;
    q.push(s);
    while(!q.empty()){
        int x = q.front();
        q.pop();
        for(int i = hd[x]; i; i = nx[i]){
            int y = eg[i], z = ln[i];
            if(z && !dep[y]){
                dep[y] = dep[x] + 1;
                q.push(y);
                if(y == t){
                    return 1;
                }
            }
        }
    }
    return 0;
}
int dfs(int x, int t, int fl){
    if(x == t){
        return fl;
    }
    int rs = fl;
    for(int i = now[x]; i && rs; i = nx[i]){
        int y = eg[i], z = ln[i];
        now[x] = i;
        if(z && dep[y] == dep[x] + 1){
            int k = dfs(y, t, min(z, rs));
            if(!k){
                dep[y] = 0;
            }
            ln[i] -= k;
            ln[i^1] += k;
            rs -= k;
        }
    }
    return fl - rs;
}
int dinic(int s, int t){
    int mf = 0, tmp;
    while(bfs(s, t)){
        while(tmp = dfs(s, t, inf)){
            mf += tmp;
        }
    }
    return mf;
}

int main(){
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for(int i = 1; i <= m; ++ i){
        scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].g);
        if(E[i].g){
            add(E[i].u, E[i].v, 1, inf);
        } else {
            add(E[i].u, E[i].v, inf, 0);
        }
    }
    printf("%d\n", dinic(s, t));
    bfs(s, t);
    for(int i = 1; i <= m; ++ i){
        if(dep[E[i].u] && !dep[E[i].v] && E[i].g){
            mc[i] = 1;
        }
    }
    tot = 1;
    memset(hd, 0, sizeof(hd));
    for(int i = 1; i <= m; ++ i){
        if(E[i].g){
            add(E[i].u, E[i].v, inf, 0);
            ++ deg[E[i].v];
            -- deg[E[i].u];
        } else {
            tot += 2;
        }
    }
    int S = n + 1, T = n + 2;
    for(int i = 1; i <= n; ++ i){
        if(deg[i] > 0){
            add(S, i, deg[i], 0);
        }
        if(deg[i] < 0){
            add(i, T, -deg[i], 0);
        }
    }
    add(t, s, inf, 0);
    dinic(S, T);
    for(int i = 1; i <= m; ++ i){
        if(E[i].g){
            int k = ln[i*2+1] + 1;
            printf("%d %d\n", k, k + 1 - mc[i]);
        } else {
            puts("0 1");
        }
    }
    return 0;
}

3. 「C.E.L.U-02」 - 苦涩

首先考虑线段树每个节点维护一个堆+区间内 max(不一定是真实的),然后每次添加往 log 个区间内加数(所以需要标记永久化,所以区间 max 不一定是真的),查询是简单的,但是需要使区间堆顶也贡献(区间堆顶=每个片区都有;区间 max=只有一个片区有。两个不同)。修改的时候暴力递归,遇到堆顶=需要删掉的数时将区间与操作区间补集交集的部分递归下去改即可。因为加的区间不多,所以删的也不会多。总复杂度 \(O(n\log^2 n)\)

点击查看代码
//P7476
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int n, m;
struct node{
    int mx;
    priority_queue<int> q;
} t[N*8];

void psu(int p){
    t[p].mx = max({ t[p<<1].mx, t[p<<1|1].mx, t[p].q.top() });
}
void add(int p, int l, int r, int ql, int qr, int v){
    if(qr < l || r < ql){
        return;
    } else if(ql <= l && r <= qr){
        t[p].mx = max(t[p].mx, v);
        t[p].q.push(v);
    } else {
        int mid = l + r >> 1;
        add(p<<1, l, mid, ql, qr, v);
        add(p<<1|1, mid+1, r, ql, qr, v);
        psu(p);
    }
}
void psd(int p, int l, int r, int ql, int qr, int v){
    if(ql <= l && r <= qr){
        return;
    }
    int mid = l + r >> 1;
    if(ql > mid){
        t[p<<1].mx = max(t[p<<1].mx, v);
        t[p<<1].q.push(v);
        psd(p<<1|1, mid+1, r, ql, qr, v);
    } else if(qr <= mid){
        t[p<<1|1].mx = max(t[p<<1|1].mx, v);
        t[p<<1|1].q.push(v);
        psd(p<<1, l, mid, ql, qr, v);
    } else {
        psd(p<<1, l, mid, ql, qr, v);
        psd(p<<1|1, mid+1, r, ql, qr, v);
    }
    psu(p);
}
void del(int p, int l, int r, int ql, int qr, int v){
    if(qr < l || r < ql || t[p].mx < v){
        return;
    } else if(t[p].q.top() == v){
        t[p].q.pop();
        psd(p, l, r, ql, qr, v);
        psu(p);
    } else {
        int mid = l + r >> 1;
        del(p<<1, l, mid, ql, qr, v);
        del(p<<1|1, mid+1, r, ql, qr, v);
        psu(p);
    }
}
int qry(int p, int l, int r, int ql, int qr){
    if(qr < l || r < ql){
        return -1;
    } else if(ql <= l && r <= qr){
        return t[p].mx;
    } else {
        int mid = l + r >> 1;
        return max({ qry(p<<1, l, mid, ql, qr),
                     qry(p<<1|1, mid+1, r, ql, qr),
                     t[p].q.top() });
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 0; i < N*4; ++ i){
        t[i].q.push(-1);
        t[i].mx = -1;
    }
    for(int i = 1; i <= m; ++ i){
        int op, l, r, k;
        scanf("%d%d%d", &op, &l, &r);
        if(op == 1){
            scanf("%d", &k);
            add(1, 1, n, l, r, k);
        } else if(op == 2){
            int p = qry(1, 1, n, l, r);
            if(p != -1){
                del(1, 1, n, l, r, p);
            }
        } else {
            printf("%d\n", qry(1, 1, n, l, r));
        }
    }
    return 0;
}

4. CF1009F - Dominant Indices

有 dp 式:\(f_x(i)=\sum f_y(i-1)\)。可以使用长链剖分优化做到 \(O(n)\):每次转移直接继承重儿子,轻儿子暴力。

解决“直接继承”的方法是开一个 *f[N],buf[N] 然后每次遍历到节点将 buf 中一个连续的 \(maxdep-dep\)\([l,r]\) 分配给这个 \(f\),然后将 \([l+1,r]\) 分配给重儿子,以此类推即可。

点击查看代码
//CF1009F
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int n, dep[N], mx[N], son[N];
int buf[N*2], ans[N], *f[N], *now = buf;
vector<int> g[N];

void dfs(int x, int fa){
    mx[x] = dep[x] = dep[fa] + 1;
    for(int i : g[x]){
        if(i != fa){
            dfs(i, x);
            if(mx[i] > mx[x]){
                son[x] = i;
                mx[x] = mx[i];
            }
        }
    }
}
void calc(int x){
    f[x][0] = 1;
    if(son[x]){
        f[son[x]] = f[x] + 1;
        calc(son[x]);
        ans[x] = ans[son[x]] + 1;
    }
    for(int y : g[x]){
        if(y == son[x] || dep[y] < dep[x]){
            continue;
        }
        f[y] = now;
        now += mx[y] - dep[y] + 1;
        calc(y);
        for(int i = 1; i <= mx[y] - dep[y] + 1; ++ i){
            f[x][i] += f[y][i-1];
            if(f[x][i] > f[x][ans[x]] ||
               (f[x][i] == f[x][ans[x]] && i < ans[x])){
                ans[x] = i;
            }
        }
    }
    if(f[x][ans[x]] == 1){
        ans[x] = 0;
    }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i < n; ++ i){
        int x, y;
        scanf("%d%d", &x, &y);
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(1, 0);
    f[1] = now;
    now += mx[1] - dep[1] + 1;
    calc(1);
    for(int i = 1; i <= n; ++ i){
        printf("%d\n", ans[i]);
    }
    return 0;
}

5. AHOI2022 - 山河重整

首先有显然的 \(O(n^2)\) dp:设 \(f_{i,j}\) 表示前 \(i\) 个数至多可以表示 \([1,j]\) 的方案数,转移即对于 \(k\in(i,j+1],f_{i,j}\to f_{k,j+k}\)

那么这个感觉不好优化。考虑容斥。设 \(f_i\) 表示选取 \([1,i]\) 的数能够表示完 \([1,i]\) 的方案数,那么答案有 \(ans=2^n-\sum_{i\in[0,n)}2^{n-i-1}f_i\)

转移为 \(f_n=g_n-\sum_{i=1}^{n-1}f_i\operatorname{cal}(i,n)\)。其中 \(g_n\) 表示选取若干个不同的正整数和为 \(n\) 的方案数;\(\operatorname{cal}(i,n)\) 表示选取若干个不同的在 \([i+2,n]\) 之间的正整数和为 \(n-i\) 的方案数。观察到这个数不为 \(0\) 时有 \(2+2i\leq n\)

考虑 \(g_n\) 怎么求:我们将选的数写成方格,如图,其中每一列表示一个数。

容易发现列数为 \(O(\sqrt n)\) 级别的,因为选的数互不相同。所以可以不按列 dp 而是按行 dp。要求是行长度覆盖 \([1,\max]\) 的每一个数,即可做到 \(O(n\sqrt n)\) 求解。


for(int i = sqrt(n * 2) + 3; i >= 1; -- i){
  for(int j = n; j >= i; -- j){
    g[j] = g[j-i];
  }
  upd(g[i], 1);
  for(int j = i; j <= n; ++ j){
    upd(g[j], g[j-i]);
  }
}
g[0] = 1;

接下来设 \(h_n=\sum_{i=1}^{n-1}f_i\operatorname{cal}(i,n)=\sum_{i=1}^{n/2-1}f_i\operatorname{cal}(i,n)\)。考虑沿用上述 dp 方式。那么对于每个 \(h_n\),初始若有一个方案数为 \(f_j\),占用空间为 \(j+(j+2)*i\)\(j+2\) 行方格,那么接下来可以同样进行 dp。dp 顺序为:

  • 遍历每一行方格数 \(i\)(真实意义为选多少个数)。
  • 必须有一个 \(i\),令 \(g_{j-i}\to g_j\)
  • 对于 \(j+(j+2)i\leq n\)\(j\) 增加一个初始状态,方案数为 \(f_j\),目前占用空间为 \(j+(j+2)i\)\(j\) 是为了将终止状态的 \(n-j\) 变为 \(n\))。
  • 更新。

最后用 \(h\) 更新 \(f\) ,但是 \(h,f\) 会相互贡献,发现对 \(h_i\) 有贡献的 \(f_j\)\(2j<i\),于是我们每次求 \([1,2),[2,4),[4,8),...\) 内的 \(f\) 即可。

点击查看代码
//P8340
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
int n;
typedef long long ll;
int f[N], P, pw[N], g[N];

void upd(int &x, int y){
    x += y;
    if(x >= P){
        x -= P;
    }
}

void calc(int n){
    if(n <= 1){
        return;
    }
    calc(n >> 1);
    memset(g, 0, sizeof(g));
    for(int i = sqrt(n * 2) + 3; i >= 1; -- i){
        for(int j = n; j >= i; -- j){
            g[j] = g[j-i];
        }
        for(int j = 0; i*(j+2)+j <= n; ++ j){
            upd(g[i*(j+2)+j], f[j]);
        }
        for(int j = i; j <= n; ++ j){
            upd(g[j], g[j-i]);
        }
    }
    for(int i = (n >> 1) + 1; i <= n; ++ i){
        upd(f[i], P - g[i]);
    }
}

int main(){
    scanf("%d%d", &n, &P);
    pw[0] = 1;
    for(int i = 1; i <= n; ++ i){
        upd(pw[i], pw[i-1]);
        upd(pw[i], pw[i-1]);
    }
    for(int i = sqrt(n * 2) + 3; i >= 1; -- i){
        for(int j = n; j >= i; -- j){
            f[j] = f[j-i];
        }
        upd(f[i], 1);
        for(int j = i; j <= n; ++ j){
            upd(f[j], f[j-i]);
        }
    }
    f[0] = 1;
    calc(n);
    int ans = 0;
    for(int i = 0; i < n; ++ i){
        upd(ans, (ll)f[i] * pw[n-i-1] % P);
    }
    printf("%d\n", (pw[n] + P - ans) % P);
    return 0;
}

6. JOISC 2022/2023 - Two Currencies

将每条路径绑定到更深的节点上,使用可持久化线段树维护每个节点到根路径所有银币数,查询则在 \(rt_x,rt_y,-2*rt_{lca}\) 三棵线段树上二分查询至多多少个数和 \(\leq y\) 即可。

点击查看代码
//qoj6332
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
typedef long long ll;
int n, m, q, p[N], c[N], to[N], fat[N][20], dep[N], cnt, rt[N];
vector<pair<int, int> > g[N];
pair<int, int> cc[N];
vector<int> cp[N];

struct node{
    ll val;
    int sum, l, r;
} t[N*40];

void upd(int p){
    t[p].val = t[t[p].l].val + t[t[p].r].val;
    t[p].sum = t[t[p].l].sum + t[t[p].r].sum;
}
void add(int &p, int l, int r, int x, ll v){
    ++ cnt;
    t[cnt] = t[p];
    p = cnt;
    if(l == r){
        t[p].val += v;
        ++ t[p].sum;
    } else {
        int mid = l + r >> 1;
        if(x <= mid){
            add(t[p].l, l, mid, x, v);
        } else {
            add(t[p].r, mid+1, r, x, v);
        }
        upd(p);
    }
}
int qx(int px, int py, int pz, int l, int r, ll y){
    if(l == r){
        return l;
    } else {
        int mid = l + r >> 1;
        ll val = t[t[px].l].val + t[t[py].l].val - 2 * t[t[pz].l].val;
        if(val > y){
            return qx(t[px].l, t[py].l, t[pz].l, l, mid, y);
        } else {
            return qx(t[px].r, t[py].r, t[pz].r, mid+1, r, y-val);
        }
    }
}
int qy(int px, int py, int pz, int l, int r, int ql, int qr){
    if(qr < l || r < ql){
        return 0;
    } else if(ql <= l && r <= qr){
        return t[px].sum + t[py].sum - 2 * t[pz].sum;
    } else {
        int mid = l + r >> 1;
        return qy(t[px].l, t[py].l, t[pz].l, l, mid, ql, qr) + 
               qy(t[px].r, t[py].r, t[pz].r, mid+1, r, ql, qr);
    }
}

void pre(int x, int fa){
    fat[x][0] = fa;
    for(int i = 1; i < 20; ++ i){
        fat[x][i] = fat[fat[x][i-1]][i-1];
    }
    dep[x] = dep[fa] + 1;
    for(auto i : g[x]){
        if(i.first == fa){
            continue;
        }
        to[i.second] = i.first;
        pre(i.first, x);
    }
}
int lca(int x, int y){
    if(dep[x] > dep[y]){
        swap(x, y);
    }
    for(int i = 19; i >= 0; -- i){
        if(dep[fat[y][i]] >= dep[x]){
            y = fat[y][i];
        }
    }
    if(x == y){
        return x;
    }
    for(int i = 19; i >= 0; -- i){
        if(fat[x][i] != fat[y][i]){
            x = fat[x][i];
            y = fat[y][i];
        }
    }
    return fat[x][0];
}
void dfs(int x){
    for(auto i : g[x]){
        int y = i.first;
        if(y == fat[x][0]){
            continue;
        }
        rt[y] = rt[x];
        for(int j : cp[y]){
            add(rt[y], 1, m+1, j, cc[j].first);
        }
        dfs(y);
    }
}

int main(){
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i < n; ++ i){
        int x, y;
        scanf("%d%d", &x, &y);
        g[x].emplace_back(y, i);
        g[y].emplace_back(x, i);
    }
    pre(1, 0);
    for(int i = 1; i <= m; ++ i){
        scanf("%d%d", &p[i], &c[i]);
        cc[i] = make_pair(c[i], i);
    }
    sort(cc + 1, cc + m + 1);
    for(int i = 1; i <= m; ++ i){
        c[i] = lower_bound(cc + 1, cc + m + 1, make_pair(c[i], i)) - cc;
        cp[to[p[i]]].push_back(c[i]);
    }
    add(rt[1], 1, m+1, m+1, 1e18);
    dfs(1);
    while(q--){
        int s, t, x;
        ll y;
        scanf("%d%d%d%lld", &s, &t, &x, &y);
        int pos = qx(rt[s], rt[t], rt[lca(s, t)], 1, m+1, y);
        int ans = qy(rt[s], rt[t], rt[lca(s, t)], 1, m+1, pos, m+1);
        printf("%d\n", max(-1, x - ans));
    }
    return 0;
}

7. CF1884E - Hard Design

第一问简单;第二问相当于要求 \(\sum\max\{a_{[i,j]}\}\)。对于一个序列显然可以一遍单调栈;但是现在是一个环,所以不妨假设环的最大值在最右侧,那么所有跨过最大值的 \(\max\) 是好算的;剩下部分是一个前缀、一个后缀。

点击查看代码
//CF1884E
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e6 + 10;
const ll P = 1e9 + 7;
ll pr[N], sf[N], ans[N], sm, a[N], mx;
int st[N], cnt[N], tp;
int T, n, ps;

int main(){
    scanf("%d", &T);
    while(T--){
        mx = ps = sm = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++ i){
            scanf("%lld", &a[i]);
            a[i+n] = a[i];
            if(a[i] > mx){
                mx = a[i];
                ps = i;
            }
            sm += a[i];
        }
        sm %= P;
        ll tmp = 0;
        tp = 0;
        for(int i = ps + 1; i <= ps + n; ++ i){
            int tc = 1;
            while(tp && a[st[tp]] <= a[i]){
                tc += cnt[tp];
                tmp = (tmp + P - a[st[tp]] * 1ll * cnt[tp] % P) % P;
                -- tp;
            }
            st[++tp] = i;
            cnt[tp] = tc;
            tmp = (tmp + a[st[tp]] * 1ll * cnt[tp]) % P;
            pr[i] = (pr[i-1] + tmp) % P;
        }
        tmp = tp = 0;
        for(int i = 0; i <= n+n; ++ i){
            cnt[i] = st[i] = 0;
        }
        for(int i = ps + n; i >= ps + 1; -- i){
            int tc = 1;
            while(tp && a[st[tp]] <= a[i]){
                tc += cnt[tp];
                tmp = (tmp + P - a[st[tp]] * 1ll * cnt[tp] % P) % P;
                -- tp;
            }
            st[++tp] = i;
            cnt[tp] = tc;
            tmp = (tmp + a[st[tp]] * 1ll * cnt[tp]) % P;
            sf[i] = (sf[i+1] + tmp) % P;
            ans[i] = (sf[i] + pr[i-1] + mx * 1ll * (i-ps-1) % P * (n-i+ps+1)) % P;
            if(i - n >= 1){
                ans[i-n] = ans[i];
            }
        }
        ll sum = 0;
        #define cal(i) max(0ll, a[i-1]-a[i])
        for(int i = 1; i < n; ++ i){
            sum += cal(i);
        }
        for(int i = 1; i <= n; ++ i){
            sum -= cal(i);
            sum += cal(i+n-1);
            ll rs = (mx * 1ll * n % P * n % P + P + P - ans[i] - ans[i] + sm) % P;
            printf("%lld %lld\n", sum + mx - a[i], rs);
        }
        for(int i = 0; i <= n+n; ++ i){
            pr[i] = sf[i] = ans[i] = a[i] = st[i] = cnt[i] = 0;
        }
    }
    return 0;
}

8. CF1622F - Quadratic Set

答案 \(\geq n-3\)。对于 \(n\) 为偶数,可以有删去 \(n/2\) 的方式使得非平方因数只可能有一个 \(2\),所以两步以内解决;对于奇数,删去 \(n\) 转化为偶数的问题。

于是可以利用哈希判定是否存在 \(\geq n-2\) 的方案即可。

posted @ 2024-09-05 15:47  KiharaTouma  阅读(10)  评论(0编辑  收藏  举报