COCI 2014/2015 解题报告

已全部完成

[COCI 2014/2015] Contest#1 D MAFIJA

题意

题面

给定 n n n,以及数组 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,,an}

n n n 个人,每个人是杀手或平民,第 i i i 个人举报了编号为 a i a_i ai 的人,杀手举报平民,平民随便举报。询问最多有多少杀手。

数据范围: 2 ≤ n ≤ 500000 2\le n\le 500000 2n500000

时间 / 空间限制: 1s / 32MB \text{1s / 32MB} 1s / 32MB

题解

人话:给定内向基环森林,询问最大点独立集大小。

环外的树 dp 一下,环内再 dp 一下就可以了。时间复杂度 O ( n ) O(n) O(n)

代码

#include <cstdio>

typedef long long ll;

const int MAXN = 500005, INF = 1000000000;
inline int max(int a, int b) { return a > b ? a : b; }

int N, p[MAXN], S, circ[MAXN], len;
struct node { int v, next; } E[MAXN]; int head[MAXN], Elen;
void add(int u, int v) { ++Elen, E[Elen].v = v, E[Elen].next = head[u], head[u] = Elen; }
bool used[MAXN], vis[MAXN];

int dp[MAXN][2];
void dfs(int u) {
    used[u] = 1, ++dp[u][1];
    for (int i = head[u]; i; i = E[i].next) {
        if (vis[E[i].v]) continue; dfs(E[i].v);
        dp[u][0] += max(dp[E[i].v][0], dp[E[i].v][1]), dp[u][1] += dp[E[i].v][0];
    }
}

int f[MAXN][2][2], ans;
int main() {
    scanf("%d", &N); int i, j;
    for (i = 1; i <= N; ++i) scanf("%d", &p[i]), add(p[i], i);
    for (j = 1; j <= N; ++j) {
        if (used[j]) continue;
        for (S = j; !vis[S]; S = p[S]) vis[S] = 1;
        for (i = j; vis[i]; i = p[i]) vis[i] = 0;
        len = 0;
        for (i = S; !vis[i]; i = p[i]) vis[i] = 1, circ[++len] = i;
        for (i = 1; i <= len; ++i) dfs(circ[i]);
        f[1][0][0] = dp[circ[1]][0], f[1][1][1] = dp[circ[1]][1], f[1][0][1] = f[1][1][0] = -INF;
        for (i = 2; i <= len; ++i) {
            f[i][0][0] = max(f[i - 1][0][0], f[i - 1][0][1]) + dp[circ[i]][0],
            f[i][0][1] = f[i - 1][0][0] == -INF ? -INF : (f[i - 1][0][0] + dp[circ[i]][1]),
            f[i][1][0] = max(f[i - 1][1][0], f[i - 1][1][1]) + dp[circ[i]][0],
            f[i][1][1] = f[i - 1][1][0] == -INF ? -INF : (f[i - 1][1][0] + dp[circ[i]][1]);
        }
        ans += max(f[len][0][1], max(f[len][0][0], f[len][1][0]));
    }
    printf("%d\n", ans);
    return 0;
}

[COCI 2014/2015] Contest#1 E ZABAVA

题意

题面

给定 n , m , k n, m, k n,m,k,以及数组 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots, a_n\} {a1,a2,,an}

m m m 个学生宿舍,一开始都为空,有 n n n 个人,第 i i i 个人会在第 i i i 天进入第 a i a_i ai 个学生宿舍。

当第 i i i 个人进入第 a i a_i ai 个学生宿舍,学生宿舍 a i a_i ai 会举行一次派对,喧闹程度为这个宿舍里的人数。

你可以进行 k k k 次操作,一次操作可以在某两天之间将某一所宿舍里的人全部清空。

请让 n n n 次派对的喧闹程度之和最小。

数据范围: 1 ≤ n ≤ 1000000 , 1 ≤ m ≤ 100 , 1 ≤ k ≤ 500 1\le n\le 1000000, 1\le m\le 100, 1\le k\le 500 1n1000000,1m100,1k500

时间 / 空间限制: 1s / 256MB \text{1s / 256MB} 1s / 256MB

题解

考虑子问题:给定 L , T L, T L,T,要求最小的 ∑ i = 1 T a i ( a i + 1 ) 2 \sum\limits_{i=1}^{T} \frac{a_i(a_i+1)}{2} i=1T2ai(ai+1),满足

  • ∀ i ∈ [ 1 , T ] , a i ∈ N ∗ \forall i\in [1,T], a_i\in \mathtt{N}^* i[1,T],aiN

  • ∑ i = 1 T a i = L \sum\limits_{i=1}^{T} a_i=L i=1Tai=L

由数学直觉,这些数应该尽量平均分配,因此

a 1 = a 2 = ⋯ = a t = ⌊ L T ⌋ , a t + 1 + a t + 2 + ⋯ + a L = ⌊ L T ⌋ + 1 a_1=a_2=\cdots=a_t=\lfloor \frac{L}{T}\rfloor, a_{t+1}+a_{t+2}+\cdots+a_L=\lfloor \frac{L}{T}\rfloor + 1 a1=a2==at=TL,at+1+at+2++aL=TL+1

其中 t t t 解得

t = ( ⌊ L T ⌋ + 1 ) × T − L t=\left(\lfloor \frac{L}{T}\rfloor+1\right)\times T-L t=(TL+1)×TL

然后将每个宿舍独立看待,问题就变成了一个背包问题,做一下就好了。

时间复杂度 O ( n + m k 2 ) O(n+mk^2) O(n+mk2)

代码

#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 1005;
const ll INF = LLONG_MAX;
int N, M, K, a[MAXN];

ll qwq(int x) { return 1ll * x * (x + 1) / 2; }
inline ll f(int L, int T) {
    int x = L / T, t = (x + 1) * T - L;
    return qwq(x) * t + qwq(x + 1) * (T - t);
}
ll dp[MAXN][MAXN];

int main() {
    scanf("%d%d%d", &N, &M, &K); int i, j, k, x;
    for (i = 1; i <= N; ++i) scanf("%d", &x), ++a[x];
    for (i = 1; i <= M; ++i) {
        for (j = 0; j <= K; ++j) dp[i][j] = INF;
        for (j = 0; j <= K; ++j) {
            for (k = j; k <= K; ++k) {
                dp[i][k] = min(dp[i][k], dp[i - 1][k - j] + f(a[i], j + 1));
            }
        }
    }
    printf("%lld\n", dp[M][K]);
    return 0;
}

[COCI 2014/2015] Contest#1 F KAMP

题意

题面

给定 n , k n, k n,k,一棵大小为 n n n 的边带权的树,以及数组 { a 1 , a 2 , ⋯   , a k } \{a_1,a_2,\cdots,a_k\} {a1,a2,,ak}

对每个 i ∈ [ 1 , n ] i\in [1,n] i[1,n],回答从 i i i 出发,以某种顺序依次走过节点 a 1 , a 2 , ⋯   , a k a_1,a_2,\cdots,a_k a1,a2,,ak,需要的代价的最小值。代价被定义为走过的边权值之和。

数据范围: 1 ≤ k ≤ n ≤ 500000 , 1 ≤ c i ≤ 1000000 1\le k\le n\le 500000, 1\le c_i\le 1000000 1kn500000,1ci1000000,其中 c i c_i ci 是边的权值。

时间 / 空间限制: 2s / 128MB \text{2s / 128MB} 2s / 128MB

题解

f u , 0 / 1 f_{u,0/1} fu,0/1 为从 u u u 开始将 u u u 子树中的所有给定点都走一遍,不返回 / 返回 u u u 的最小代价。当 u u u 是根是, f u , 0 f_{u,0} fu,0 即答案。

显然计算一个 u u u f u , 0 , f u , 1 f_{u,0},f_{u,1} fu,0,fu,1 O ( ∣ son ∣ ) O(|\text{son}|) O(son) 的,因此我们可以使用换根 dp 解决。

代码

#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;

typedef long long ll;
int read() {
    int ret = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
    return ret;
}

const int MAXN = 500005;
const ll INF = LLONG_MAX >> 1;
int N, K;
struct node { int v, next, k; } E[MAXN << 1]; int head[MAXN], Elen;
void add(int u, int v, int k) { ++Elen, E[Elen].v = v, E[Elen].next = head[u], head[u] = Elen, E[Elen].k = k; }

ll dp[MAXN][2]; bool has[MAXN], mark[MAXN];
void calc(int u, int ff, int del = 0) {
    dp[u][0] = INF, dp[u][1] = 0, has[u] = mark[u]; int i;
    for (i = head[u]; i; i = E[i].next) {
        if (E[i].v == ff || E[i].v == del) continue;
        if (has[E[i].v]) dp[u][1] += dp[E[i].v][1] + 2 * E[i].k, dp[u][0] = min(dp[u][0], -E[i].k - dp[E[i].v][1] + dp[E[i].v][0]);
        has[u] |= has[E[i].v];
    }
    if (dp[u][0] == INF) dp[u][0] = 0;
    dp[u][0] += dp[u][1];
}

void dfs1(int u, int ff) {
    for (int i = head[u]; i; i = E[i].next) if (E[i].v != ff) dfs1(E[i].v, u);
    calc(u, ff, 0);
}
ll ans[MAXN];
void dfs2(int u, int ff) {
    ll fa0, fa1, u0, u1; bool hasfa, hasu;
    if (u != 1) {
        fa0 = dp[ff][0], fa1 = dp[ff][1], u0 = dp[u][0], u1 = dp[u][1], hasfa = has[ff], hasu = has[u];
        calc(ff, 0, u), calc(u, 0, 0);
        ans[u] = dp[u][0], dp[u][0] = u0, dp[u][1] = u1, has[u] = hasu;
    } else ans[u] = dp[u][0];
    for (int i = head[u]; i; i = E[i].next) {
        if (E[i].v != ff) dfs2(E[i].v, u);
    }
    if (u != 1) dp[ff][0] = fa0, dp[ff][1] = fa1, has[ff] = hasfa;
}

int main() {
    N = read(), K = read(); int i, u, v, k;
    for (i = 1; i < N; ++i) u = read(), v = read(), k = read(), add(u, v, k), add(v, u, k);
    for (i = 1; i <= K; ++i) mark[read()] = 1;
    dfs1(1, 0), dfs2(1, 0);
    for (i = 1; i <= N; ++i) printf("%lld\n", ans[i]);
    return 0;
}

[COCI 2014/2015] Contest#2 D BOB

题意

题面

给定一个 n × m n\times m n×m 的矩形。

回答多少子矩形内的所有数都是相同的。

数据范围: 1 ≤ n , m ≤ 1000 , 1 ≤ a i , j ≤ 1 0 9 1\le n,m\le 1000, 1\le a_{i,j}\le 10^9 1n,m1000,1ai,j109

时间 / 空间限制: 1s / 64MB \text{1s / 64MB} 1s / 64MB

题解

l i , j l_{i,j} li,j 表示从 ( i , j ) (i,j) (i,j) 往左保证都相同的情况下最多延长多少。可以 O ( n 2 ) O(n^2) O(n2) 求出。

然后我们枚举矩阵右上角,先枚举行,然后列从下往上枚举,并维护 l l l 构成的单调栈,同时记录答案即可。

时间复杂度 O ( n 2 ) O(n^2) O(n2)

代码

#include <cstdio>

typedef long long ll;

const int MAXN = 1005;
int N, M, a[MAXN][MAXN], lef[MAXN][MAXN];
ll ans, tans;

struct node {
    int x, y;
    node (int x = 0, int y = 0) : x(x), y(y) {}
} S[MAXN]; int Slen;

int main() {
    scanf("%d%d", &N, &M); int i, j;
    for (i = 1; i <= N; ++i) for (j = 1; j <= M; ++j) scanf("%d", &a[i][j]);
    for (i = 1; i <= N; ++i) {
        lef[i][1] = 1;
        for (j = 2; j <= M; ++j) {
            lef[i][j] = a[i][j] == a[i][j - 1] ? lef[i][j - 1] + 1 : 1;
        }
    }
    for (j = 1; j <= M; ++j) {
        for (i = N; i >= 1; --i) {
            if (i == N || a[i][j] != a[i + 1][j]) {
                Slen = tans = 0;
            }
            int y = 1;
            while (Slen && lef[i][j] <= S[Slen].x) {
                y += S[Slen].y, tans -= S[Slen].x * S[Slen].y, --Slen;
            }
            S[++Slen] = node(lef[i][j], y);
            tans += S[Slen].x * S[Slen].y, ans += tans;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

[COCI 2014/2015] Contest#2 E SUMA

题意

题面

给定每个格子上有一个一次函数的 n × n n\times n n×n 的矩阵。

对一个 x x x,计算 满足其中每个一次函数在 x x x 处取值都相同 的最大连通分量大小,输出对于所有 x ≥ 0 , x ∈ Q x\ge 0, x\in \mathtt{Q} x0,xQ 这个数的最大值。

数据范围: 1 ≤ n ≤ 700 1\le n\le 700 1n700,值域为 [ 1 , 1 0 6 ] [1,10^6] [1,106]

时间 / 空间限制: 2s / 128MB \text{2s / 128MB} 2s / 128MB

题解

枚举边,然后宽搜。注意非常多的细节。

宽搜复杂度是错的,直接将边排序,建并查集找最大连通分量即可。

时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)

代码

超大常数:

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 2005;
int N;

struct fac {
    int x, y;
    fac (int x = 0, int y = 1) : x(x), y(y) {}
    friend bool operator== (const fac& l, const fac& r) {
        return 1ll * l.x * r.y == 1ll * r.x * l.y;
    }
    friend bool operator< (const fac& l, const fac& r) {
        if (1ll * l.y * r.y > 0) return 1ll * l.x * r.y - 1ll * r.x * l.y < 0;
        else return 1ll * l.x * r.y - 1ll * r.x * l.y > 0;
    }
};

struct edge {
    int u, v; fac k;
} E[MAXN * MAXN]; int Elen;
void add(int u, int v, fac k) { ++Elen, E[Elen].u = u, E[Elen].v = v, E[Elen].k = k; }
bool cmp(edge e1, edge e2) { return e1.k < e2.k; }

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
inline int id(int x, int y) { return (x - 1) * N + y; }
int a[MAXN][MAXN], b[MAXN][MAXN];
int fa[MAXN * MAXN], siz[MAXN * MAXN], SIZ[MAXN * MAXN], w[MAXN * MAXN], tans;
int get(int x) {
    if (fa[x] != x) fa[x] = get(fa[x]);
    return fa[x];
}
void merge(int x, int y) {
    int gx = get(x), gy = get(y);
    if (gx == gy) return;
    if (siz[gx] > siz[gy]) swap(x, y);
    siz[gy] += siz[gx], fa[gx] = gy, w[gy] += w[gx], tans = max(tans, w[gy]);
}

int ans;
int main() {
    scanf("%d", &N); int i, j, k;
    for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) scanf("%d", &b[i][j]);
    for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) scanf("%d", &a[i][j]);
    for (i = 1; i <= N * N; ++i) fa[i] = i, siz[i] = 1;
    for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) for (k = 0; k < 4; ++k) {
        if (a[i][j] == a[i + dx[k]][j + dy[k]] && b[i][j] == b[i + dx[k]][j + dy[k]]) 
            merge(id(i, j), id(i + dx[k], j + dy[k]));
    }
    for (i = 1; i <= N * N; ++i) get(i), SIZ[fa[i]] = siz[fa[i]];
    for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) for (k = 0; k < 4; ++k)
    {
        if (i + dx[k] <= 0 || i + dx[k] > N || j + dy[k] <= 0 || j + dy[k] > N) continue;
        if (fa[id(i, j)] == fa[id(i + dx[k], j + dy[k])]) continue;
        if (a[i][j] == a[i + dx[k]][j + dy[k]]) continue;
        if (1ll * (b[i][j] - b[i + dx[k]][j + dy[k]]) * (a[i + dx[k]][j + dy[k]] - a[i][j]) < 0) continue;
        add(fa[id(i, j)], fa[id(i + dx[k], j + dy[k])], fac(b[i][j] - b[i + dx[k]][j + dy[k]], a[i + dx[k]][j + dy[k]] - a[i][j]));
    }

    sort(E + 1, E + Elen + 1, cmp);
    for (i = 1; i <= N * N; ++i) fa[i] = i, siz[i] = 1, w[i] = SIZ[i], ans = max(ans, w[i]);
    tans = 0;
    int left = 1;
    for (i = 1; i <= Elen; ++i) {
        if (i != Elen && E[i].k == E[i + 1].k) {
            merge(E[i].u, E[i].v);
        } else {
            ans = max(ans, tans);
            for (j = left; j <= i; ++j) {
                fa[E[j].u] = E[j].u, fa[E[j].v] = E[j].v, 
                siz[E[j].u] = siz[E[j].v] = 1, w[E[j].u] = SIZ[E[j].u], w[E[j].v] = SIZ[E[j].v];
            }
            tans = 0, left = i + 1;
        }
    }

    printf("%d\n", ans);
    return 0;
}

[COCI 2014/2015] Contest#2 F NORMA

题意

题面

给定 n n n,以及数组 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,,an}

计算

( ∑ i = 1 n ∑ j = i n ( j − i + 1 ) min ⁡ { a i , a i + 1 , ⋯   , a j } max ⁡ { a i , a i + 1 , ⋯   , a j } ) m o d    1 0 9 \left(\sum_{i=1}^n \sum_{j=i}^n (j-i+1)\min\{a_i,a_{i+1},\cdots,a_j\}\max\{a_i,a_{i+1},\cdots,a_j\}\right) \mod 10^9 (i=1nj=in(ji+1)min{ai,ai+1,,aj}max{ai,ai+1,,aj})mod109

的值。

数据范围: 1 ≤ n ≤ 500000 1\le n\le 500000 1n500000,值域为 [ 1 , 1 0 8 ] [1,10^8] [1,108]

时间 / 空间限制: 3s / 64MB \text{3s / 64MB} 3s / 64MB

题解

好题!

这里使用单调栈做法。

我们枚举右端点,用单调栈记下最值相同的区间,在修改的同时统计答案。

首先要清楚的是普通单调栈无法做到,因为在修改 min 的同时还要遍历 max 来更新答案,用 n = 500000 n=500000 n=500000 的递增序列可以卡掉,因为此时 min 有好多段,max 只有一段,每次修改 max 遍历 min 不删就会增加 O ( n ) O(n) O(n) 次运算,运算次数就是 O ( n 2 ) O(n^2) O(n2) 级别的了。

那么我们采用线段树维护单调栈,这样我们就能做更多操作!

我 yy 的做法:线段树二分找到最左边的要修改的端点,然后直接区间覆盖,需要维护 ∑ mn j , ∑ mx j , ∑ mn j × mx j , ∑ j × mn j , ∑ j × mx j , ∑ j × mn j × mx j \sum \text{mn}_j,\sum \text{mx}_j,\sum \text{mn}_j\times \text{mx}_j, \sum j\times \text{mn}_j,\sum j\times \text{mx}_j,\sum j\times \text{mn}_j\times \text{mx}_j mnj,mxj,mnj×mxj,j×mnj,j×mxj,j×mnj×mxj 这 6 个值,如果常数差一点 3s 也跑不过去的。

题解做法:就是像普通单调栈一样每次删一段,由于删这一段每个数都是一样的,所以对区间做的是区间除法操作,同理加一段是做区间乘法操作。因此我们的线段树需要支持:区间除法,区间乘法,单点加法。如果模数是质数,我们就只需要维护 ∑ mn j × mx j , ∑ j × mn j × mx j \sum \text{mn}_j\times \text{mx}_j, \sum j\times \text{mn}_j\times \text{mx}_j mnj×mxj,j×mnj×mxj,但模数不是质数,无法做除法,考虑到我们用线段树维护单调栈的操作的特性,我们需要分别将对 min 栈乘和对 max 栈乘开永久化标记即可,只需要维护 4 个值了。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码

#include <cstdio>

typedef long long ll;
int read() {
    int ret = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
    return ret;
}
const int MAXN = 500005, MOD = 1000000000;

int N, a[MAXN], S1[MAXN], S1len, S2[MAXN], S2len;
ll ans;

struct sgt {
    ll s1, s2, mul[2];
} T[MAXN << 2];

#define ls (o << 1)
#define rs (o << 1 | 1)
#define mid ((l + r) >> 1)

void build(int o, int l, int r) {
    T[o].mul[0] = T[o].mul[1] = 1;
    if (l < r) build(ls, l, mid), build(rs, mid + 1, r);
}
void pushUp(int o) {
    ll mull = T[ls].mul[0] * T[ls].mul[1] % MOD, mulr = T[rs].mul[0] * T[rs].mul[1] % MOD;
    T[o].s1 = (T[ls].s1 * mull + T[rs].s1 * mulr) % MOD, T[o].s2 = (T[ls].s2 * mull + T[rs].s2 * mulr) % MOD;
}
void insert(int o, int l, int r, int L, int R, int id, ll K) {
    if (l == L && r == R) T[o].mul[id] = K;
    else {
        if (R <= mid) insert(ls, l, mid, L, R, id, K);
        else if (L > mid) insert(rs, mid + 1, r, L, R, id, K);
        else insert(ls, l, mid, L, mid, id, K), insert(rs, mid + 1, r, mid + 1, R, id, K);
        pushUp(o);
    }
}
void add(int o, int l, int r, int pos) {
    if (l == r) T[o].s1 = 1, T[o].s2 = l;
    else {
        if (pos <= mid) add(ls, l, mid, pos);
        else add(rs, mid + 1, r, pos);
        pushUp(o);
    }
}

int main() {
    N = read(); int i, j;
    build(1, 1, N);
    for (i = 1; i <= N; ++i) {
        a[i] = read(), add(1, 1, N, i);
        while (S1len && a[S1[S1len]] >= a[i]) insert(1, 1, N, S1[S1len - 1] + 1, S1[S1len], 0, 1), --S1len;
        insert(1, 1, N, S1[S1len] + 1, i, 0, a[i]), S1[++S1len] = i;
        while (S2len && a[S2[S2len]] <= a[i]) insert(1, 1, N, S2[S2len - 1] + 1, S2[S2len], 1, 1), --S2len;
        insert(1, 1, N, S2[S2len] + 1, i, 1, a[i]), S2[++S2len] = i;
        ans = (ans + T[1].s1 * T[1].mul[0] % MOD * T[1].mul[1] % MOD * (i + 1) - T[1].s2 * T[1].mul[0] % MOD * T[1].mul[1]) % MOD;
    }
    printf("%lld\n", (ans + MOD) % MOD);
    return 0;
}

[COCI 2014/2015] Contest#3 D COCI

题意

题面

给定 n n n,以及数组 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,,an} { b 1 , b 2 , ⋯   , b n } \{b_1,b_2,\cdots,b_n\} {b1,b2,,bn}

a i > a j a_i>a_j ai>aj b i > b j b_i>b_j bi>bj,则有 c i ≥ c j c_i\ge c_j cicj

对每个 i i i,计算按照 a i + b i + c i a_i+b_i+c_i ai+bi+ci 排序后,它的排名的最大值与最小值。

已知 a i , b i , c i ∈ [ 0 , 650 ] ∩ Z a_i,b_i,c_i\in [0,650]\cap \mathtt{Z} ai,bi,ci[0,650]Z

数据范围: 1 ≤ n ≤ 500000 1\le n\le 500000 1n500000

时间 / 空间限制: 1s / 32MB \text{1s / 32MB} 1s / 32MB

题解

若有 a i > a j , b i > b j a_i>a_j, b_i>b_j ai>aj,bi>bj,则称 i i i 影响 j j j

若有 a i + b i + 0 > a j + b j + 650 a_i+b_i+0>a_j+b_j+650 ai+bi+0>aj+bj+650,则称 i i i 必然胜 j j j

若有 a i + b i + 0 ≥ a j + b j + 650 a_i+b_i+0\ge a_j+b_j+650 ai+bi+0aj+bj+650,则称 i i i 必胜或平 j j j

最低 rank:让 c i = 0 c_i=0 ci=0,并让它影响的所有变成 0 0 0,其它的是 650 650 650

最高 rank:让 c i = 650 c_i=650 ci=650,并让影响它的所有变成 650 650 650,其它的是 0 0 0

依这个策略,得:

最低 rank: N − N- N (( i i i 必胜或平的集合) 与 ( i i i 影响的集合) 的并集大小)

最高 rank:(必胜 i i i 的集合) 与 (影响 i i i 集合) 的并集大小 + 1 +1 +1

但容易证明 [ i i i 必然胜 j j j] 能够推出 [ i i i 影响 j j j],假设不能,不妨设 a i ≤ a j a_i\le a_j aiaj,考虑到

a j + b j + 650 ≥ a i + b j + 650 ≥ a i + 650 ≥ a i + b i a_j+b_j+650\ge a_i+b_j+650\ge a_i+650\ge a_i+b_i aj+bj+650ai+bj+650ai+650ai+bi

故矛盾。

而 [ i i i 必然胜或平 j j j] 不能推出 [ i i i 影响 j j j] 的充要条件是 a i = 650 , a j = 0 , b i = b j a_i=650,a_j=0,b_i=b_j ai=650,aj=0,bi=bj b i = 650 , b j = 0 , a i = a j b_i=650,b_j=0,a_i=a_j bi=650,bj=0,ai=aj,我们成为 P 条件。

因此最低 rank 是 N − N - N ( i i i 影响的集合大小 + { j ∣ P ( i , j ) } \{j|P(i,j)\} {jP(i,j)}),最高 rank 是 (影响 i i i 的集合大小) + 1 + 1 +1

m = 650 m=650 m=650,采用二维前缀和维护,时间复杂度 O ( n + m 2 ) O(n+m^2) O(n+m2)

代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 500005;
struct node { int x, y; } a[MAXN];

int N, ans1[MAXN], ans2[MAXN], S[652][652];
int s(int x1, int y1, int x2, int y2) {
    ++x1, ++y1, ++x2, ++y2;
    if (x1 > x2 || y1 > y2) return 0;
    return S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1];
}
int main() {
    scanf("%d", &N); int i, j, left, sum;
    for (i = 1; i <= N; ++i) scanf("%d%d", &a[i].x, &a[i].y), ++S[a[i].x + 1][a[i].y + 1];
    for (i = 1; i <= 651; ++i) for (j = 1; j <= 651; ++j) {
        S[i][j] += S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1];
    }
    for (i = 1; i <= N; ++i) {
        ans1[i] = s(0, 0, a[i].x - 1, a[i].y - 1);
        ans2[i] = s(a[i].x + 1, a[i].y + 1, 650, 650);
        if (a[i].x == 650) ans1[i] += s(0, a[i].y, 0, a[i].y);
        if (a[i].y == 650) ans1[i] += s(a[i].x, 0, a[i].x, 0);
        printf("%d %d\n", ans2[i] + 1, N - ans1[i]);
    }
    return 0;
}

[COCI 2014/2015] Contest#3 E STOGOVI

题意

题面

给定 n n n,以及 n n n 个操作。

一开始有一个空栈编号为 0 0 0

i i i 个操作包含参数 v ( 0 ≤ v < i ) v(0\le v<i) v(0v<i),可能包含参数 w w w,在一次操作前我们复制栈 S v S_v Sv 到新栈 S i S_i Si 上,并对 S i S_i Si 做三种操作:

  • A 操作:将数 i i i 加到栈顶

  • B 操作:弹栈

  • C 操作:给定参数 w w w,询问 ∣ S i ∩ S w ∣ |S_i\cap S_w| SiSw

数据范围: 1 ≤ n ≤ 300000 1\le n\le 300000 1n300000

时间 / 空间限制: 1s / 64MB \text{1s / 64MB} 1s / 64MB

题解

A 操作: v → i v\rightarrow i vi 连边

B 操作:设 i i i v v v 父亲

C 操作:设 i i i v v v,并问 dep ( lca ( v , w ) ) \text{dep}(\text{lca}(v,w)) dep(lca(v,w))

时间复杂度 O ( n ( log ⁡ n + α ( n ) ) ) O(n(\log n+\alpha(n))) O(n(logn+α(n))) (这是使用倍增 lca \text{lca} lca 的复杂度,若使用 Tarjan 算法可以做到 O ( n α ( n ) ) O(n\alpha(n)) O(nα(n)))。

代码

#include <cstdio>

typedef long long ll;
inline void swap(int& a, int& b) { int t = a; a = b, b = t; }

const int MAXN = 300005;

int N, fa[MAXN][19], dep[MAXN];
int F[MAXN];
int get(int x) {
    if (F[x] != x) F[x] = get(F[x]);
    return F[x];
}
int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v); int i;
    for (i = 18; i >= 0; --i) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
    if (u == v) return u;
    for (i = 18; i >= 0; --i) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}

int main() {
    scanf("%d", &N); int i, j, v, w; char op;
    for (i = 1; i <= N; ++i) F[i] = i;
    for (i = 1; i <= N; ++i) {
        scanf("%s%d\n", &op, &v), v = get(v);
        if (op == 'a') {
            fa[i][0] = v, dep[i] = dep[v] + 1;
            for (j = 1; j <= 18; ++j) fa[i][j] = fa[fa[i][j - 1]][j - 1];
        } else if (op == 'b') {
            printf("%d\n", v), F[i] = get(fa[v][0]);
        } else {
            scanf("%d", &w), F[i] = v, printf("%d\n", dep[lca(v, get(w))]);
        }
    }
    return 0;
}

[COCI 2014/2015] Contest#3 F KAMIONI

题意

题面

一个数轴上有 n n n 辆卡车,每辆卡车有路径 A 1 , A 2 , ⋯   , A k i A_1,A_2,\cdots,A_{k_i} A1,A2,,Aki 满足

A 1 < A 2 > A 3 < ⋯ 或 A 1 > A 2 < A 3 > ⋯ A_1<A_2>A_3<\cdots \text{或} A_1>A_2<A_3>\cdots A1<A2>A3<A1>A2<A3>

所有卡车同时出发,以 1 1 1 的速率走路,一开始在 A 1 A_1 A1,每次前往下一个 A i A_i Ai,到达后掉头。到达终点后立即消失。

q q q 个询问,一次询问问卡车 x , y x,y x,y 的相遇次数,询问的两辆卡车满足相遇时两辆卡车都不在它们的 A i A_i Ai 上,可以在非整点相遇。

数据范围: 1 ≤ n , m ≤ 100000 , k i ≥ 2 , ∑ k i ≤ 300000 , 1 ≤ A i ≤ 1 0 9 1\le n,m\le 100000, k_i\ge 2, \sum k_i\le 300000, 1\le A_i\le 10^9 1n,m100000,ki2,ki300000,1Ai109

时间 / 空间限制: 3s / 64MB \text{3s / 64MB} 3s / 64MB

题解

好题!

K = ∑ k i K=\sum k_i K=ki

人话: n n n 个斜率为正负一的折线,问两条折线交点个数

考虑一条线段 f f f 和一条折线 g g g(我们称线段、折线每段的斜率都是正负一)的交点,最多有一个,且有当且仅当在左右端点 l , r l,r l,r 上有 f ( l ) < g ( l ) , f ( r ) > g ( r ) f(l)<g(l),f(r)>g(r) f(l)<g(l),f(r)>g(r) f ( l ) > g ( l ) , f ( r ) < g ( r ) f(l)>g(l),f(r)<g(r) f(l)>g(l),f(r)<g(r)

因此给定两条折线 x , y x,y x,y,我们可以在 O ( k x log ⁡ k y ) O(k_x\log k_y) O(kxlogky) 的时间内在线回答交点个数。( log ⁡ \log log 是二分过去的复杂度)

然后我们对每个询问,让 x x x k k k 较小的。即如果 k x > k y k_x>k_y kx>ky,交换 x , y x,y x,y

这样做的复杂度是多少呢? O ( K m log ⁡ K ) O(K\sqrt{m}\log K) O(Km logK)

具体证明在下面,总之这个复杂度肯定是不行的。我们采取离线处理,把 log ⁡ \log log 去掉。这样就是 O ( K m ) O(K\sqrt{m}) O(Km )。离线处理的细节没有思维难度,但是细节真的很烦,这里有一组样例:

Sample Input

4 11
2 1 1000000000
3 5 1 1000000000
3 4 5 1
5 2 1 2 1 3
1 2
1 3
1 4
2 3
2 4
3 4
2 1
3 1
3 2
2 4
1 4

Sample Output

1
1
1
2
1
1
1
1
2
1
1

或许能帮您解决问题。

至于这个算法的复杂度证明,大致如下:(搬运自 smarthehe,我一开始想到这种做法但没发现复杂度能过)

不妨设 k 1 ≥ k 2 ≥ ⋯ ≥ k m k_1\ge k_2\ge \cdots\ge k_m k1k2km,设时间复杂度 T = a 1 k 1 + a 2 k 2 + ⋯ + a m k m T=a_1k_1+a_2k_2+\cdots+a_mk_m T=a1k1+a2k2++amkm,这里记 a i a_i ai 为询问复杂度带它的次数。显然有 a i < i a_i<i ai<i(只有比它大的才会选它),且由于 ∑ k i = K \sum k_i=K ki=K,故 k i ≤ K i k_i\le \frac{K}{i} kiiK

那么

O ( T ) = O ( 0 k 1 + 1 k 2 + 2 k 3 + ⋯ + m k m + 1 ) = O ( K m ) O(T)=O(0k_1+1k_2+2k_3+\cdots+\sqrt{m}k_{\sqrt{m}+1})=O(K\sqrt{m}) O(T)=O(0k1+1k2+2k3++m km +1)=O(Km )

代码

常数巨大:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
ll read() {
    ll ret = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
    return ret;
}

const int MAXN = 300005;
int N, M, K[MAXN];

struct node {
    ll x, y, xid;
} p[MAXN], *a[MAXN]; int len, SK; ll P[MAXN];
bool cmp(node n1, node n2) { return n1.x < n2.x; }

struct query { int x, y, id; } Q[MAXN];
bool cmpq(query q1, query q2) { return q1.x < q2.x || (q1.x == q2.x && q1.y < q2.y); }
vector<query> V[MAXN];
vector<pair<int, int> > T[MAXN];

int po[MAXN], ans[MAXN], lasy[MAXN];

int main() {
    N = read(), M = read(); int i, j;
    for (i = 1; i <= N; ++i) {
        K[i] = read(), a[i] = new node[K[i] + 1], a[i][0].x = a[i][0].y = a[i][0].xid = 0;
        for (j = 1; j <= K[i]; ++j) {
            a[i][j].y = read(), a[i][j].x = j == 1 ? 1 : abs(a[i][j].y - a[i][j - 1].y) + a[i][j - 1].x;
            p[++len] = a[i][j], p[len].xid = 1ll * i * 300005 + j;
        }
    }
    sort(p + 1, p + len + 1, cmp);
    for (i = 1; i <= len; ++i) {
        SK += p[i].x != p[i - 1].x, P[SK] = p[i].x, T[SK].push_back(make_pair(p[i].xid / 300005, p[i].xid % 300005));
        a[p[i].xid / 300005][p[i].xid % 300005].xid = SK;
    }
    for (i = 1; i <= M; ++i) {
        Q[i].x = read(), Q[i].y = read(), Q[i].id = i;
        if (K[Q[i].x] > K[Q[i].y] || (K[Q[i].x] == K[Q[i].y] && Q[i].x > Q[i].y)) swap(Q[i].x, Q[i].y);
    }
    sort(Q + 1, Q + M + 1, cmpq);
    for (i = 1; i <= M; ++i) if (Q[i].x != Q[i - 1].x || Q[i].y != Q[i - 1].y) V[Q[i].x].push_back(Q[i]);
    for (i = 1; i <= N; ++i) po[i] = 1;
    for (i = 1; i <= SK; ++i) {
        for (pair<int, int> pr : T[i]) for (query qu : V[pr.first]) { while (po[qu.y] != K[qu.y] && a[qu.y][po[qu.y] + 1].x < P[i]) ++po[qu.y]; }
        for (pair<int, int> pr : T[i]) {
            int X = pr.first; ll valx = a[X][pr.second].y;
            for (query qu : V[X]) {
                int Y = qu.y;
                if (po[Y] == K[Y]) {
                    if (lasy[qu.id]) {
                        ll tvalx;
                        if (a[X][pr.second - 1].y < a[X][pr.second].y) tvalx = a[X][pr.second - 1].y - a[X][pr.second - 1].x + a[Y][K[Y]].x;
                        else tvalx = a[X][pr.second - 1].y + a[X][pr.second - 1].x - a[Y][K[Y]].x;
                        if ((a[X][pr.second - 1].y < lasy[qu.id] && tvalx > a[Y][K[Y]].y) || (a[X][pr.second - 1].y > lasy[qu.id] && tvalx < a[Y][K[Y]].y)) ++ans[qu.id];
                        lasy[qu.id] = 0;
                    }
                } else {
                    ll valy;
                    if (a[Y][po[Y]].y < a[Y][po[Y] + 1].y) valy = P[i] - a[Y][po[Y]].x + a[Y][po[Y]].y;
                    else valy = a[Y][po[Y]].x - P[i] + a[Y][po[Y]].y;
                    if ((a[X][pr.second - 1].y < lasy[qu.id] && valx > valy) || (a[X][pr.second - 1].y > lasy[qu.id] && valx < valy)) ++ans[qu.id];
                    lasy[qu.id] = valy;
                }
            }
        }
    }
    for (i = 1; i <= M; ++i) if (Q[i].x == Q[i - 1].x && Q[i].y == Q[i - 1].y) ans[Q[i].id] = ans[Q[i - 1].id];
    for (i = 1; i <= M; ++i) printf("%d\n", ans[i]);
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值