ARC076 F Exhausted? Hall定理 + 线段树扫描线
题目大意:
有n个人,m个座位,每个人可以匹配的座位是[1, li] || [ri, m],可能有人不需要匹配座位(默认满足),问最少有多少人不能被满足。
题解:
首先可以看出这是一个二分图匹配,根据hall定理,我们只需要求出max(人的子集大小 - 被选出的人可以选的座位集合大小)。
但是枚举人的复杂度太高,所以考虑枚举座位集合,因为每个人的可选区间都是一段前缀or后缀,因此要表达一个合法的座位集合,我们只需要所有人中最右边的li和最左边的ri即可。
如图所示:
因此这个时候要使得尽可能接近max,就要把所有可选区间不超过我们当前枚举的区间的人都加进来。
可以使用扫描线,求出对于每个R,所有的L相对应的值。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 400100 5 #define ac 1601000//error!!!数据范围是2 00000, 不是1开头!!! 6 7 int n, m, ans = -INT_MAX, w; 8 int Head[AC], Next[ac], date[ac], tot; 9 int tree[ac], lazy[ac], l[ac], r[ac], l_[AC], r_[AC]; 10 11 inline int read() 12 { 13 int x = 0;char c = getchar(); 14 while(c > '9' || c < '0') c = getchar(); 15 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 16 return x; 17 } 18 19 inline void add(int f, int w) 20 { 21 date[++tot] = w, Next[tot] = Head[f], Head[f] = tot; 22 } 23 24 inline void upmax(int &a, int b) 25 { 26 if(b > a) a = b; 27 } 28 29 void pre() 30 { 31 n = read(), m = read(); 32 for(R i = 1; i <= n; i ++) 33 l_[i] = read(), r_[i] = read(), add(r_[i], i); 34 } 35 36 inline void pushdown(int x) 37 { 38 if(lazy[x]) 39 { 40 int ll = x * 2, rr = ll + 1; 41 lazy[ll] += lazy[x], lazy[rr] += lazy[x]; 42 tree[ll] += lazy[x], tree[rr] += lazy[x];//这里因为是+=,所以必须用lazy[x],不然会将lazy[ll]中的一些东西重复统计 43 lazy[x] = 0;//最后才清空!!!!!!!!!! 44 }//error!!!是区间加,不是赋值,不能直接覆盖,要+= 45 } 46 47 inline void update(int x) 48 { 49 tree[x] = max(tree[x * 2], tree[x * 2 + 1]); 50 } 51 52 void build(int x, int ll, int rr) 53 { 54 l[x] = ll, r[x] = rr; 55 if(ll == rr) 56 { 57 tree[x] = -ll + 1; 58 return ; 59 } 60 int mid = (ll + rr) >> 1; 61 build(x * 2, ll, mid); 62 build(x * 2 + 1, mid + 1, rr); 63 update(x); 64 } 65 66 void change(int x, int ll, int rr) 67 { 68 pushdown(x); 69 if(l[x] == ll && r[x] == rr) 70 { 71 lazy[x] += w, tree[x] += w; 72 return ; 73 } 74 int mid = (l[x] + r[x]) >> 1; 75 if(rr <= mid) change(x * 2, ll, rr); 76 else if(ll > mid) change(x * 2 + 1, ll, rr); 77 else 78 { 79 change(x * 2, ll, mid); 80 change(x * 2 + 1, mid + 1, rr); 81 } 82 update(x); 83 } 84 85 void find(int x, int ll, int rr) 86 { 87 pushdown(x); 88 if(l[x] == ll && r[x] == rr) 89 { 90 upmax(ans, tree[x]); 91 return ; 92 } 93 int mid = (l[x] + r[x]) >> 1; 94 if(rr <= mid) find(x * 2, ll, rr); 95 else if(ll > mid) find(x * 2 + 1, ll, rr); 96 else find(x * 2, ll, mid), find(x * 2 + 1, mid + 1, rr);//这是取max啊,,,, 97 } 98 99 void work() 100 { 101 int now; 102 ans = n - m;//r = 0的情况 103 for(R i = Head[m + 1]; i; i = Next[i]) 104 { 105 now = date[i], w = 1; 106 change(1, l_[now] + 1, m + 1); 107 //find(1, 4, 4); 108 } 109 //find(1, 4, 4); 110 upmax(ans, tree[1]); 111 for(R i = m; i; -- i) 112 { 113 w = -1; 114 change(1, 1, i + 1); 115 for(R j = Head[i]; j; j = Next[j]) 116 { 117 now = date[j], w = 1; 118 change(1, l_[now] + 1, i + 1); 119 } 120 find(1, 1, i + 1);//左端点在后面就不合法了 121 } 122 printf("%d\n", ans); 123 } 124 125 int main() 126 { 127 freopen("in.in", "r", stdin); 128 pre(); 129 build(1, 1, m + 1);//要多出一位来代表左端点取0的情况 130 work();//ri最大居然可以到m+1... 131 fclose(stdin); 132 return 0; 133 }