第十四届华中科技大学程序设计竞赛 部分有趣题的题解(A, C, I, L)

传送门
A: 题意: 就是定义树上的一个三元组(a, b, c) 成立的条件是|dis(a, c) - dis(b, c)| % 2 == n % 2; 给定一棵树, 问树上这样的三元组有多少个.

思路: 分析可知, 我们肯定是把枚举c, 然后判断每一个c有多少个a, b, 可以填, 然后我们发现填这个是和n的奇偶性有关, n为奇, 那么一定时奇-偶(或者相反), 那么我们要知道的就是对于树上每一个点, 距离它的距离为奇偶的点分别有多少个, 然后知道树上每一点距离的奇偶点的数目是一定的, 可以事先处理出来, 然后直接算就行了, n为偶数同理

AC Code

const int maxn = 1e5+5;
int a[2], dis[maxn];
int n;
vector<int>g[maxn];
void dfs(int u, int fa, int d) {
    a[d]++; dis[u] = d;
    for (int i = 0 ; i < sz(g[u]) ; i ++) {
        int to = g[u][i];
        if (to == fa) continue;
        dfs(to, u, 1-d);
    }
}
void solve() {
    scanf("%d", &n);
    for (int i = 1 ; i < n ; i ++) {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].pb(v); g[v].pb(u);
    }
    dfs(1, -1, 0);
    ll ans = 0;
    if (n&1) ans += 1ll*a[0]*a[1]*2*n;
    else ans += 1ll*a[0]*a[0]*n + 1ll*a[1]*a[1]*n;
    printf("%lld\n", ans);
    for (int i = 1 ; i <= n ; i ++) g[i].clear();
    a[0] = a[1] = 0;
}

C: 题意&&思路: 就是维护并查集的一些操作, 集合并, 询问树的size, 是否在同一个集合, 和将一个点从当前集合分离出来, 那么实际上就是给这个点新开一个点, 然后每次访问这个点的时候就去访问它新开的那个点就行了.
AC Code

const int maxn = 2e5+5;
int fa[maxn], vis[maxn], r[maxn];
int Find(int x) {
    return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
void Un(int x, int y)  {
    int fx = Find(x);
    int fy = Find(y);
    if (fx == fy) return ;
    if (r[fx] < r[fy]) swap(fx, fy);
    fa[fx] = fy;
    r[fy] += r[fx]; r[fx] = 0;
}
int n, q;
void init() {
    for (int i = 1 ; i <= n ; i ++) {
        fa[i] = i; vis[i] = i;
        r[i] = 1;
    }
}
void solve() {
    scanf("%d%d", &n, &q);
    int m = n; init();
    while(q--) {
        int op, u, v ;
        scanf("%d%d", &op, &u);
        if (op == 1) {
            scanf("%d", &v);
            Un(vis[u], vis[v]);
        }
        else if (op == 2) {
            --r[Find(vis[u])];
            vis[u] = ++m;
            fa[m] = m;
            r[m] = 1;
        }
        else if (op == 3) {
            printf("%d\n", r[Find(vis[u])]);
        }
        else {
            scanf("%d", &v);
            printf("%s\n", Find(vis[u]) == Find(vis[v]) ? "YES":"NO");
        }
    }
}

I: 题意: 给定一个序列, 问这个序列的所有子区间中最大值-最小值的值加起来等于多少
思路: 所以我们要维护的就是每一个数它作为最大值和最小值往左和往右最多能扩展到什么位置, 这个我们可以在O(n)的时间内做到, 注意边界问题, 所以我们要一方取=, 一方不取等, 然后就是算一下每一个数在每个区间对答案的贡献就行了.

AC Code

const int maxn = 1e6+5;
int lmx[maxn], rmx[maxn], lmi[maxn], rmi[maxn];
int a[maxn];
void solve() {
    int n;
    while(~scanf("%d", &n)) {
        for (int i = 1 ; i <= n ; i ++) {
            scanf("%d", a+i);
            lmx[i] = rmx[i] = lmi[i] = rmi[i] = i;
        }
        int id1, id2;
        for (int i = 2 ; i <= n ;i ++) {
            id1 = i;
            while(id1 > 1 && a[i] >= a[id1-1])
                id1 = lmx[id1-1];
            lmx[i] = id1;
            id2 = i;
            while(id2 > 1 && a[i] <= a[id2-1])
                id2 = lmi[id2-1];
            lmi[i] = id2;
        }
        for (int i = n-1 ; i >= 1 ; i --) {
            id1 = i;
            while(id1 < n && a[i] > a[id1+1])
                id1 = rmx[id1+1];
            rmx[i] = id1;
            id2 = i;
            while(id2 < n && a[i] < a[id2+1])
                id2 = rmi[id2+1];
            rmi[i] = id2;
        }
        ll ans = 0;
        for (int i = 1 ; i <= n ; i ++) {
            ans += 1ll*(rmx[i]-i+1)*(i-lmx[i]+1)*a[i];
            ans -= 1ll*(rmi[i]-i+1)*(i-lmi[i]+1)*a[i];
        }
        printf("%lld\n", ans);
    }
}

L题: 就是n个操作, 每次会在一个地方上种上一棵树, 问每次操作后被树围起来的区域有多大.

思路: 这道题最关键的一点就是我们正着不好求, 所以我们倒着bfs, 每次消失一棵树, 然后从这棵树的四周有墙的地方开始搜, 跟新答案就行了. 最关键的地方就是倒着bfs!!!
AC Code

const int maxn = 2e3 + 5;
const int maxm = 1e5 + 5;
pii a[maxm];
int s[maxn][maxn], ans[maxm], tot;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
void bfs(int sx, int sy) {
    queue<pii>q;
    s[sx][sy] = 1;
    q.push({sx, sy});
    while(!q.empty()) {
        pii u = q.front();
        q.pop();
        for (int i = 0 ; i < 4 ; i ++) {
            int xx = u.fi + dx[i];
            int yy = u.se + dy[i];
            if (xx < 0 || xx >= 2000 || yy < 0 || yy >= 2000) continue;
            if (s[xx][yy]) continue;
            -- tot; s[xx][yy] = 1;
            q.push({xx, yy});
        }
    }
}
void solve() {
    int n; scanf("%d", &n); tot = 4000000;
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%d%d", &a[i].fi, &a[i].se);
        a[i].fi += 1000; a[i].se += 1000;
        s[a[i].fi][a[i].se] = 2;
        -- tot;
    }
    --tot; bfs(0, 0); // 因为不可能到(0, 0)这个点, 所以从这个点出发一定能遍历图
    ans[n] = tot;
    for (int i = n - 1 ; i >= 1 ; i --) {
        int xx = 0, yy = 0;
        for (int j = 0 ; j < 4 ; j ++) {
            int tx = a[i+1].fi + dx[j];
            int ty = a[i+1].se + dy[j];
            if (s[tx][ty] == 1) {
                xx = tx, yy = ty;
            }
        }
        s[a[i+1].fi][a[i+1].se] = 0;
        ++ tot;
        bfs(xx, yy);
        ans[i] = tot;
    }
    for (int i = 1 ; i <= n ; i ++) {
        printf("%d\n", ans[i]);
    }
}

你可能感兴趣的:(比较杂的题解,想法思维题)