[ARC126E]Infinite Operations

137 篇文章 1 订阅
89 篇文章 0 订阅

题目

传送门 to AtCoder

思路

这题看上去有个 lim ⁡ \lim lim,其实就是在说,有无限次操作的机会,问最多能得到多少分。

我最初以为:不就是 ∑ ∣ A i − A ˉ ∣ \sum |A_i-\bar{A}| AiAˉ 吗,多简单!结果并不是这样的,因为两个大于平均数 A ˉ \bar A Aˉ 的数之间进行操作,并不会让 ∑ ∣ A i − A ˉ ∣ \sum|A_i-\bar A| AiAˉ 减小,但是实实在在地得分了。

所以还是考虑方案的性质。不要引入任何数学背景——我试着把问题递归为,大于 A ˉ \bar A Aˉ 的内部进行操作,小于 A ˉ \bar A Aˉ 也内部进行操作,最后两两操作。无果。

发挥人类智慧,想想到底怎样才能让得分最高?显然是多次转手。也就是说,如果 A i A_i Ai 要给 A j A_j Aj 一个值,那么尽量选取一个 A k A_k Ak 来周转一次,就可以加分了!

由于有无穷次操作,可以把 A i A_i Ai A j A_j Aj 的值分成很多个小部分,少量多次嘛。每个小部分应该小到 1 2 min ⁡ A i < A j ( A j − A i ) \frac{1}{2}\min_{A_i<A_j}(A_j-A_i) 21minAi<Aj(AjAi),也就是任意两个不相等的数之间都可以进行操作的量。此时,如果我们要从 A i A_i Ai 给到 A j A_j Aj,那么只需找到 A k ∈ ( A j , A i ) A_k\in(A_j,A_i) Ak(Aj,Ai),就可以作为一次中转了!

所以 最优操作方案中,总是排名相邻的两个数字之间进行操作。同时我们发现操作是不会改变相对大小关系的。所以 相对大小关系始终不变

如果最初将 A i A_i Ai 从小到大排序,那么所有操作都在相邻两个数之间进行。那么 A i A_{i} Ai A i − 1 A_{i-1} Ai1 之间的操作,会赚多少分呢?显然 A i A_i Ai 及以后的数字的和,只能靠这个操作进行减少。所以得分为
∑ j = i n ( A j − A ˉ ) \sum_{j=i}^{n}(A_j-\bar A) j=in(AjAˉ)
这个方案的存在性是必然的!因为最优方案必然存在。

对于所有的 i i i 求和,也就是 n n n 个后缀和相加,也就是 A i A_i Ai 乘排名 i i i 的求和。再把 A ˉ \bar A Aˉ 提出来,可以发现
a n s = ∑ i = 1 n i ⋅ A i − n + 1 2 ∑ i = 1 n A i ans=\sum_{i=1}^{n}i\cdot A_i-\frac{n+1}{2}\sum_{i=1}^{n}A_i ans=i=1niAi2n+1i=1nAi
原本是 A ˉ \bar A Aˉ n ( n + 1 ) 2 n(n+1)\over 2 2n(n+1),把 n n n 乘进去就是总和。这东西用平衡树(或者线段树)很容易维护。时间复杂度 O [ ( n + q ) log ⁡ ( n + q ) ] \mathcal O[(n+q)\log(n+q)] O[(n+q)log(n+q)]

代码

#include <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int Mod = 998244353;
const int inv2 = (Mod+1)>>1;

const int MaxN = 300001;
const int MaxM = (MaxN<<1)*3;
struct Node{
	int ans, siz, sum;
	Node(){ sum = ans = siz = 0; }
	Node(int D){
		sum = ans = D, siz = 1;
	}
	Node operator + (const Node &t) const {
		Node res;
		res.ans = (ans+int_(siz)*t.sum+t.ans)%Mod;
		res.siz = siz+t.siz; // won't explode
		res.sum = (sum+t.sum)%Mod;
		return res;
	}
};
Node node[MaxM];
int size_of_bottom;
void prepare(int n){
	size_of_bottom = 1;
	while(size_of_bottom < n)
		size_of_bottom <<= 1;
}
void modify(int x,const Node &v){
	int j = x+size_of_bottom-1;
	for(node[j]=v,j>>=1; j; j>>=1)
		node[j] = node[j<<1]+node[j<<1|1];
}

int x[MaxN], y[MaxN];
int a[MaxN], tmp[MaxN<<1];

int main(){
	int n = readint(), q = readint();
	rep(i,1,n) a[i] = readint();
	memcpy(tmp+1,a+1,n<<2);
	rep(i,1,q){
		x[i] = readint();
		y[i] = readint();
	}
	memcpy(tmp+n+1,y+1,q<<2);
	sort(tmp+1,tmp+(n+q)+1);
	prepare(n+q); // zkw segment tree
	for(int i=1,rnk; i<=n; ++i){
		rnk = lower_bound(tmp+1,tmp+(n+q)+1,a[i])-tmp;
		-- tmp[rnk]; // avoid repetition
		modify(rnk,Node(a[i]));
		a[i] = rnk; // record rank
	}
	for(int i=1,rnk; i<=q; ++i){
		rnk = lower_bound(tmp+1,tmp+(n+q)+1,y[i])-tmp;
		-- tmp[rnk]; // avoid repetition
		modify(a[x[i]],Node()); // delete
		a[x[i]] = rnk; // record rank
		modify(rnk,Node(y[i])); // insert
		int ans = (node[1].ans-int_(n+1)*node[1].sum%Mod*inv2%Mod+Mod)%Mod;
		writeint(ans), putchar('\n');
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值