1 条题解

  • 0
    @ 2025-8-24 22:09:06

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar xht
    好想爱这个世界啊

    搬运于2025-08-24 22:09:06,当前版本为作者最后更新于2019-04-11 04:50:11,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目地址:P5290 [十二省联考2019]春节十二响

    骗分方法

    如果你实在一点思路也没有,暴力都不会打,那么请考虑一下骗分。

    方法一

    输出所有 MM 的和。

    期望得分:0分。

    实际还有5分

    方法二

    注意到有 1515 分为一条链,分两种情况考虑:

    1. 1号点有一个儿子——详见方法一。
    2. 1号点有两个儿子——把对这两个儿子下的两条链弄成两个堆,每次取出两个堆的堆顶,取 maxmax 加入答案,当一个堆取尽后,把另一个堆里的所有元素加入答案,最后加入 M1M_1

    期望得分:15分。

    暴力方法

    如果你的暴力时间复杂度很低并且常数很优秀,那么拿到一道题的大部分分数是很容易的。

    方法三

    可以写一个很高超的纯暴搜过掉数据较小的2~4个点。

    然而说实话这道题写纯暴搜的难道貌似大于写正解2333

    时间复杂度:不详。

    期望得分:10~20分。

    方法四

    如果两个点是祖先后代关系,则在这两点之间连边,最终会形成一个 nn 个点的图。则答案是这个图的一个最大独立集。

    图的最大独立集是 NPC 问题,最快的方法貌似是状压, O(3n)O(3^n)

    期望得分:45分。

    方法五

    借用方法四的思想,如果 x,yx,y 是祖先后代关系,则 ax,ya_{x,y}11 ,否则为 00 ,这样可以构造出来一个 n×nn \times n 的 01 矩阵。

    MM 值从大到小贪心地选。每选择一个就再从大到小把能选的都选上,然后把选择的这个的 MM 值加入答案。

    由于每次选完之后都要更新可选的集合,这个更新是 O(n)O(n) 的,而每次选择一个之后,还有要从大到小把能选的都选上,这个过程也是 O(n)O(n) 的,一共要进行 O(n)O(n) 次选择,所以总时间复杂度为 O(n3)O(n^3) 的。

    期望得分:45分。

    方法六

    从方法四的 O(3n)O(3^n) 到方法五 O(n3)O(n^3) ,期望得分没变海星。

    考虑优化方法五,我们在方法五中看到这两个词:01 矩阵,集合。尝试用 bitset 优化,时间复杂度严格来讲没变,但是常数变成原来的 164\frac{1}{64}

    期望得分:60分。

    方法七

    换一种思路。

    考虑类似方法二的第 22 种情况合并两颗子树。

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

    期望得分:60分。

    考场代码

    我在考场上写出来的代码是方法六和方法二的结合版,得分为 7575 分。

    #include <bits/stdc++.h>
    #define ll long long
    #define pii pair<int, int>
    #define mp make_pair
    using namespace std;
    const int N = 2e5 + 6;
    int n, a[N], f[N];
    ll ans = 0;
    
    inline int rd() {
        int x = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9') ch = getchar();
        while (ch >= '0' && ch <= '9') x = x * 10 + (ch - '0'), ch = getchar();
        return x;
    }
    
    inline bool pd1() {
        for (int i = 2; i <= n; i++) if (f[i] != i - 1) return 0;
        return 1;
    }
    
    inline void P101112_1() {
        for (int i = 1; i <= n; i++) ans += a[i];
        cout << ans << endl;
    }
    
    namespace P101112_2 {
        int deg[N];
    
        inline bool pd() {
            for (int i = 2; i <= n; i++) ++deg[f[i]];
            if (deg[1] != 2) return 0;
            for (int i = 2; i <= n; i++) if (deg[i] > 1) return 0;
            return 1;
        }
    
        int son[N];
        priority_queue<int> q[2];
    
        inline void work() {
            int g[2], t = 0;
            for (int i = 2; i <= n; i++)
                if (f[i] == 1) g[t++] = i;
                else son[f[i]] = i;
            ans = a[1];
            for (int i = 0; i < 2; i++) {
                int x = g[i];
                while (x) q[i].push(a[x]), x = son[x];
            }
            while (q[0].size() && q[1].size())
                ans += max(q[0].top(), q[1].top()), q[0].pop(), q[1].pop();
            for (int i = 0; i < 2; i++)
                if (q[i].size())
                    while (q[i].size()) ans += q[i].top(), q[i].pop();
            cout << ans << endl;
        }
    }
    
    namespace TX {
        const int M = 2e3 + 6;
        bitset<M> b[M], o, v;
        vector<int> e[M];
        int st[M], top = 0, p[M];
        pii g[M];
    
        void dfs(int x) {
            b[p[x]] = o;
            for (int i = 1; i <= top; i++) b[p[st[i]]][p[x]] = 0;
            st[++top] = x;
            o[p[x]] = 0;
            for (unsigned int i = 0; i < e[x].size(); i++) {
                int y = e[x][i];
                if (!o[p[y]]) continue;
                dfs(y);
            }
            o[p[x]] = 1;
            --top;
        }
    
        inline void work() {
            for (int i = 2; i <= n; i++) e[f[i]].push_back(i);
            for (int i = 1; i <= n; i++) g[i] = mp(a[i], i);
            sort(g + 1, g + n + 1);
            for (int i = 1; i <= n; i++) p[g[i].second] = i;
            o.set();
            dfs(1);
            v.reset();
            for (int i = n; i; i--) {
                if (v[i]) continue;
                o = b[i];
                ans += g[i].first;
                v[i] = 1;
                for (int j = i - 1; j; j--)
                    if (o[j] && !v[j]) {
                        v[j] = 1;
                        o &= b[j];
                    }
            }
            cout << ans << endl;
        }
    }
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) a[i] = rd();
        for (int i = 2; i <= n; i++) f[i] = rd();
        if (pd1()) {
            P101112_1();
            return 0;
        }
        if (P101112_2::pd()) {
            P101112_2::work();
            return 0;
        }
        if (n < 2001) {
            TX::work();
            return 0;
        }
        return 0;
    }
    

    正确方法

    方法八

    方法七是 O(n2)O(n^2) 的,思考瓶颈在哪儿?

    合并两颗子树 x,yx,y 时,我们相当于进行了 O(max(sizex,sizey))O(max(size_x,size_y))

    能否将复杂度降到 O(min(sizex,sizey))O(min(size_x,size_y))

    先考虑降复杂度之后,总的时间复杂度是多少?

    降复杂度之后,相当于每一次合并,用 O(min(sizex,sizey))O(min(size_x,size_y)) 扔掉了 min(sizex,sizey)min(size_x,size_y) 个元素。换句话说,扔掉一个元素是 O(1)O(1) 的。我们要把 nn 个元素经过若干次合并,扔掉 O(n)O(n) 个元素,最终剩下 11 个元素。那么扔元素的复杂度为 O(n)O(n) ,考虑堆的影响,总时间复杂度为 O(n log n)O(n\ log\ n)

    时间复杂度满足限制,可以放心的回去考虑如何降复杂度了。

    当我们取完 min(sizex,sizey)min(size_x,size_y) 个元素后,一个堆是空的,另一个堆还剩下一些元素。

    那我们直接把刚才取出来的元素再塞到非空的那个堆中不就完了么?

    蛤?正解这么暴力?

    我再告诉你,这个正解还有个好听的名字:启发式合并。

    代码实现细节

    有一个小细节是,代码实现中会出现 swap 两个堆的情况。

    ouuan 的博客十二省联考2019 游记 & 题解中,对此有这样的说法:

    swap 在不开 C++11 的情况下是 O(n)O(n) 的,开 C++11 则是 O(1)O(1) 的,如果不开 C++11 可以记录 id 然后交换 id

    最终的代码真心好写而且好短QwQ

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5 + 6;
    int n, a[N], f;
    vector<int> e[N], o;
    priority_queue<int> q[N];
    
    inline void merge(int x, int y) {
    	if (q[x].size() < q[y].size()) swap(q[x], q[y]);
    	while (q[y].size()) {
    		o.push_back(max(q[x].top(), q[y].top()));
    		q[x].pop(), q[y].pop();
    	}
    	while (o.size()) q[x].push(o.back()), o.pop_back();
    }
    
    void dfs(int x) {
    	for (unsigned int i = 0; i < e[x].size(); i++)
    		dfs(e[x][i]), merge(x, e[x][i]);
    	q[x].push(a[x]);
    }
    
    int main() {
    	cin >> n;
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    	for (int i = 2; i <= n; i++) scanf("%d", &f), e[f].push_back(i);
    	dfs(1);
    	long long ans = 0;
    	while (q[1].size()) ans += q[1].top(), q[1].pop();
    	cout << ans << endl;
    	return 0;
    }
    
    • 1

    信息

    ID
    4274
    时间
    1000ms
    内存
    500MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者