OI笔记 | 2023.1-2 做题记录(二)

Sports Festival

AGC018B

Takahashi举办了一场运动会,这场运动会有 $M$ 个项目,有 $N$ 个人来参加。

给你一个 $N\times M$ 的二维数组 $a$,其中 $a[i][j]$ 表示第 $i$ 个人,她心目中排名第 $j$ 的项目是哪个。

这 $M$ 个项目不一定全都要进行,可以选其中一些项目进行,剩下的都鸽掉,当然肯定不能鸽掉所有 $M$ 个项目。

每个人会在所有开展的项目当中,选择她心目中排名最高的那个项目参加。

因此,如果开展全部项目的话,可能某个项目的人数会爆多无比,所以Takahashi决定,只开展其中的一部分项目,使得参加人数最多的那个项目,参加人数尽量少。

请输出这个值。

题解

考虑全选时,参加人数最多的项目为 $del$,它的参加人数为 $x$。

显然一旦我们开展这个任务,答案一定为 $x$。不开展其他项目对这件事没有影响,不会减少答案。

所以我们只能尝试不开展这个项目 $del$,这样之后答案不大于 $x$。

统计答案,直到删光,时间复杂度 $O(nm)$。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 305, INF = (1 << 30);
int a[MAX_N][MAX_N], now[MAX_N], cnt[MAX_N], vis[MAX_N];

inline ll read() {...}
inline void write(ll x) {...}

int main() {
    int n = read(), m = read(), ans = INF;
    for(int i = 1; i <= n; i++) {
        now[i] = 1;
        for(int j = 1; j <= m; j++)
            a[i][j] = read();
    }
    while(1) {
        int del = 0;
        for(int i = 1; i <= m; i++)
            cnt[i] = 0;
        for(int i = 1; i <= n; i++) {
            while(now[i] <= m && vis[a[i][now[i]]]) now[i]++;
            if(now[i] > m)  continue;
            int x = a[i][now[i]];
            cnt[x]++;
            if(cnt[x] > cnt[del])   del = x;
        }
        if(!del)    break;      
        vis[del] = 1;
        ans = min(ans, cnt[del]); 
    }
    write(ans);
    return 0;
}

Fox And Names

CF510C

有一些按照某个被改变的字典序排序后的字符串,请求出其字典序。

(存在多组解,请输出任意一组)

否则输出 Impossible(请注意大小写)

题解

拓扑排序。

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
char s[105][105];
int in[27];
bool G[27][27];
vector<int> ans;

inline ll read() {...}
inline void write(ll x) {...}

bool topo() {
    queue<int> s;
    for(int i = 0; i < 26; i++)
        if(!in[i])  s.push(i);
    while(!s.empty()) {
        int u = s.front();
        s.pop();
        ans.pb(u);
        for(int v = 0; v < 26; v++) {
            if(!G[u][v])    continue;
            if(--in[v] == 0)    s.push(v);
        }
    }
    return (ans.size() == 26);
}

int main() {
    int n = read();
    for(int i = 1; i <= n; i++)
        scanf("%s", s[i] + 1);
    for(int i = 1; i < n; i++) {
        int len1 = strlen(s[i] + 1), len2 = strlen(s[i + 1] + 1), k = 1;
        while(k < min(len1, len2) && s[i][k] == s[i + 1][k])
            k++;
        int u = s[i][k] - 'a', v = s[i + 1][k] - 'a';
        if(u == v && len1 <= len2)  continue;
        if(!G[u][v]) {
            G[u][v] = 1;
            in[v]++;
        }
    }
    if(topo()) {
        for(int u : ans)
            putchar(u + 'a');
        putchar('\n');
    }
    else    puts("Impossible");
    return 0;
}

ρars/ey

P8564

给定一颗有 $n$ 个节点的有根树,其中根节点是 $1$。你可以进行若干次以下操作:

此操作的代价为 $f_i$,其中 $i$ 是你选择的节点子树大小。

你希望删掉除了 $1$ 以外的所有点,请问代价的最小值是多少?

题解

树形背包,$O(n^2)$ 的简单写法。

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
const int MAX_N = 5e3 + 10;
const ll INF = (1ll << 60);
int f[MAX_N], siz[MAX_N], n;
ll dp[MAX_N][MAX_N];
vector<int> G[MAX_N];

inline ll read() {...}
inline void write(ll x) {...}

void dfs(int u, int fa) {
    siz[u] = 1;
    for(int i = 1; i <= n; i++)
        dp[u][i] = INF;
    for(int v : G[u]) {
        if(v == fa) continue;
        dfs(v, u);
        for(int i = siz[u] - 1; i >= 0; i--)
            for(int j = siz[v] - 1; j >= 0; j--)
                dp[u][i + j] = min(dp[u][i + j], dp[u][i] + dp[v][j]);
        siz[u] += siz[v];
    } 
    for(int i = 0; i < siz[u] - 1; i++)
        dp[u][siz[u] - 1] = min(dp[u][siz[u] - 1], dp[u][i] + f[siz[u] - i]);
}

int main() {
    n = read();
    for(int i = 1; i <= n - 1; i++)
        f[i + 1] = read();
    for(int i = 1; i <= n - 1; i++) {
        int u = read(), v = read();
        G[u].pb(v), G[v].pb(u);
    }
    dfs(1, 0);
    write(dp[1][n - 1]);
    return 0;
}

[NOI2018] 归程

洛谷 P4768

本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。 魔力之都可以抽象成一个 $n$ 个节点、$m$ 条边的无向连通图(节点的编号从 $1$ 至 $n$)。我们依次用 $l,a$ 描述一条边的长度、海拔

作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。我们用水位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。

Yazid 是一名来自魔力之都的 OIer,刚参加完 ION2018 的他将踏上归程,回到他温暖的家。Yazid 的家恰好在魔力之都的 $1$ 号节点。对于接下来 $Q$ 天,每一天 Yazid 都会告诉你他的出发点 $v$ ,以及当天的水位线 $p$。

每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。 需要特殊说明的是,第二天车会被重置,这意味着:

Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。

本题的部分测试点强制在线。

题解

先考虑离线的部分分。可以先把 $1$ 号点到所有点的最短路预处理,然后把所有询问的水位线从大到小排序。对于每个询问,把海拔大于当前水位线的边加上,然后用并查集维护当前连通块距离 $1$ 号节点最近的距离即可。

对于上面的排序再加边的过程,我们可以用 Kruskal 重构树使其在线。这个就比较套路。

如何构建 Kruskal 重构树呢?考虑排序后的每一次加边过程。假设我们现在要加上 $(u,v)$ 这条边:

  1. 如果 $u$ 和 $v$ 连通,忽略这次操作。

  2. 否则,找到它们在重构树上的的祖先 $f_u$ 和 $f_v$,并在重构树上新建一个节点 $k$,连两条边 $(k,f_u),(k,f_v)$。节点 $k$ 带有的信息是原图中的 $(u,v)$ 边。

这样出来的重构树有非常好的性质。

  1. 它是一个二叉堆。

  2. $\operatorname{lca}(u,v)$ 对应原图中 $u$ 到 $v$ 的路径上的瓶颈。

本题主要用到第一个性质,我们会构造出一个小根堆,即 $u$ 子树内的所有节点 $w$ 所代表的边的海拔都有 $p_w\ge p_u$。

这样这题的思路就有了。首先预处理 Kruskal 重构树并用 dijkstra 求出 $1$ 号点到所有点的最短路。然后每个询问只要在 Kruskal 重构树上倍增找到第一个海拔大于询问水位,且父亲的海拔小于或等于当前水位的节点。然后在子树的叶子节点中找到在原图中距离 $1$ 号节点最近的即可。

用链式前向星存图的代码见此处

vector 存图的代码(奇怪的习惯,虽然慢了两秒但是不会被卡常):

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MAX_N = 4e5 + 10, MAX_LOG = 20, INF = (1 << 30);

inline ll read() {
    ll ret = 0, w = 1; char c = getchar();
    while(!isdigit(c))    {if(c == '-')    w = -1; c = getchar();}
    while(isdigit(c))    {ret = (ret << 1) + (ret << 3) + (c ^ 48); c = getchar();}
    return ret * w;
}
inline void write(ll x) {
    if(x < 0)  putchar('-'), x = -x;
    if(x > 9)  write(x / 10);
    putchar(x % 10 + 48);
}

int T, n, m, cnt, tot;
int fa[MAX_N][MAX_LOG], dis[MAX_N], dp[MAX_N], h[MAX_N], dep[MAX_N];
bool vis[MAX_N];
struct edge {
    int u, v, l, a;
    edge(int _u = 0, int _v = 0, int _l = 0, int _a = 0) :
        u(_u), v(_v), l(_l), a(_a) {}
    bool operator < (const edge &t) const {return a > t.a;}
} E[MAX_N];
int f[MAX_N];
int find(int x) {
    if(x == f[x])   return x;
    return f[x] = find(f[x]);
}
vector<edge> edges;
vector<int> G[MAX_N], tree[MAX_N];
void AddEdge(int u, int v, int l, int a) {
    edges.pb(edge(u, v, l, a));
    G[u].pb((int)edges.size() - 1);
}


void dijkstra() {
    for(int i = 1; i <= n; i++)
        dis[i] = INF, vis[i] = 0;
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push(make_pair(0, 1));
    dis[1] = 0;
    while(!q.empty()) {
        int u = q.top().second;
        q.pop();
        if(vis[u])  continue;
        vis[u] = 1;
        for(int i : G[u]) {
            int v = edges[i].v, w = edges[i].l;
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                q.push(make_pair(dis[v], v));
            }
        }
    }
}

void dfs(int u, int pre) {
    fa[u][0] = pre;
    dp[u] = tree[u].empty() ? dis[u] : INF;
    for(int i = 1; i < MAX_LOG; i++)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for(int v : tree[u]) {
        dep[v] = dep[u] + 1;
        dfs(v, u);
        dp[u] = min(dp[u], dp[v]);
    }
}

void kruskal() {
    sort(E + 1, E + m + 1);
    for(int i = 1; i <= (n << 1); i++)
        f[i] = i, tree[i].clear();
    cnt = n, tot = 0;
    for(int i = 1; i <= m; i++) {
        int x = E[i].u, y = E[i].v, a = E[i].a;
        int fx = find(x), fy = find(y);
        if(fx == fy)    continue;
        cnt++;
        tree[cnt].pb(fx);
        tree[cnt].pb(fy);
        h[cnt] = a;
        f[fx] = f[fy] = cnt;
        if(++tot == n - 1)    break;
    }
}

int main() {
    T = read();
    while(T--) {
        n = read(), m = read();
        edges.clear();
        for(int i = 1; i <= n; i++)
            G[i].clear();
        for(int i = 1; i <= m; i++) {
            int u = read(), v = read(), l = read(), a = read();
            E[i] = edge(u, v, l, a); 
            AddEdge(u, v, l, a);
            AddEdge(v, u, l, a);
        }
        dijkstra();
        kruskal();
        dep[cnt] = 1;
        dfs(cnt, 0);
        int q = read(), k = read(), s = read(), lastans = 0;
        while(q--) {
            int v = read(), p = read();
            v = (v + k * lastans - 1) % n + 1;
            p = (p + k * lastans) % (s + 1);
            for(int i = MAX_LOG - 1; i >= 0; i--)
                if(h[fa[v][i]] > p && dep[v] > (1 << i))    v = fa[v][i];
            write(lastans = dp[v]);
            putchar('\n');
        }
    }
    return 0;
}

[NOIP2013 提高组] 货车运输

洛谷 P1967

A 国有 $n$ 座城市,编号从 $1$ 到 $n$,城市之间有 $m$ 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 $q$ 辆货车在运输货物,一次运输有起点和终点。司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

$1 \le n < 10^4$,$1 \le m < 5\times 10^4$,$1 \le q< 3\times 10^4 $,$0 \le z \le 10^5$。

题解

询问 $x$ 到 $y$ 的路径中最小的边权最大是多少。

其实就是最大生成树的路径最小值。可以用 Kruskal 重构树解决,重构树的 LCA 的权值就是最小的边权。

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
const int MAX_M = 5e4 + 10, MAX_LOG = 16;

inline ll read() {...}
inline void write(ll x) {...}

int n, m;
struct edge {
    int u, v, w;
    bool operator < (const edge &t) const {return w > t.w;}
} E[MAX_M];
vector<int> G[MAX_M];
int fa[MAX_M], dep[MAX_M], val[MAX_M];
bool vis[MAX_M];
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}

int f[MAX_M][MAX_LOG];
void dfs(int u, int pre) {
    f[u][0] = pre;
    dep[u] = dep[pre] + 1;
    vis[u] = 1;
    for(int i = 1; i < MAX_LOG; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for(int v : G[u])
        dfs(v, u);
}
int lca(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    for(int i = MAX_LOG - 1; i >= 0; i--)
        if(f[u][i] && dep[f[u][i]] >= dep[v])   u = f[u][i];
    if(u == v)  return u;
    for(int i = MAX_LOG - 1; i >= 0; i--)
        if(f[u][i] && f[v][i] && f[u][i] != f[v][i])    u = f[u][i], v = f[v][i];
    return f[u][0];
}

int main() {
    n = read(), m = read();
    for(int i = 1; i <= m; i++)
        E[i].u = read(), E[i].v = read(), E[i].w = read();
    sort(E + 1, E + m + 1);
    int cnt = n;
    for(int i = 1; i < (n << 1); i++)
        fa[i] = i;
    for(int i = 1; i <= m; i++) {
        int u = E[i].u, v = E[i].v, w = E[i].w;
        int fu = find(u), fv = find(v);
        if(fu != fv) {
            cnt++;
            val[cnt] = w;
            G[cnt].pb(fu);
            G[cnt].pb(fv);
            fa[fu] = fa[fv] = cnt;
        }
    }
    for(int i = 1; i <= cnt; i++)
        if(!vis[i]) dfs(find(i), 0);
    int q = read();
    while(q--) {
        int x = read(), y = read();
        if(find(x) != find(y))  write(-1);
        else    write(val[lca(x, y)]);
        putchar('\n');
    }
    return 0;
}

[NOIP2017 提高组] 逛公园

策策同学特别喜欢逛公园。公园可以看成一张 $N$ 个点 $M$ 条边构成的有向图,且没有 自环和重边。其中 $1$ 号点是公园的入口,$N$ 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从 $1$ 号点进去,从 $N$ 号点出来。

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 $1$ 号点 到 $N$ 号点的最短路长为 $d$,那么策策只会喜欢长度不超过 $d + K$ 的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

为避免输出过大,答案对 $P$ 取模。

如果有无穷多条合法的路线,请输出 $-1$。

$1 \le P \le 10^9$,$1 \le a_i,b_i \le N$,$0 \le c_i \le 1000$,$0\le k\le50$。

题解

我们看到 $k$ 的范围很小,所以可以暴力枚举。设 $dp_{u,k}$ 为 $1$ 到 $u$,最短路恰好为 $dis_u + k$ 时的路径条数。那么答案就是 $\sum\limits_{w=0}^k dp_{n,w}$。

这个路径条数我们可以在反图上记忆化搜索。反图可以使合法状态数减少一些。

然后我们考虑一条边 $(u,v,w)$,怎么从 $dp_{v,x}$ 转移到 $dp_{u,k}$?用边权找到约束关系:$dis_{v} + x + w = dis_u + k$。所以转移方程为:

\[dp_{u,k} = \sum\limits_{(u,v,w)\in E} dp_{v,dis_u+k-dis_v-w}\]

然后观察样例,原图中有零环的情况下最短路当然有无数多条。这个只要在记搜的时候记录一下会不会成环即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MAX_N = 2e5 + 10, INF = (1 << 30);

inline ll read() {...}
inline void write(ll x) {...}

int T, n, m, k, p, cnt1, cnt2;
int fir1[MAX_N], fir2[MAX_N], dis[MAX_N], dp[MAX_N][60];
bool vis[MAX_N], instack[MAX_N][60];
struct node {int v, w, nxt;} e1[MAX_N], e2[MAX_N];

void AddEdge(int u, int v, int w, int &cnt, int *fir, node *e) {
    e[++cnt].v = v;
    e[cnt].w = w;
    e[cnt].nxt = fir[u];
    fir[u] = cnt;
}

void clear() {
    cnt1 = cnt2 = 0;
    for(int i = 1; i <= m; i++)
        fir1[i] = fir2[i] = 0;
    for(int i = 1; i <= n; i++) {
        dis[i] = INF, vis[i] = 0;
        for(int k = 0; k <= 50; k++)
            dp[i][k] = instack[i][k] = 0;
    }
}

void dijkstra() {
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push(make_pair(0, 1));
    dis[1] = 0;
    while(!q.empty()) {
        int u = q.top().second;
        q.pop();
        if(vis[u])  continue;
        vis[u] = 1;
        for(int i = fir1[u]; i; i = e1[i].nxt) {
            int v = e1[i].v, w = e1[i].w;
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                q.push(make_pair(dis[v], v));
            }
        }
    }
}

int dfs(int u, int k) {
    if(k < 0)   return 0;
    if(instack[u][k])   return -1;
    if(dp[u][k])    return dp[u][k];
    instack[u][k] = 1;
    int ret = 0;
    for(int i = fir2[u]; i; i = e2[i].nxt) {
        int v = e2[i].v, w = e2[i].w;
        int tmp = dfs(v, dis[u] - dis[v] + k - w);
        if(tmp == -1)   return -1;
        (ret += tmp) %= p; 
    }
    instack[u][k] = 0;
    return dp[u][k] = ret;
}

int work() {
    int ans = 0;
    if(dfs(1, 0) == -1) return -1;
    dp[1][0] = 1;
    for(int i = 0; i <= k; i++) {
        int tmp = dfs(n, i);
        if(tmp == -1)   return -1;
        (ans += tmp) %= p;
    }
    return ans;
}

int main() {
    T = read();
    while(T--) {
        n = read(), m = read(), k = read(), p = read();
        clear();
        for(int i = 1; i <= m; i++) {
            int u = read(), v = read(), w = read();
            AddEdge(u, v, w, cnt1, fir1, e1);
            AddEdge(v, u, w, cnt2, fir2, e2);
        }
        dijkstra();
        write(work());
        putchar('\n');
    }

    return 0;
}

[SCOI2015]情报传递

洛谷 P4216

奈特公司是一个巨大的情报公司,它有着庞大的情报网络。情报网络中共有 $n$ 名情报员。每名情报员可能有若干名 (可能没有) 下线,除 $1$ 名大头目外其余 $n-1$ 名情报员有且仅有 $1$ 名上线。奈特公司纪律森严,每名情报员只能与自己的上、下线联系,同时,情报网络中任意两名情报员一定能够通过情报网络传递情报。

奈特公司每天会派发以下两种任务中的一个任务:

  1. 搜集情报:指派 $T$ 号情报员搜集情报;
  2. 传递情报:将一条情报从 $X$ 号情报员传递给 $Y$ 号情报员。

情报员最初处于潜伏阶段,他们是相对安全的,我们认为此时所有情报员的危险值为 $0$;一旦某个情报员开始搜集情报,他的危险值就会持续增加,每天增加 $1$ 点危险值 (开始搜集情报的当天危险值仍为 $0$,第 $2$ 天危险值为 $1$,第 $3$ 天危险值为 $2$,以此类推)。传递情报并不会使情报员的危险值增加。

为了保证传递情报的过程相对安全,每条情报都有一个风险控制值 $C$。奈特公司认为,参与传递这条情报的所有情报员中,危险值大于 $C$ 的情报员将对该条情报构成威胁。现在,奈特公司希望知道,对于每个传递情报任务,参与传递的情报员有多少个,其中对该条情报构成威胁的情报员有多少个。

题解

对于这个树上问题,先用 dfs 序 或 重链剖分 转化为序列问题。

所以需要支持:

  1. 在序列的位置 $u$ 打时间戳 $t_u$。

  2. 查询 $t$ 时刻序列的区间 $[l,r]$ 内有多少个位置 $u$ 满足 $t-t_u> c$。

Solution 1

可以用主席树来维护。把位置的权值设为时间戳的值,查询即查询区间内权值小于 $t-c$ 的个数。

Solution 2

考虑离线。对于查询 $t$ 时刻的情况,它只被 $t_u < t - c$ 的位置影响,所以我们可以在 $t=t_u - c - 1$ 的时刻算出 $t$ 时刻的答案。

此外,我们可以考虑差分。打时间戳的操作可以改为对 $u$ 的子树区间内 +1;询问的答案可以用差分得出。

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
const int MAX_N = 2e5 + 10, MAX_LOG = 19;
int n, q, dfncnt, rt;
int ans[MAX_N], opt[MAX_N], X[MAX_N], Y[MAX_N], C, T;
int fa[MAX_N][MAX_LOG], siz[MAX_N], dep[MAX_N], dfn[MAX_N];
vector<int> G[MAX_N], opter[MAX_N];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

struct TreeArray {
	int t[MAX_N];
	TreeArray() {memset(t, 0, sizeof(t));}
	inline int lowbit(int i)	{return i & -i;}
	inline void add(int i, int x) {
		for(; i <= n; i += lowbit(i))
			t[i] += x;
	}
	inline int query(int i) {
		int ret = 0;
		for(; i; i -= lowbit(i))
			ret += t[i];
		return ret;
	}
} tree;

void dfs(int u) {
	siz[u] = 1;
	dep[u] = dep[fa[u][0]] + 1;
	dfn[u] = ++dfncnt;
	for(int i = 1; i < MAX_LOG; i++)
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for(int v : G[u]) {
		dfs(v);
		siz[u] += siz[v];
	}
}

int lca(int u, int v) {
	if(dep[u] < dep[v])	swap(u, v);
	for(int i = MAX_LOG - 1; i >= 0; i--)
		if(fa[u][i] && dep[fa[u][i]] >= dep[v])	u = fa[u][i];
	if(u == v)		return u;
	for(int i = MAX_LOG - 1; i >= 0; i--)
		if(fa[u][i] && fa[v][i] && fa[u][i] != fa[v][i])	u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int dis(int u, int v) {
	int p = lca(u, v);
	return dep[u] + dep[v] - dep[p] * 2 + 1;
}

int main() {
	n = read();
	for(int i = 1; i <= n; i++)
		fa[i][0] = read(), G[fa[i][0]].pb(i), rt = (fa[i][0] ? rt : i);
    dfs(rt);
	q = read();
	for(int i = 1; i <= q; i++) {
		opt[i] = read();
		if(opt[i] == 1) {
			X[i] = read(), Y[i] = read(), C = read();
			if(i - C - 1 >= 0)	opter[i - C - 1].pb(i);
		}
		else	X[i] = read();
	}
	for(int i = 1; i <= q; i++) {
		int x = X[i], y = Y[i];
		if(opt[i] == 2)	tree.add(dfn[x], 1), tree.add(dfn[x] + siz[x], -1);
		for(int u : opter[i]) {
			int p = lca(X[u], Y[u]);
			ans[u] = tree.query(dfn[X[u]]) + tree.query(dfn[Y[u]]) - tree.query(dfn[p]) - tree.query(dfn[fa[p][0]]);
		}
		if(opt[i] == 1) {
			write(dis(x, y)), putchar(' ');
			write(ans[i]), putchar('\n');
		}
	}
	return 0;
}

[国家集训队] 小 Z 的袜子

洛谷 P1494

有一个长度为 $n$ 的序列 ${c_i}$。现在给出 $m$ 个询问,每次给出两个数 $l,r$,从编号在 $l$ 到 $r$ 之间的数中随机选出两个不同的数,求两个数相等的概率。

题解

莫队模板题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
const int MAX_N = 5e4 + 10;
int n, m, siz, tot;
int c[MAX_N], cnt[MAX_N];
pll ans[MAX_N];
inline int belong(int i) {return (i - 1) / siz + 1;}
struct query {
    int l, r, id;
    inline bool operator < (const query &t) const {
        if(belong(l) == belong(t.l))    return l < t.l;
        return belong(l) & 1 ? r < t.r : r > t.r;
    }
} q[MAX_N];

inline void add(int col) {
    tot += cnt[col];
    cnt[col]++;
}

inline void del(int col) {
    cnt[col]--;
    tot -= cnt[col];
} 

inline ll gcd(ll x, ll y) {return y ? gcd(y, x % y) : x;}

pll reduce(pll x) {
    ll g = gcd(x.first, x.second);
    return make_pair(x.first / g, x.second / g);
}

inline ll read() {...}
inline void write(ll x) {...}

int main() {
    n = read(), m = read();
    siz = sqrt(n);
    for(int i = 1; i <= n; i++)
        c[i] = read();
    for(int i = 1; i <= m; i++)
        q[i].l = read(), q[i].r = read(), q[i].id = i;
    sort(q + 1, q + m + 1);
    for(int i = 1, l = 1, r = 0; i <= m; i++) {
        if(q[i].l == q[i].r) {
            ans[q[i].id] = make_pair(0, 1);
            continue;
        }
        while(l > q[i].l)   add(c[--l]);
        while(r < q[i].r)   add(c[++r]);
        while(l < q[i].l)   del(c[l++]);
        while(r > q[i].r)   del(c[r--]);
        ans[q[i].id] = reduce(make_pair(tot, 1ll * (r - l + 1) * (r - l) / 2));
        if(!ans[q[i].id].first)   ans[i].second = 1;
    }
    for(int i = 1; i <= m; i++) {
        write(ans[i].first), putchar('/');
        write(ans[i].second), putchar('\n');
    }
    return 0;
}

Destiny

CF840D

给定 $n$ 个元素,$m$ 次询问。

每次给出三个参数 $l,r,k$,询问区间 $[l,r]$ 内是否存在出现次数严格大于 $\frac{r-l+1}{k}$ 的数。如果存在就输出最小的那个 $ans$,否则输出 $-1$.

$2\le k\le 5$

题解

考虑随机化。在区间 $[l,r]$ 中随机取值,有 $\frac{1}{k}$ 的概率取到答案, $1-\frac{1}{k}$ 的概率不是答案。由于 $k\le 5$,可知 $1-\frac{1}{k}\le \frac{4}{5}$,所以投答案 $T$ 次取不到答案的概率为 $(\frac{4}{5})^T$。取 $T=100$,则这个概率约为 $2\times 10^{-10}$。

由于可以离线,我们用莫队来求区间内每个数的出现次数。

时间复杂度 $O(100n\sqrt n)。$

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

const int MAX_N = 3e5 + 10;
int n, m, siz;
int a[MAX_N], cnt[MAX_N], ans[MAX_N];
mt19937 rng(random_device{}());
inline int belong(int i) {return (i - 1) / siz + 1;}

struct query {
	int l, r, k, id;
	bool operator < (const query &t) const {
		if(belong(l) != belong(t.l))	return l < t.l;
		return belong(l) & 1 ? r < t.r : r > t.r;
	}
} q[MAX_N];

int main() {
	n = read(), m = read();
	siz = sqrt(n);
	for(int i = 1; i <= n; i++)
		a[i] = read();
	for(int i = 1; i <= m; i++)
		q[i].l = read(), q[i].r = read(), q[i].k = read(), q[i].id = i;
	sort(q + 1, q + m + 1);
	for(int i = 1, l = 1, r = 0; i <= m; i++) {
		while(l > q[i].l)	cnt[a[--l]]++;
		while(r < q[i].r)	cnt[a[++r]]++;
		while(l < q[i].l)	cnt[a[l++]]--;
		while(r > q[i].r)	cnt[a[r--]]--;
		int res = -1;
		for(int j = 1; j <= 100; j++) {
			int x = l + rng() % (r - l + 1);
			if(cnt[a[x]] * q[i].k > r - l + 1)
				res = (res == -1 || res > a[x]) ? a[x] : res;
		}
		ans[q[i].id] = res;
	}
	for(int i = 1; i <= m; i++)
		write(ans[i]), putchar('\n');
	return 0;
}

Joy © 2023 Powered by Jekyll and Theme by solid

今日诗词API Valine 评论管理 Github