CSP2019 树上的数

题目链接

Solution

以下是各位大佬们总结出来的正确的考场拿分顺序

10pts 暴力

(这不就是我在考场上写的吗 QWQ

暴搜枚举删边顺序,复杂度 (O(n!))

Code

附上考场代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 433333;
int n, k, T, w[N], ans[N], e[N], f[N], vis[N], tre[N];

bool lit()
{
    for(int i = 1; i <= n; i++)
        if(w[i] != ans[i]) return w[i] < ans[i];
    return false;
}

void dfs(int cnt)
{
    if(cnt == n - 1)
    {
        if(lit())
            for(int i = 1; i <= n; i++) ans[i] = w[i];
        return ;
    }
    for(int i = 1; i < n; i++)
    {
        if(vis[i]) continue;
        swap(w[tre[e[i]]], w[tre[f[i]]]);
        swap(tre[e[i]], tre[f[i]]);
        vis[i] = 1;
        dfs(cnt + 1);
        vis[i] = 0;
        swap(w[tre[e[i]]], w[tre[f[i]]]);
        swap(tre[e[i]], tre[f[i]]);
    }
}

int main()
{
    //freopen("tree.in", "r", stdin);
    //freopen("tree.out", "w", stdout);
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        memset(vis, 0, sizeof(vis));
        memset(ans, 0x3f, sizeof(ans));
        memset(w, 0, sizeof(w));
        memset(tre, 0, sizeof(tre));
        memset(e, 0, sizeof(e));
        memset(f, 0, sizeof(f));
        for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
        for(int i = 1; i <= n; i++) tre[w[i]] = i;
        for(int i = 1; i < n; i++)
            scanf("%d%d", &e[i], &f[i]);
        dfs(0);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        printf("
");
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

35pts 菊花图

假设交换顺序是 {(p_{rt}, p_1, p_2, p_3...p_{n-1})},经过手模可以发现,最后每个点的权值都跑到下一个点的位置上去了。就是 (a_{rt}) -> (p_1) , (a_{p_1}) -> (p_2) , (a_{p_2}) -> (p_3...a_{p_{n-1}}) -> (p_{rt})

于是我们得出一个性质:对于每个点,让它与它最后的位置连边,最后会形成一个大环。既然是一个大环,那么中间形成小环的情况就不合法。因此只需要贪心地连边,用并查集维护。

Code

代码仅是菊花图。

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

const int N = 2333333;
int T, rt, n, p[N], a[N], du[N], fa[N], vis[N], ans[N];

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        memset(fa, 0, sizeof(fa));
        memset(du, 0, sizeof(du));
        memset(vis, 0, sizeof(vis));
        memset(ans, 0, sizeof(ans));
        for(int i = 1; i <= n; i++) scanf("%d", &p[i]), a[p[i]] = i;
        for(int u, v, i = 1; i < n; i++) scanf("%d%d", &u, &v), du[u]++, du[v]++;
        for(int i = 1; i <= n; i++) fa[i] = i; //并查集初始化 
        for(int i = 1; i <= n; i++) if(du[i] > 1) rt = a[i]; //找到菊花图的根节点 
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                if(!vis[j] && find(p[i]) != find(j)) //找当前权值能到的最小的点 
                {
                    fa[find(p[i])] = find(j);
                    ans[i] = j, vis[j] = 1;
                    break;
                }
        for(int i = 1; i < n; i++) printf("%d ", ans[i]);
        for(int i = 1; i <= n; i++) if(!vis[i]) printf("%d
", i);
    }
    return 0;
}

60pts 链

(开局一张图,内容全靠编)

假设图中的 (a_u)向右跑到图中的 (v) 点去,我们可以得到以下几条性质:

(1.) (u) 点的右边要在左边删之前删,要不然 (u) 点早就跑到左边了。

(2.) (u)(v) 之间的点都是先删左边再删右边

(2.) (v) 点右边的边要在左边的边删之前删,要不然好不容易跑到 (v) 点的 (a_u) 一会儿就会继续向右跑

同样的道理,(a_v) 向左跑到 (u) 也可以轻松地推出来。

抽象一下这个东西:给每个点打一个 tag,tag = 0 表示未标记;tag = 1 表示先删左边再删右边;tag = 2 表示先删右边再删左边

每次只要贪心地去找,让较小的权值尽量到达编号较小的点。删边过程中添加的标记不能与已有的标记冲突。

注意: 链开头的点和结尾的点没有标记

Code

代码仅是链。

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

const int N = 6666;
struct edge { int nxt, to; } e[N];
int T, n, st, tot, cnt, l[N], p[N], a[N], du[N], vis[N];
int ans[N], tag[N], use[N], head[N];

void add(int x, int y)
{
    e[++cnt] = (edge) { head[x], y };
    head[x] = cnt;
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        tot = cnt = st = 0;
        memset(p, 0, sizeof(p));
        memset(l, 0, sizeof(l));
        memset(du, 0, sizeof(du));
        memset(use, 0, sizeof(use));
        memset(vis, 0, sizeof(vis));
        memset(ans, 0, sizeof(ans));
        memset(head, 0, sizeof(head));
        for(int i = 1; i <= n; i++) scanf("%d", &p[i]);
        for(int u, v, i = 1; i < n; i++)
        {
            scanf("%d%d", &u, &v);
            add(u, v), add(v, u);
            du[u]++, du[v]++;
        }
        for(int i = 1; i <= n; i++) if(du[i] == 1) { st = i; break; }
        // 找到这条链 
        while(st)
        {
            int fl = 0; 
            l[++tot] = st, vis[st] = 1, use[st] = tot;
            for(int i = head[st]; i; i = e[i].nxt)
            {
                int v = e[i].to;
                if(!vis[v])
                {
                    st = v, fl = 1;
                    break;
                }
            }
            if(fl == 0) break;
        }
        // 开始贪心统计答案 
        memset(tag, 0, sizeof(tag));
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
        {
            int res = 0x3f3f3f3f, ki = 0;
            // 往右边走 
            if(!tag[p[i]] || tag[p[i]] == 2)
            {
                for(int j = use[p[i]] + 1; j <= n; j++)
                {
                    if(!tag[l[j - 1]] || tag[l[j - 1]] == 1 || l[j - 1] == p[i])
                    {
                        if((!tag[l[j]] || tag[l[j]] == 2) && !vis[l[j]])
                            if(res > l[j]) res = l[j], ki = 1;
                    }
                    else break;
                }
            }
            // 往左边走 
            if(!tag[p[i]] || tag[p[i]] == 1)
            {
                for(int j = use[p[i]] - 1; j >= 1; j--)
                {
                    if(!tag[l[j + 1]] || tag[l[j + 1]] == 2 || l[j + 1] == p[i])
                    {
                        if((!tag[l[j]] || tag[l[j]] == 1) && !vis[l[j]])
                            if(res > l[j]) res = l[j], ki = 2;
                    }
                    else break;
                }
            }
            // 如果往右边走更优 
            if(ki == 1)
            {
                tag[p[i]] = 2, tag[res] = 2;
                for(int j = use[p[i]] + 1; l[j] != res; j++) tag[l[j]] = 1;
            }
            // 如果往左边走更优 
            if(ki == 2)
            {
                tag[p[i]] = 1, tag[res] = 1;
                for(int j = use[p[i]] - 1; l[j] != res; j--) tag[l[j]] = 2;
            }
            tag[l[1]] = tag[l[n]] = 0, ans[i] = res;
            vis[res] = 1;
        }
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        printf("
");
    }
    return 0;
}

100pts 正解

不会

原文地址:https://www.cnblogs.com/Andy-park/p/13778312.html