• [JOISC2017]门票安排


    [JOISC2017]门票安排

    题面

    loj

    题解

    首先考虑(c[i]=1)的情况,首先默认所有(lleq r),而且一开始所有人选择的均为([l,r]),然后我们考虑把一些区间反转过来。

    然后有一条比较显然的性质,就是对于两个不交的区间,在最优解中一定不会同时把他们两个反转过来,否则只有可能变多,而不会变少。

    那么假设所有可能反转区间(还没反转)的交集为([x,y]),一开始时所有门票被覆盖次数是(a_i),反转后是(b_i),在([x,y])(b)的最大值位置为(t),那么又有三条性质:

    性质1:(b_tgeq max b_i-1)
    证明1:如果有(b_tleq max b_i-2),那么可以取消反转一个(l=x)的区间和(r=y)的区间,答案不会更劣,同时满足了这条性质。

    性质2:(a_tgeq max a_i)
    证明2:如果存在(a_k>a_t)的话,则(k otin [x,y]),那么不是所有的区间都会覆盖(k),而(t)必然被所有反转区间覆盖,又因为有上一条性质,所以(k,t)满足:
    (a_k-a_tgeq 1,b_t-b_kgeq -1 Rightarrow a_k-a_t+b_t-b_kgeq 0Leftrightarrow a_k-b_kgeq a_t-b_t)
    这个式子的意义就是(k)上反转的区间个数大于等于(t),这与上面所述的矛盾。

    性质3:最优解的(t)满足(a_t=max a_i)(t)尽量小或大。
    证明3:所有反转区间都会经过最小或最大点。

    最后考虑我们的算法:
    首先拿出一个使得(a_t)最大的最小或最大的(t),然后再二分答案( ext{mid}),那么反转后该点的值为( ext{mid})( ext{mid}-1),可以分别算出来我总共进行的反转次数( ext{cnt})再分开检验。

    把所有跨过(t)的区间拿出来再从左到右扫描线,因为后面的反转对前面的操作有影响,但是现在我们知道了操作数,所以我们可以的出这个点最少要被反多少下。反转的话维护一个大根堆,每次反转找出右端点最靠有的区间反肯定最右,因为对后面的位置影响小。([t+1,n])可以由前面的操作差分判断,具体细节详见代码。

    代码

    #include <bits/stdc++.h> 
    using namespace std; 
    int gi() { 
    	int res = 0, w = 1; 
    	char ch = getchar(); 
    	while (ch != '-' && !isdigit(ch)) ch = getchar(); 
    	if (ch == '-') w = -1, ch = getchar(); 
    	while (isdigit(ch)) res = res * 10 + ch - '0', ch = getchar(); 
    	return res * w; 
    } 
    typedef long long LL;
    const int MAX_N = 2e5 + 5; 
    int N, M; 
    int A[MAX_N], B[MAX_N]; 
    LL C[MAX_N], c[MAX_N], tag[MAX_N]; 
    #define fi first
    #define se second
    vector<pair<int, LL> > pot[MAX_N];
    priority_queue<pair<int, LL> > Q; 
    bool check(LL mid, int t, LL cnt) { 
    	if (mid < cnt) return 0;
    	for (int i = 1; i <= N; i++) pot[i].clear(); 
    	while (!Q.empty()) Q.pop(); 
    	for (int i = 1; i <= N; i++) tag[i] = 0; 
    	for (int i = 1; i <= M; i++) 
    		if (A[i] <= t && t < B[i]) pot[A[i]].push_back(make_pair(B[i], C[i])); 
    	LL now = 0; 
    	for (int i = 1; i <= t; i++) { 
    		for (auto j : pot[i]) Q.push(j); 
    		while (c[i] - now + cnt > mid) { 
    			if (Q.empty()) return 0; 
    			auto p = Q.top(); Q.pop(); 
    			LL need = min(p.se, (c[i] - now + cnt - mid + 1) >> 1); 
    			now += need, cnt -= need, tag[p.fi] += need << 1; 
    			if (p.se != need) Q.push({p.fi, p.se - need}); 
    		} 
    	} 
    	tag[t + 1] -= now; 
    	for (int i = t + 1; i <= N; i++) { 
    		tag[i] += tag[i - 1];
    		if (tag[i] + c[i] > mid) return 0; 
    	} 
    	return 1; 
    } 
    
    int main () { 
    #ifndef ONLINE_JUDGE 
        freopen("cpp.in", "r", stdin); 
    #endif 
    	N = gi(), M = gi(); 
    	for (int i = 1; i <= M; i++) { 
    		A[i] = gi(), B[i] = gi(), C[i] = gi(); 
    		if (A[i] > B[i]) swap(A[i], B[i]); 
    		c[A[i]] += C[i], c[B[i]] -= C[i]; 
    	} 
    	for (int i = 1; i <= N; i++) c[i] += c[i - 1]; 
    	int mxl = 0, mxr = 0; 
    	for (int i = 1; i <= N; i++) if (c[i] > c[mxl]) mxl = i; 
    	for (int i = N; i >= 1; i--) if (c[i] > c[mxr]) mxr = i; 
    	LL l = 0, r = c[mxl], ans = r; 
    	while (l <= r) { 
    		LL mid = (l + r) >> 1; 
    		if (check(mid, mxl, c[mxl] - mid) || check(mid, mxl, c[mxl] - mid + 1) ||
    			check(mid, mxr, c[mxr] - mid) || check(mid, mxr, c[mxr] - mid + 1)) ans = mid, r = mid - 1; 
    		else l = mid + 1; 
    	} 
    	printf("%lld
    ", ans); 
        return 0; 
    } 
    
  • 相关阅读:
    redis的常用操作
    django中的缓存 单页面缓存,局部缓存,全站缓存 跨域问题的解决
    drf 下的 url控制 解析器 响应器 版本控制 分页
    django下的 restful规范 Drf框架 psotman的安装使用 及一些容易遗忘的小点
    vue学习 自建服务器 node
    vue学习 条件、循环指令、computed、watch、局部、全局、组件间交互
    Linux入门
    redis进阶
    redis介绍和安装
    DRF解析器和渲染器
  • 原文地址:https://www.cnblogs.com/heyujun/p/13722367.html
Copyright © 2020-2023  润新知