「6月雅礼集训 2017 Day4」qyh(bzoj2687 交与并)
原题传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2687
【题目大意】
给出若干区间,求一个区间的大于等于2的子集,使得 |区间并| 和 |区间交| 的乘积最大。
$1\leq n,L_i,R_i\leq 10^6$
【题解】
把区间去掉包含情况,然后进行排序,变成$l_i$和$r_i$都递增的数列。
然后容易发现取得区间一定是连续的一段。
然后我们推一推决策单调性。
容易得出当$j$优于$k$的情况:
$r_i * (r_j - r_k) + l_i * (l_j - l_k) > l_j * r_j - l_k * r_k$
当$i$变化成$i+1$的时候,若$j > k$,那么如下情况还成立。
说明当$i$往右的时候,最优决策点不会往左。
然后要注意的是,还有一种情况,也就是包含的情况需要讨论。
我的做法可能比较奇怪,包含的情况,对于每个被包含的区间,贡献最大值是包含它的长度最长的区间。
我们对左端点进行排序,发现要找的包含的一定是右端点大于当前讨论区间的右端点(左端点已经固定小于了),的最长区间。
我们对于区间长度建一棵线段树,然后线段树维护长度在区间内的右端点max,询问相当于在线段树上二分,复杂度$O(logn)$。
现在来讨论有了决策单调性要怎么办
1. 维护一个i递增,答案递减的的单调队列。
2. 二分决策
考虑第一种方案,当$i$右移的时候,前面一个单调下降函数,不一定会变成一个单峰或单调下降的函数,可能是有很多峰值,我们当前爬
上的这个峰不是最优解。
网上基本上所有单调队列代码都是错的(我只看到一份对的),包括我之前发的那份文章也是错的。可以被这个数据卡掉:
5
0 100
10 105
20 112
25 115
30 140
最优解是选择[20,112], [25,115], [30,140](中间的那个区间可以选可以不选)。
可是由于我们的单调队列的设定,左端点会一直停留在[0,100]这个区间,实际上后面的[20,112]这个区间比[0,100]更优。
所以只能二分决策了。。
二分的时候有个技巧,就是可以按照类似于整体二分的思路。
定义solve(l, r, al, ar)
表示目前处理[l,r]之间的转移,决策点在[al, ar]之间。
然后每次暴力找出mid的时候的决策即可。
# include <queue> # include <stdio.h> # include <string.h> # include <iostream> # include <algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; const int M = 2e5 + 10, N = 1e6 + 10, F = 1e6; const int mod = 1e9 + 7; const ll inf = 1e15; inline int getint() { int x = 0; char ch = getchar(); while(!isdigit(ch)) ch = getchar(); while(isdigit(ch)) { x = (x<<3) + (x<<1) + ch - '0'; ch = getchar(); } return x; } int n, q[N]; ll ans; struct pa { int l, r; pa() {} pa(int l, int r) : l(l), r(r) {} friend bool operator < (pa a, pa b) { return a.l < b.l || (a.l == b.l && a.r > b.r); } }p[N], t[N]; int tn = 0; inline bool cmp(pa a, pa b) { return a.l < b.l || (a.l == b.l && a.r < b.r); } inline ll gsum(int i, int j) { return (ll)(p[i].r-p[j].l) * (ll)(p[j].r-p[i].l); } int RM[N]; struct SMT { # define ls (x<<1) # define rs (x<<1|1) int w[N << 2]; inline void set() { memset(w, 0, sizeof w); } inline void edt(int x, int l, int r, int pos, int d) { if(l == r) { w[x] = max(w[x], d); return ; } int mid = l+r>>1; if(pos <= mid) edt(ls, l, mid, pos, d); else edt(rs, mid+1, r, pos, d); w[x] = max(w[ls], w[rs]); } inline int gs(int x, int l, int r, int R) { if(w[x] < R) return 0; if(l == r) return l; int mid = l+r>>1; if(w[rs] >= R) return gs(rs, mid+1, r, R); else return gs(ls, l, mid, R); } }T; inline void solve(int l, int r, int al, int ar) { if(l > r) return ; int pos = 0, mid = l+r>>1; ll mx = -1e15, t; for (int i=al; i<=ar && i<mid; ++i) if((t = gsum(mid, i)) > mx) mx = t, pos = i; if(pos) ans = max(ans, gsum(mid, pos)); solve(l, mid-1, al, pos); solve(mid+1, r, pos, ar); } int main() { ll tmp; T.set(); n = getint(); for (int i=1; i<=n; ++i) p[i].l = getint(), p[i].r = getint(); sort(p+1, p+n+1); t[tn = 1] = p[1]; T.edt(1, 0, F, p[1].r - p[1].l, p[1].r); int mxr = p[1].r; for (int i=2; i<=n; ++i) { if(p[i].r <= mxr) { tmp = T.gs(1, 0, F, p[i].r); tmp = tmp * (p[i].r - p[i].l); if(tmp > ans) ans = tmp; continue; } t[++tn] = p[i]; T.edt(1, 0, F, p[i].r-p[i].l, p[i].r); mxr = p[i].r; } n = tn; for (int i=1; i<=n; ++i) p[i] = t[i]; solve(1, n, 1, n); cout << ans << endl; return 0; } /* 5 0 100 10 105 20 112 25 115 30 140 */
下面那份是考场写的,过了但是有问题,可以被卡掉(感谢chrt给我了一个提醒,发现自己代码是错的qwq)
# include <queue> # include <stdio.h> # include <string.h> # include <iostream> # include <algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; const int M = 2e5 + 10, N = 1e6 + 10, F = 1e6; const int mod = 1e9 + 7; inline int getint() { int x = 0; char ch = getchar(); while(!isdigit(ch)) ch = getchar(); while(isdigit(ch)) { x = (x<<3) + (x<<1) + ch - '0'; ch = getchar(); } return x; } int n, q[N]; struct pa { int l, r; pa() {} pa(int l, int r) : l(l), r(r) {} friend bool operator < (pa a, pa b) { return a.l < b.l || (a.l == b.l && a.r > b.r); } }p[N], t[N]; int tn = 0; inline bool cmp(pa a, pa b) { return a.l < b.l || (a.l == b.l && a.r < b.r); } /* inline int gs(int x) { int l = 1, r = n, mid; while(1) { if(r-l <= 3) { for (int i=l; i<=r; ++i) if(p[i].r > x) return i; return -1; } mid = l+r>>1; if(p[mid].r > x) r = mid; else l = mid; } return -1; } */ inline ll gsum(int i, int j) { return (ll)(p[i].r-p[j].l) * (ll)(p[j].r-p[i].l); } int RM[N]; struct SMT { # define ls (x<<1) # define rs (x<<1|1) int w[N << 2]; inline void set() { memset(w, 0, sizeof w); } inline void edt(int x, int l, int r, int pos, int d) { if(l == r) { w[x] = max(w[x], d); return ; } int mid = l+r>>1; if(pos <= mid) edt(ls, l, mid, pos, d); else edt(rs, mid+1, r, pos, d); w[x] = max(w[ls], w[rs]); } inline int gs(int x, int l, int r, int R) { if(w[x] < R) return 0; if(l == r) return l; int mid = l+r>>1; if(w[rs] >= R) return gs(rs, mid+1, r, R); else return gs(ls, l, mid, R); } }T; // # include <time.h> int main() { // int tm = clock(); freopen("qyh.in", "r", stdin); freopen("qyh.out", "w", stdout); ll ans = 0, tmp; T.set(); // cin >> n; n = getint(); // for (int i=1; i<=n; ++i) scanf("%d%d", &p[i].l, &p[i].r); for (int i=1; i<=n; ++i) p[i].l = getint(), p[i].r = getint(); sort(p+1, p+n+1); t[tn = 1] = p[1]; T.edt(1, 0, F, p[1].r - p[1].l, p[1].r); int mxr = p[1].r; for (int i=2; i<=n; ++i) { if(p[i].r <= mxr) { tmp = T.gs(1, 0, F, p[i].r); tmp = tmp * (p[i].r - p[i].l); if(tmp > ans) ans = tmp; continue; } t[++tn] = p[i]; T.edt(1, 0, F, p[i].r-p[i].l, p[i].r); mxr = p[i].r; } n = tn; for (int i=1; i<=n; ++i) p[i] = t[i]; // for (int i=1; i<=n; ++i) printf("%d %d\n", p[i].l, p[i].r); int lst = 1, head = 1, tail = 0; for (int i=2; i<=n; ++i) { /* (r_i - l_j) * (r_j - l_i) r_i * r_j + l_i * l_j - l_j * r_j - l_i * r_i max if (r_i - l_j) * (r_j - l_i) > (r_i - l_k) * (r_k - l_i) r_i * r_j + l_i * l_j - l_j * r_j > r_i * r_k + l_i * l_k - l_k * r_k r_i * (r_j - r_k) + l_i * (l_j - l_k) > l_j * r_j - l_k * r_k suppose j>k and j is better than k if i + 1, then r_{i+1} * (r_j - r_k) + l_{i+1} * (l_j - l_k) > l_j * r_j - l_k * r_k suppose j<k and j is better than k if i + 1, then r_{i+1} * (r_j - r_k) + l_{i+1} * (l_j - l_k) > l_j * r_j - l_k * r_k */ /* if(gsum(i, i-1) > gsum(i, lst)) lst = i-1; tmp = gsum(i, lst); // cout << lst << endl; if(tmp > ans) ans = tmp; */ while(head < tail && gsum(i, q[head]) < gsum(i, q[head+1])) ++head; while(head <= tail && gsum(i, i-1) >= gsum(i, q[tail])) --tail; q[++tail] = i-1; if(head <= tail) { // printf("%d %d\n", i, q[head]); tmp = gsum(i, q[head]); if(tmp > ans) ans = tmp; } } cout << ans; // cerr << clock() - tm << " ms" << endl; return 0; }
网上还有某种双指针做法,已经被cha掉了
4
1 301000
300990 301001
300991 301002
300992 500000
答案:3999992