[九省联考2018]IIIDX 贪心 线段树
题解:
一开始翻网上题解看了好久都没看懂,感觉很多人都讲得不太详细,所以导致一些细节的地方看不懂,所以这里就写详细一点吧,如果有不对的or不懂的可以发评论在下面。
首先有一个比较明显的50分贪心:
先把d排好序,然后按从小到大的顺序贪心的给每个点选值,同等条件下优先编号大的,于是越小的值会越趋近于放在编号越大的上面。
但是这样在数字重复的情况下是不对的, 比如下面这组数据:
4 3.0
1 1 2 2
贪心会得到1 1 2 2 ,而正确答案是1 2 2 1.
因此换个角度考虑,在什么情况下一个点可以取到一个值x?
设这个点的子树大小为Size[i],那么这个点可以取到值x,当且仅当大于等于x的还没被取的值的个数 >= Size[i],原因应该是很好理解的,毕竟还要有Size[i] - 1和比x大的值放在i的子树上才行。
因此我们先对d从小到大排序去重,统计每个值的出现次数cnt[x], 然后对于每个数统计一个f[x]表示大于等于x的还没被取的值的个数为f[x].
假设我们给节点i匹配上了一个值x,那么这个策决策对小于等于x的值的影响是确定的,因此将所有小于等于x的值的f数组都减小SIze[i],表示这些值的右边可以取的值又减小了Size[i]个。
我们将和这个操作称为“预定”,因为我们现在并不知道点i的子树分别会选择哪些值,但我们知道它们要选几个值,所以我们相当于先告诉后面的人,这个节点i已经坐到了x这个值上,并且要求后面的人为它的子树留Size[i] - 1个座位。
因为这个决策对大于x的值的影响是不确定的,我们暂时没有修改它,但其实这个决策会对它产生影响,那么对于一个值k,在取它之前的决策到底对它产生了什么样的影响呢?
对于一个值k,它的真正的f[k]其实是min(f[1], f[2], f[3] ....f[k]),因为每个f值都相当于一个后缀和,一个合法的值不能使得f[k]反而比它前面的f值更大,因为前面的f值要比f[k]统计了更多的数。
因为题目要求使得字典序最大,所以我们按照编号从小到大给节点分配值显然是最优的。
因此我们每次就是要在剩下的数上寻找一个最靠右的,并且使得min(f[1], f[2], f[3] ...f[k]) >= Size[i]的k。
因为涉及区间修改和查询,我们使用线段树来维护所有的f值,每次选好一个值,我们就把一些已经确定的影响从线段树中删除(对一个区间进行- Size[i]的操作)。
对于每次选值,我们都可以在线段树上进行二分来查找满足上述粗体字要求的最靠右的值。
值得注意的是,在每次查询前,如果一个节点的父亲的影响还没有被撤销的话,应该要撤销它父亲的影响。(即把父亲给子树占的座给加回来 :1 ~ 父亲匹配值的f值 加上 Size[fa] - 1)
为什么呢?
想象一下,如果不撤销父亲的影响的话,岂不是相当于别人特意给你占了座,结果你自己还不能坐上去?
因为一个节点的儿子都是连续的,所以我们在撤销的父亲的影响后,会马上把不应该撤销的部分给补上(儿子的子树在不断的加上来),所以不用担心对之后的决策造成影响。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 551000 5 #define ac 2500000 6 7 int n, w, go, tot, rnt; 8 double k; 9 int ans[AC], cnt[AC], father[AC], Size[AC], f[AC], s[AC];//cnt[i]表示排名第i位的d值出现的次数 10 bool done[AC]; 11 12 inline int read() 13 { 14 int x = 0;char c = getchar(); 15 while(c > '9' || c < '0') c = getchar(); 16 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 17 return x; 18 } 19 20 inline int Min(int a, int b){ 21 return a > b ? b : a; 22 } 23 24 struct seg_tree{ 25 int tree[ac], lazy[ac], l[ac], r[ac]; 26 27 inline void update(int x){ 28 tree[x] = Min(tree[x * 2], tree[x * 2 + 1]); 29 } 30 31 inline void pushdown(int x) 32 { 33 if(lazy[x]) 34 { 35 int ll = x * 2, rr = ll + 1; 36 tree[ll] += lazy[x], tree[rr] += lazy[x]; 37 lazy[ll] += lazy[x], lazy[rr] += lazy[x]; 38 lazy[x] = 0; 39 } 40 } 41 42 void build(int x, int ll, int rr) 43 { 44 l[x] = ll, r[x] = rr; 45 if(ll == rr){tree[x] = f[ll]; return ;} 46 int mid = (ll + rr) >> 1; 47 build(x * 2, ll, mid), build(x * 2 + 1, mid + 1, rr); 48 update(x); 49 } 50 51 void change(int x, int ll, int rr) 52 { 53 pushdown(x); 54 if(l[x] == ll && r[x] == rr) 55 { 56 tree[x] += w, lazy[x] += w; 57 return ; 58 } 59 int mid = (l[x] + r[x]) >> 1; 60 if(rr <= mid) change(x * 2, ll, rr); 61 else if(ll > mid) change(x * 2 + 1, ll, rr); 62 else change(x * 2, ll, mid), change(x * 2 + 1, mid + 1, rr); 63 update(x); 64 } 65 66 void find(int x) 67 { 68 pushdown(x); 69 if(l[x] == r[x]){go = tree[x] >= w ? l[x] : l[x] - 1; return ;} 70 if(tree[x * 2] >= w) find(x * 2 + 1); 71 else find(x * 2); 72 update(x); 73 } 74 }T; 75 76 void pre() 77 { 78 n = read(), scanf("%lf", &k); 79 for(R i = 1; i <= n; i ++) s[i] = read(), Size[i] = 1; 80 sort(s + 1, s + n + 1); 81 for(R i = 1; i <= n; i ++) 82 { 83 ++ cnt[tot + 1]; 84 if(s[i] != s[i + 1]) s[++tot] = s[i]; 85 } 86 for(R i = tot; i; i --) 87 f[i] = f[i + 1] + cnt[i];//存下每个值右边有多少个可供选择的值 88 for(R i = n; i; i --) 89 father[i] = floor(i / k), Size[father[i]] += Size[i];//获取每个节点的Size 90 T.build(1, 1, tot); 91 } 92 93 void work() 94 { 95 for(R i = 1; i <= n; i ++) 96 { 97 int fa = floor(i / k); 98 if(fa && !done[fa]) w = Size[fa] - 1, T.change(1, 1, ans[fa]);//如果有父亲的话,要先把给儿子预定的节点还回来以帮助正确判断 99 w = Size[i], done[fa] = true; 100 T.find(1), w = -Size[i];//先找到一个合法的点 101 T.change(1, 1, go);//再减去已经被预定的位置 102 ans[i] = go; 103 printf("%d ", s[go]); 104 } 105 printf("\n"); 106 } 107 108 int main() 109 { 110 // freopen("in.in", "r", stdin); 111 pre(); 112 work(); 113 // fclose(stdin); 114 return 0; 115 }