zoukankan      html  css  js  c++  java
  • JZOJ 6653. 【2020.05.27省选模拟】树(权值线段树)

    JZOJ 6653. 【2020.05.27省选模拟】树

    题目大意

    • 一棵以 1 1 1为根大小为 n n n的树,要求父亲编号小于儿子, 2 n − 2 2n-2 2n2个数,可任意分配使它们作为树的边权和每个点的父亲编号,求各种分配下 1 1 1 n n n路径长度分别为 [ 1 , n ) [1,n) [1,n)时路径的最大边权和。
    • n ≤ 1 0 5 nle10^5 n105

    题解

    • 树的形态确定后,最大边权和自然为剩余未选的若干个数之和。
    • 统计每个数出现的个数 c i c_i ci,求出前缀和,若存在 s u m i − 1 < i − 1 sum_{i-1}<i-1 sumi1<i1则必然无解,因为可选的父亲个数小于儿子个数;若存在 s u m i − 1 = i − 1 sum_{i-1}=i-1 sumi1=i1则它 i i i必然要作为 1 1 1 n n n路径上的点,不然没有多余的父亲编号让 i i i后某个点连向 i i i前某个点;其他的点都是在或不在路径上皆可。
    • 否则,先考虑从 1 1 1 n n n路径最长的情况,自然可以把所有 c i ≠ 0 c_i ot=0 ci=0作为中间点连接起来,然后剩下的点从前往后选择编号较小的儿子,即可求出当前最大的边权和。
    • 记录下作为答案的数字集合 D D D,以及剩余可选的数字集合 C C C
    • 接着考虑减小路径长度,为了剩出更大的数,则从后往前删去可不在路径上的点 x x x
    • 此时因为边数减少了,则从 D D D中挑出最小的数删去,放入 C C C,重复做两遍这个过程,因为接下来的操作可能可以加入更大的数。
    • x x x和父亲断开了,则把它父亲放入 C C C,再把它连向 C C C中最小的点,
    • 最后找到 C C C中最大的数加入 D D D,并从 D D D中删去。
    • C C C D D D用权值线段树维护。

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define N 100010
    #define ll long long
    ll dis[N], F[N];
    int n, a[N * 2], c[N], d[N], tp[N], fr[N], q[N];
    struct {
    	int q[N * 4];
    	void add(int v, int l, int r, int x, int c) {
    		if(l == r) q[v] += c;
    		else {
    			int mid = (l + r) / 2;
    			if(x <= mid) add(v * 2, l, mid, x, c); else add(v * 2 +1, mid + 1, r, x, c);
    			q[v] = q[v * 2] + q[v * 2 + 1];
    		}
    	}
    	int find_mx(int v, int l, int r) {
    		if(l == r) return l;
    		int mid = (l + r) / 2;
    		if(q[v * 2 + 1] > 0) return find_mx(v * 2 + 1, mid + 1, r);
    		return find_mx(v * 2, l, mid);
    	}
    	int find_mi(int v, int l, int r) {
    		if(l == r) return l;
    		int mid = (l + r) / 2;
    		if(q[v * 2] > 0) return find_mi(v * 2, l, mid);
    		return find_mi(v * 2 + 1, mid + 1, r);
    	}
    	void ins(int k, int c) {
    		add(1, 1, n, k, c);
    	}
    	int ans_mx() {
    		return find_mx(1, 1, n);
    	}
    	int ans_mi() {
    		return find_mi(1, 1, n);
    	}
    }di, ci;
    int main() {
    	int i, j, k;
    	scanf("%d", &n);
    	for(i = 1; i <= 2 * n - 2; i++) scanf("%d", &a[i]), c[a[i]]++;
    	memset(dis, 255, sizeof(dis));
    	sort(a + 1, a + n * 2 - 1);
    	for(i = 1; i <= n; i++) {
    		F[i] = F[i - 1] + c[i];
    		if(F[i - 1] < i - 1 || (i < n && F[i - 1] == i - 1 && !c[i])) {
    			for(j = 1; j < n; j++) printf("-1 ");
    			return 0;
    		}
    	}
    	int sum = 0, s = 0, la = 1;
    	for(i = 2; i <= n; i++) {
    		if(F[i - 1] == i - 1 || i == n) tp[i] = 1, s++;
    		if(c[i] || i == n) fr[i] = la, c[la]--, sum++, la = i;
    	}
    	j = 1;
    	for(i = 2; i < n; i++) if(!fr[i]) {
    		while(!c[j]) j++;
    		c[j]--, fr[i] = j;
    	}
    	int t = sum; 
    	dis[sum] = 0;
    	for(i = n - 1; i; i--) while(c[i] && t) dis[sum] += i, c[i]--, d[i]++, t--;
    	for(i = 1; i < n; i++) di.ins(i, d[i]), ci.ins(i, c[i]);
    	j = n, k = 1;
    	while(--sum >= s) {
    		while(tp[fr[j]]) j = fr[j];
    		int x = fr[j];
    		dis[sum] = dis[sum + 1];
    		
    		k = di.ans_mi();
    		di.ins(k, -1);
    		ci.ins(k, 1);
    		dis[sum] -= k;
    		
    		k = di.ans_mi();
    		di.ins(k, -1);
    		ci.ins(k, 1);
    		dis[sum] -= k;
    		
    		fr[j] = fr[x];
    		ci.ins(x, 1);
    		
    		k = ci.ans_mi();
    		ci.ins(k, -1);
    		fr[x] = k;
    		
    		k = ci.ans_mx();
    		di.ins(k, 1);
    		ci.ins(k, -1);
    		dis[sum] += k;
    	}
    	for(i = 1; i < n; i++) printf("%lld ", dis[i]);
    	return 0;
    }
    

    自我小结

    • 尽管这题有 O ( n ) O(n) O(n)的做法,但这种解法优化到 O ( n ) O(n) O(n)并不容易,另外因为 C C C同时需要最小值和最大值,所以用堆维护也比较麻烦,因此在尝试上花费了较多的时间。
    • 维护最大最小值,使用权值线段树虽然时间并非最优,但想法自然,写起来也方便。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    __doPostBack的使用
    【转】function,new,constructor and prototye
    谈谈一些网页游戏失败的原因到底有哪些?(转)
    全面剖析页游巨头发家史(转)
    2013.02.20开通博客
    老子喜欢的女人
    如何成为强大的程序员?(转)
    注重健康
    学习方法总结
    数据库知识点滴积累
  • 原文地址:https://www.cnblogs.com/LZA119/p/14608429.html
Copyright © 2011-2022 走看看