2018.08.23高二互测

本文详细解析了2018年8月24日的NOIp模拟赛三道题目,包括树状结构的DP算法、图论问题的思维解决及概率计算的主席树应用,附带完整代码。

2018.08.24 NOIp模拟赛

今天是doe的神题,表示根本做不出,还好可以\(~IOI~\)实时评测,不然第一题都被\(~Subtask~\)捆死了。。。

第一题

1416390-20180824220407304-1485766481.png

第一眼看着题感觉把所有的叶子节点都染黑很优秀,但看样例就知道是错的。于是发现对于一个点,在他的儿子节点中,只要选得只剩下一个就是正确并且优秀的。而当前点被选之后,就把他标记一下不计入他父亲节点那一层的答案统计。但是这个DP会根据树的形态而改变,比如说对于一条链,如果选取两端作为根节点则答案为\(~0\),但答案应为\(~1\),所以挑一个好根很重要。再发现,若根的度大于\(~2\)时,其连向祖先的联通块中必然有点为⿊点,所以就选一个度大于等于\(~3\)的就好了。

code
#include<bits/stdc++.h>
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

inline void File() {
    freopen("bf.in", "r", stdin);
    freopen("bf.out", "w", stdout);
}

const int N = 1e5 + 10;
int n, e = 1, beg[N], nex[N << 1], to[N << 1];
int siz[N], u, v, deg[N], rt, ans, dp[N], maxx = 0;

inline void add(int x, int y) {
    to[++ e] = y, nex[e] = beg[x], beg[x] = e, ++ deg[x];
    to[++ e] = x, nex[e] = beg[y], beg[y] = e, ++ deg[y];
}

inline void dfs(int u, int f) {
    siz[u] = 0;
    Travel(i, u) if (v != f) {
        dfs(v, u);
        int sz = 0, tot = 0;
        for (int t = beg[v]; t; t = nex[t]) if (to[t] != u) ++ tot, sz += siz[to[t]] == 0;
        ans += max(0, sz - 1);
        siz[v] = tot > 1 && tot == sz || tot != sz;
    }
}

int main() {
    File();
    n = read();
    For(i, 2, n) u = read(), v = read(), add(v, u);
    For(i, 0, n - 1) if (deg[i] > maxx) rt = i, maxx = deg[i]; 
    dfs(rt, -1);

    int sz = 0;
    Travel(i, rt) if (!siz[v]) ++ sz;
    ans += max(0, sz - 1);
    
    cout << ans << endl;
    return 0;
}

第二题

1416390-20180824220419107-223445082.png

​ 这是一道很思维的题啊,离散化,首先对错位的数向应在的位置连一条边,可以发现最后的图是一些互不相交的简单环。当几个环中有相同的点权时,可以通过断开再连接的方式把这几个环合并成一个环以减少环数。对于若干个环,先把这几个环首尾连接变成一个大环进行操作,可以发现这样之后只有每个小环的末尾位置没有复位,再对这些数连成一个环。那么对于若干个环最少只需要两次操作就好了。但是有一个\(~S~\)的限制,由于我很懒就直接搬\(~Solution~\)了。

1416390-20180824220425767-1301010407.png

code
#include<bits/stdc++.h>
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; v = to[i = nex[i]])
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

inline void File() {
    freopen("gen.in", "r", stdin);
    freopen("gen.out", "w", stdout);
}

const int N = 2e5 + 10;
int n, s, fa[N], e = 1, beg[N], to[N << 1], nex[N << 1], num[N << 1];
int c[N], tot, a[N], ls[N], vis[N], cnt, k;
int lst[N], c1, c2;

vector <int> ans[N];

inline void add(int x, int y, int z) {
    to[++ e] = y, nex[e] = beg[x], beg[x] = e, num[e] = z;
}

inline void dfs(int u) {
    vis[u] = cnt;
    while (beg[u]) {
        int now = beg[u]; beg[u] = nex[beg[u]];
        dfs(to[now]), ans[cnt].push_back(num[now]);
    }
}

int main() {
    File();
    n = read(), s = read();
    For(i, 1, n) a[i] = ls[i] = read(), fa[i] = i;;
    sort(ls + 1, ls + 1 + n);
    For(i, 1, n) c[i] = ls[i] == ls[i - 1] ? c[i - 1] : c[i - 1] + 1;

    tot = unique(ls + 1, ls + 1 + n) - ls - 1;
    For(i, 1, n) {
        a[i] = lower_bound(ls + 1, ls + 1 + tot, a[i]) - ls;
        if (a[i] != c[i]) ++ k, add(a[i], c[i], i);     
    }

    if (k > s) return puts("-1"), 0;
    For(i, 1, n) if (!vis[i] && beg[i]) ++ cnt, dfs(i);     

    if (cnt <= 1 || s - k <= 1) {
        printf("%d\n", cnt);
        For(i, 1, cnt) {
            printf("%d\n", ans[i].size());
            for (auto v : ans[i]) printf("%d ", v); puts("");
        }
        return 0;
    }

    cout << cnt - min(cnt, s - k) + 2 << endl;

    For(i, s - k + 1, cnt) {
        printf("%d\n", ans[i].size());
        for (auto v : ans[i]) printf("%d ", v); puts("");
    }

    if (s - k > 0) {
        Forr(i, min(cnt, s - k), 1) {
            c1 += ans[i].size();
            lst[++ c2] = ans[i][0];
        }           
        
        printf("%d\n", c1); 
        For(i, 1, min(cnt, s - k)) for (auto v : ans[i]) printf("%d ", v); puts("");
        printf("%d\n", c2);
        For(i, 1, c2) printf("%d ", lst[i]); puts("");
    }
    return 0;
}

第三题

​ 给定⼀棵\(~n~\)个点树和树上的\(~m~\)条路径( 互不相同),求这\(~m~\)条路径中任选两条时其中⼀条完全包含另⼀
条的概率是多少。

​ 只要画个图就很容易推出满足题意的两种情况(一对子树限制和两对区间限制),就不再赘述。考虑用主席树维护\(~dfn~\),对于每一条路径,在主席树中插入其另一端点的\(~dfn~\),在查询的时候,由于一棵子树的\(~dfn~\)是连续的,所以只要查询以在当前这个区间中的\(~dfn~\)为目标区间的节点个数。但一条路径的端点可以互逆,所以记得反过来查询一下。

code
#include<bits/stdc++.h>
#define For(i, j, k) for(register int i = j; i <= k; ++i)
#define Forr(i, j, k) for(register int i = j; i >= k; --i)
#define Travel(i, u) for(register int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

inline void File() {
    freopen("std.in", "r", stdin);
    freopen("std.out", "w", stdout);
}

typedef long long ll;
const int N = 2e5 + 10, M = N << 1;
int e = 1, beg[N], nex[M], to[M];
int dep[N], tot, pos[N], fa[18][N];
int n, m, u, v, st[N], ed[N], rt[N];
struct node { int x, y; } P[N];
ll ansx, ansy;

inline void add(int x, int y) {
    to[++ e] = y, nex[e] = beg[x], beg[x] = e;
    to[++ e] = x, nex[e] = beg[y], beg[y] = e;
}

inline void dfs(int u, int f) {
    pos[st[u] = ++ tot] = u, dep[u] = dep[f] + 1;   
    fa[0][u] = f;
    Travel(i, u) if (v != f) dfs(v, u);
    ed[u] = tot;
}

namespace PRE {
#define mid (l + r >> 1)
    struct node { int l, r, v; } tr[N * 30];
    int cnt = 0;

    inline void update(int &now, int pre, int l, int r, int x) {
        now = ++ cnt, tr[now] = tr[pre], ++ tr[now].v;                  
        if (l < r) {
            if (x <= mid) update(tr[now].l, tr[pre].l, l, mid, x);
            else update(tr[now].r, tr[pre].r, mid + 1, r, x);
        }
    }

    inline int query(int u, int v, int l, int r, int L, int R) {
        if (tr[v].v - tr[u].v == 0) return 0;
        if (L <= l && r <= R) return tr[v].v - tr[u].v;
        if (R <= mid) return query(tr[u].l, tr[v].l, l, mid, L, R);
        if (L > mid) return query(tr[u].r, tr[v].r, mid + 1, r, L, R);
        return query(tr[u].l, tr[v].l, l, mid, L, R) + query(tr[u].r, tr[v].r, mid + 1, r, L, R);
    }

#undef mid
};

vector <int> vec[N];

int main() {
    File(), read();
    using namespace PRE;

    n = read(), m = read();
    For(i, 2, n) u = read(), v = read(), add(u, v);
    dfs(1, 0);

    For(j, 1, 17) For(i, 1, n) fa[j][i] = fa[j - 1][fa[j - 1][i]];
    For(i, 1, m) P[i].x = read(), P[i].y = read(), vec[P[i].x].push_back(P[i].y);
    
    For(i, 1, n) { // this if dfn
        int v = pos[i];
        if (vec[v].size() == 0) rt[i] = rt[i - 1];
        else {
            update(rt[i], rt[i - 1], 1, n, st[vec[v][0]]);
            For(j, 1, vec[v].size() - 1) update(rt[i], rt[i], 1, n, st[vec[v][j]]);
        }
    }

    For(i, 1, m) {
        int x = P[i].x, y = P[i].y;
        if (dep[x] < dep[y]) swap(x, y);

        if (st[y] <= st[x] && st[x] <= ed[y]) {
            int t = x; Forr(i, 17, 0) if (dep[fa[i][t]] > dep[y]) t = fa[i][t]; 
            
            ansx += query(rt[0], rt[st[t] - 1], 1, n, st[x], ed[x]);    
            ansx += query(rt[st[x] - 1], rt[ed[x]], 1, n, st[1], st[t] - 1);
            ansx += query(rt[ed[t]], rt[n], 1, n, st[x], ed[x]);
            ansx += query(rt[st[x] - 1], rt[ed[x]], 1, n, ed[t] + 1, n);

        } else {

            ansx += query(rt[st[x] - 1], rt[ed[x]], 1, n, st[y], ed[y]);
            ansx += query(rt[st[y] - 1], rt[ed[y]], 1, n, st[x], ed[x]);
        }
        -- ansx;
    }

    ansy = 1ll * m * (m - 1) / 2;
    ll gg = __gcd(ansx, ansy); ansx /= gg, ansy /= gg;
    printf("%lld/%lld\n", ansx, ansy);

    cerr << 1.0 * clock() / CLOCKS_PER_SEC << endl;
    return 0;
}

转载于:https://www.cnblogs.com/LSTete/p/9532215.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值