• 【题解】 CF1534G A New Beginning dp+函数键值化


    不难发现:总存在一种最优方案使得每一个土豆都在 (|X-x|=|Y-y|) 的时候发出。

    那么对于每一个土豆都有一个 ( imes) 形状的触发区间,但实际上我们只要保留形如 的,形如 / 总会劣于前者。

    所以我们只需要在经过每个土豆的 (y=-x+b) 这条直线上进行贡献计算就行了。

    现在我们对于土豆按 (x+y) 进行分层。可以对于按层进行一个 (dp),我们设 (dp_{i,j}) 表示走到 ((j,i-j)) (即第 (i)(x=j) 的位置)的最小代价。

    其实可以把第 (i) 层的 (dp) 数组看成一个函数,它经过 ((j,dp_{i,j})),我们做 (dp) 转移实际上就是函数值的变动。

    我们考虑一下 (dp_{i}) 这个函数到 (dp_{k}) 时产生的变化,其中 (i,k) 是两个相邻的有土豆的层。

    这个变化分两步:

    1. 首先第 (k) 层是由第 (i) 层的某些位置转移过来的。

    [dp_{k,j}= min_{t=0}^{k-i} dp_{i,j-t} ]

    注意这里转移要求 (j-t) 是一个合法的位置。

    1. 然后我们再给 (dp_{k}) 的每个位置加上这一层发射土豆的代价,不难发现这个代价函数 (f_k(x)) 是关于 (x) 的一个凸函数。

    所以说我们的 (dp) 数组在任意时刻也是一个凸函数,所以第一步中我们的 (dp) 转移就非常好完成。

    所以算法流程是:

    1. 我们找到函数最低点 (x_0)(如果存在多个最低点,即一条斜率为 (0) 的线段,则选择横坐标尽可能大的),将当前凸函数从这个最低点切开,右半部分向右平移 (k-i) 格。中间缺失的部分补成函数最低点的值(即 (dp_{k,x_0}))。
    2. 将函数加上发射土豆的代价函数 (f_k(x))

    具体实现可以直接维护 (dp) 数组的导数(也就是斜率)的差分,记录在哪些 (x) 斜率有增加,增加了多少。记录的这些地方就是凸函数的所有“凸起”的位置。

    至于找最低点可以二分。

    至于加上代价函数,可以直接暴力修改,因为代价函数也是凸的,且它的折线段不会超过 (O(n)) 段。

    发现可以用个 splay 维护这些信息。

    细节:每个土豆最多新增 (1) 个折线段,每次函数平移最多增加 (1) 个折线段,故最多有 (2n) 个折线段,记得开两倍数组。

    UPD1:现在我发现,最小值的位置移动次数是 (O(n)) 的,可以直接拿对顶堆来维护,就不需要写 splay 了……

    可怜的代码
    #include <bits/stdc++.h>
    
    #define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
    #define __FILE(x)
    	freopen(#x".in" ,"r" ,stdin);
    	freopen(#x".out" ,"w" ,stdout)
    #define LL long long
    
    const int MX = (8e5 + 23) * 2;
    const LL MOD = 998244353;
    
    int read(){
    	char k = getchar(); int x = 0;
    	while(k < '0' || k > '9') k = getchar();
    	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
    	return x;
    }
    
    struct POINT{
    	int x ,y;
    	bool operator <(const POINT& B)const{
    		return x + y == B.x + B.y ? x < B.x : x + y < B.x + B.y;
    	}
    }A[MX];
    
    namespace SPLAY{
    #define lch(x) ch[x][0]
    #define rch(x) ch[x][1]
    	int ch[MX][2] ,fa[MX] ,root ,tot;
    	int add[MX] ,key[MX] ,size[MX] ,cnt[MX];
    	int get(int x){return x == rch(fa[x]);}
    	int Nroot(int x){return get(x) || x == rch(fa[x]);}
    	void pushup(int x){size[x] = size[lch(x)] + size[rch(x)] + cnt[x];}
    	void doadd(int x ,int v){
    		add[x] += v ,key[x] += v;
    	}
    	void pushdown(int x){
    		if(add[x]){
    			if(lch(x)) doadd(lch(x) ,add[x]);
    			if(rch(x)) doadd(rch(x) ,add[x]);
    			add[x] = 0;
    		}
    	}
    	void rotate(int x){
    		int f = fa[x] ,gf = fa[f] ,which = get(x) ,W = ch[x][!which];
    		if(Nroot(f)) ch[gf][get(f)] = x;
    		ch[x][!which] = f ,ch[f][which] = W;
    		if(W) fa[W] = f;
    		fa[f] = x ,fa[x] = gf ,pushup(f) ,pushup(x);
    	}
    	int stk[MX] ,dep;
    	void splay(int x ,int goal = 0){
    		int f = x; stk[++dep] = f;
    		while(fa[f] != goal) stk[++dep] = f = fa[f];
    		while(dep) pushdown(stk[dep--]);
    		while((f = fa[x]) != goal){
    			if(fa[f] != goal) rotate(get(x) == get(f) ? f : x);
    			rotate(x);
    		}if(!goal) root = x;
    	}
    	void insert(int val ,int QWQWQ ){
    		int x = root ,f = 0;
    		while(x && val != key[x]){
    			pushdown(x);
    			f = x ,x = ch[x][val > key[x]];
    		}
    		if(x && val == key[x]){
    			cnt[x] += QWQWQ;
    			size[x] += QWQWQ;
    			return splay(x);
    		}
    		x = ++tot ,fa[x] = f;
    		size[x] = cnt[x] = QWQWQ ,key[x] = val;
    		if(f) ch[f][val > key[f]] = x ,pushup(f);
    		return splay(x);
    	}
    	std::pair<int ,int> lower_bound(int value){
    		// 第一个数字返回结点标号
    		// 第二个数字返回斜率
    		int x = root ,sum = 0;
    		while(x){
    			pushdown(x);
    			if(lch(x) && sum + size[lch(x)] >= value) x = lch(x);
    			else{
    				sum += cnt[x] + (lch(x) ? size[lch(x)] : 0);
    				if(sum >= value) return {x ,sum};
    				x = rch(x);
    			}
    		}
    		assert(0);
    		return {-1 ,-1};
    	}
    	int prev(int x){
    		splay(x);
    		x = lch(x);
    		while(rch(x)){
    			pushdown(x);
    			x = rch(x);
    		}
    		return x;
    	}
    	int next(int x){
    		splay(x);
    		x = rch(x);
    		while(lch(x)){
    			pushdown(x);
    			x = lch(x);
    		}
    		return x;
    	}
    	void output(int x){
    		if(!x) return;
    		pushdown(x);
    		output(lch(x));
    		debug("pos: %d delk = %d
    " ,key[x] ,cnt[x]);
    		output(rch(x));
    	}
    }using namespace SPLAY;
    
    int main(){
    	int n = read();
    	const int MXV = 1e9 + 1;
    	for(int i = 1 ,x ,y ; i <= n ; ++i){
    		x = read() ,y = read();
    		A[i] = (POINT){x ,y};
    		if(x + y == 0){
    			--i;
    			--n;
    		}
    	}
    
    	if(!n) return puts("0") ,0;
    	
    	insert(INT_MIN ,0);
    
    	std::vector<int> addition;
    	std::sort(A + 1 ,A + 1 + n);
    
    	int minpos = 0 ,las = 0;
    	for(int i = 1 ,j  ; i <= n ; i = j){
    		j = i;
    		addition.clear();
    		while(j <= n && A[i].x + A[i].y == A[j].x + A[j].y){
    			addition.push_back(A[j].x);
    			++j;
    		}
    		if(i != 1){
    			std::pair<int ,int> tmp = lower_bound(i - 1);
    			if(tmp.second == i - 1){
    				tmp = lower_bound(i);
    				tmp = {prev(tmp.first) ,i - 1};
    			}
    			minpos = lower_bound(i - 1).first;
    			splay(minpos);
    			
    			int kksk = prev(minpos);
    			splay(kksk) ,splay(minpos ,kksk);
    			int del = A[i].x + A[i].y - las;
    
    			int location = key[minpos];
    			int psum = tmp.second - cnt[minpos];
    			int k = psum - (i - 1);
    
    			doadd(minpos ,del);
    			insert(location ,-k);
    			insert(location + del ,k);
    		}
    		for(auto k : addition){
    			insert(k ,2);
    		}
    		las = A[i].x + A[i].y;
    	}
    	
    	LL sum = 0;
    	for(int i = 1 ; i <= n ; ++i)
    		sum += A[i].x;
    	insert(0 ,0);
    	int cur = next(lower_bound(-1).first);
    	las = 0;
    	int k = -n;
    	while(1){
    		k += cnt[cur];
    		if(k >= 0) break;
    		int nxt = next(cur);
    		sum += 1LL * k * (key[nxt] - key[cur]);
    		cur = nxt;
    		
    	}
    	printf("%lld
    " ,sum);
    	return 0;
    }
    
  • 相关阅读:
    浮动广告
    jQuery给table添加行和删除行
    oracle优化方式和sql标准
    使用JavaScript中的ActiveXObject填充并设置Excel格
    打印相关的js
    利用js导出Excel
    Oracle左外连接和右外连接的写法
    天气预报抓取的方法和源代码(包括从IP获取)
    algorithm
    ungetc
  • 原文地址:https://www.cnblogs.com/imakf/p/14931737.html
Copyright © 2020-2023  润新知