Educational Codeforces Round 148 (Rated for Div. 2) D1. Red-Blue Operations

Easy Version传送门
Hard Version传送门

题目大意:

Easy Version解题思路:

   1.  不难发现,若k小于等于n,我们将a排序,a数组下标[1, k]区间上的每个数字依次加上 k, k - 1, ..., 1,取最小值就是答案。(下述操作都是基于排序a)

   2.  若k大于n,观察发现如果我们想让一个位置上的数字变得更大,那么操作次数必定为奇数次,只要n不为1,我们一定有方法能让操作的位置被操作奇数次。

   3.  我们要使答案尽可能的大,那么就是奇数步数出现的次数尽可能的多,那么肯定尽量让每个位置上的数字被操作的次数都是奇数次最佳,最好是让n个数字备操作的次数都为奇数次。那么最少会有(k - n) / 2次偶数操作,对于这个计算,不难发现,若n和k的奇偶性不同,那么 (k - n) % 2 == 1也就是会剩下一个操作,那么这个操作会影响到一个奇数,也就是会导致n个数字里面有一个数字会被操作偶数次。所以要分为两种情况讨论一下。

   4.  若k和n奇偶性相同,在不考虑最后n个操作以前的所有操作,也就是[1, k - n - 1]这些操作的情况下,这n步最优的方案是把[k, k - n]这些数字依次加到a数组下标[1, n]。若n和k奇偶性不同,那么会少一个奇数操作,在不考虑最后n - 1个操作以前的所有操作的情况下,这n - 1步最优的方案是把[k, k - n - 1]这些数字依次加到a数组下标[1, n - 1]。从现在开始我们就不需要分奇偶讨论了,因为我们确切的只剩下t = (k - n) / 2次偶数操作, 那么如何最小化这t * 2步操作呢。偶数步数必定是减小,我们会发现若是把相邻的两个安排在一起那么起到的影响就是-1,接下来怎么让这t个-1来进行最优操作呢,是一个很简单的小问题就不说了。

#include <bits/stdc++.h>

const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f * 2;
using ll = long long;

typedef std::pair<int, int> PII;
int n, m;

int a[N];
inline void solve() {
	std::cin >> n >> m;
	for (int i = 1; i <= n; i ++) std::cin >> a[i];
	std::vector<int> b(n + 1);
	std::sort(a + 1, a + n + 1);
	
	while (m --) {
		int x;
		std::cin >> x;
		int xx = x;
		if (n == 1) {
			if (x & 1) std::cout << a[1] + x - x / 2 << ' ';
			else std::cout << a[1] - x / 2 << ' ';
			continue;
		}
		

		if (x <= n) {
			int mn = INF;
			for (int i = 1, j = x; i <= n; i ++, j --) {
				if (j > 0)	mn = std::min(mn, a[i] + j);
				else mn = std::min(mn, a[i]);
			} 
				
			std::cout << mn << ' ';
			continue;
		}
		
		if ((n & 1) == (x & 1)) {//有n个可以加上去的
	 		for (int i = 1, j = x; i <= n; i ++, j --) 
	 			b[i] = a[i] + j;
	 		x -= n;
	 		x /= 2;
	 		
	 		int mn = INF;
	 		for (int i = 1; i <= n; i ++) mn = std::min(mn, b[i]);
	 		
	 		ll sum = 0;
	 		for (int i = 1; i <= n; i ++) 
	 			sum += b[i] - mn;
	 		
	 		if (sum >= x) std::cout << mn << ' ';
	 		else {
	 			x -= sum;
	 			std::cout << mn - (x + n - 1) / n << ' ';
	 		}
		} else {
	 		for (int i = 1, j = x; i < n; i ++, j --) 
	 			b[i] = a[i] + j;
	 		b[n] = a[n];
	 		x -= n - 1;
	 		x /= 2;
	 		
	 		int mn = INF;
	 		for (int i = 1; i <= n; i ++) mn = std::min(mn, b[i]);
			 		
	 		ll sum = 0;
	 		for (int i = 1; i <= n; i ++) 
	 			sum += b[i] - mn;
	 		
	 		if (sum >= x) std::cout << mn << ' ';
	 		else {
	 			x -= sum;
	 			std::cout << mn - (x + n - 1) / n << ' ';
	 		}
		}
	}
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int _ = 1;
    //std::cin >> _;
    while (_ --) solve();
    
    return 0;
}

Hard Version解题思路:

  1.  将Esay Version的第一个tips转化为从a数组[1, n] 减去 0, 1, .., n - 1, 对于询问而言给他们每一个加上k也是一样的。会发现对于k <= n 而言,我们只需要知道前k个加上k以后的最小值,和a数组[k + 1, n]的最小值,在这两个值里面取一个min就是答案,因为前k个数字都加上k的最小值与k无关,我们可以预处理处 k <= n 的所有答案然后对于 k <= n 的询问我们可以做到O(1)查询。

  2.  对于k > n并且k与n同为奇数或者同为偶数,我们知道a数组每个值加上k不影响最小值和其他值的差值,所以我们可以预处理出来一个值tot1 = a数组里面的最小值mn1和其他所有数字的差值总和。我们知道我们有t = (k - n) / 2次 -1操作,如果 t <= tot1那么答案就是mn1 + k。否则的话就让 del = t - tot1, mn1 - (del / n向上取整),答案就是mn1 - (del + n - 1) / n + k

  3.  对于k > n并且k与n不满足同为奇数或者同为偶数,那么对于这种情况Easy Version也提到过,就是第n个数字不保持原样,也就是要将a[n]复原回去,我们将a[n] + n - 1即可,询问结束后再减回去。我们可以预处理出来数组a的前n - 1个数字的最小值mn2, 然后预处理出来一个值 tot2 = a数组里面前n - 1个数字的最小值mn2和前n - 1个数字的差值总和。这个时候就需要分为两种情况,如果mn2 + k 大于 a[n]的话那么显然最小值就应该是a[n],需要做一些修改操作,这个略作思考该怎么修改就行,若mn + k < a[n]的话就不需要修改最小值,统计答案的方法与2类似。

#include <bits/stdc++.h>

const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f * 2;
using ll = long long;

typedef std::pair<int, int> PII;
int n, m;

int a[N];
inline void solve() {
	std::cin >> n >> m;
	for (int i = 1; i <= n; i ++) std::cin >> a[i];
	std::vector<int> b(n + 1), c(n + 1);
	std::sort(a + 1, a + n + 1);
	
	b[n] = a[n];
	for (int i = n - 1; i; i --) 
		b[i] = std::min(a[i], b[i + 1]);
	
	int mn1 = INF, mn2 = INF;
	for (int i = 1; i <= n; i ++) {
		a[i] -= i - 1;
		if (i == 1) c[i] = a[i];
		else c[i] = std::min(c[i - 1], a[i]);
		mn1 = std::min(a[i], mn1);
		if (i != n) 
			mn2 = std::min(a[i], mn2);
	}
	
	ll tot1 = 0, tot2 = 0;//tot1 表示奇偶一样, tot2表示奇偶不一样
	
	for (int i = 1; i <= n; i ++)
		tot1 += a[i] - mn1;
 	
	for (int i = 1; i < n; i ++)
		tot2 += a[i] - mn2;
		
	while (m --) {
		int x;
		std::cin >> x;

		if (n == 1) {
			if (x & 1) std::cout << a[1] + x - x / 2 << ' ';
			else std::cout << a[1] - x / 2 << ' ';
			continue;
		}
		

		if (x <= n) {
			if (x != n) std::cout << std::min(c[x] + x, b[x + 1]) << ' ';
			else std::cout << c[x] + x << ' ';
			continue;
		}
		
		ll del = (x - n + 1) / 2;

		if ((n & 1) == (x & 1)) {//有n个可以加上去的
	 		if (tot1 >= del) std::cout << mn1 + x << ' ';
	 		else {
	 			del -= tot1;
	 			std::cout << mn1 - (del + n - 1) / n + x << ' ';
	 		}
		} else {
			int tmp = mn2 + x;
			a[n] += n - 1;
			if (tmp <= a[n]) {
				int c = a[n] - tmp;
				tot2 += c;
				if (tot2 >= del) std::cout << std::min(mn2 + x, a[n]) << ' ';
				else {
		 			del -= tot2;
		 			std::cout << tmp - (del + n - 1) / n << ' ';
		 		}
				
				tot2 -= c;
			} else {
				if (tot2 >= del) std::cout << std::min(mn2 + x, a[n]) << ' ';
				else {
					del -= tot2;
					if (a[n] >= tmp) {
						del -= a[n] - tmp;
						if (del <= 0) std::cout << tmp << ' ';
						else std::cout << tmp + (del + n - 1) / n << ' ';
					} else {
						int tmn2 = mn2;
						ll ttot2 = tot2;
						tot2 = 1ll * (tmp - a[n]) * (n - 1);
						mn2 = a[n];
						del -= tot2;
						if (del <= 0) 	std::cout << mn2 << ' ';
						else std::cout << mn2 - (del + n - 1) / n << ' ';
						
						tot2 = ttot2;
						mn2 = tmn2;
					}
		 		}
			}
			a[n] -= n - 1;
	 	}
	}
}


int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int _ = 1;
    //std::cin >> _;
    while (_ --) solve();
    
    return 0;
}
posted @ 2023-05-23 22:49  春始于雪之下  阅读(23)  评论(0编辑  收藏  举报