Codeforces Round #813 (Div. 2) 题解A~E2

https://codeforces.com/contest/1712

估计也就我赛中才D都写不出来了

A题

题意:
给你一个数组和一个正整数\(k\),每次可以选择数组的任意两个数交换,问你最少交换多少次能使数组的前\(k\)个数的和最小。

思路:
直接进行排序,然后统计一下前\(k\)个数原数组和排序后数组每个数的数目,相同的数就在排序后的数组减去,剩下的就只能是通过交换换过来的了。

int a[N], b[N];
int cnt[N];
 
void solve() {
	int n, k;
	memset(cnt, 0, sizeof cnt);
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i ++)
		cin >> a[i], b[i] = a[i];
	sort(b + 1, b + n + 1);
	for(int i = 1 ; i <= k ; i ++)
		cnt[b[i]] ++;
	for(int i = 1 ; i <= k ; i ++)
		if(cnt[a[i]]) cnt[a[i]] --;
	int res = 0;
	for(int i = 1 ; i <= n ; i ++)
		res += cnt[i];
	cout << res << "\n";
}

B题

题意:
给你一个数\(n\), 让你构造一个\(1~n\)的排列\(p\)使得\(lcm(p_1, 1) + lcm(p_2, 2) + ... lcm(p_n, n)\)最小。

思路:
要使得答案尽可能大,那么最好是\(lcm(x, y) = x * y\), 即\(x, y\)互质。
那显然,接下来我们就要想办法让大的数和大的数相乘。
\(i, i + 1\)是绝对互质的,我们这样构造就能满足上述条件。

int a[N];
 
void solve() {
	int n;
	cin >> n;
	int res = 0;
	for(int i = n ; i >= 2 ; i -= 2) {
		a[i] = i - 1;
		a[i - 1] = i;
	}
	if(n & 1) a[1] = 1;
	for(int i = 1 ; i <= n ; i ++)
		cout << a[i] << " ";
	cout << "\n";
}

C题

题意:
给你一个\(n\)个正整数的数组,每次操作你可以将数组内的任意一个数置为零,问你最少操作多少次能使得整个数组非递减.

思路:
首先会导致数组出现递减的,肯定是大的数,如果最大的数不在最后,是肯定不行的。
然后再考虑相同数的情况,如果多个数同为最大,有的数在中间,也是不行的,详见代码。
我写的可能比较乱
可以看看如下其他人的分析

假如一个数字被变成了0,那么他前面的所有数字都必须变成0。因此操作后的结果要么是全0,要么是后缀有一些数没有变成0。0和非0的界限呢 ?
假如有两个相同的数字不相邻,那么他们中间的数字不管更大还是更小都要变成0。既然中间变成0了,那么这个区间内包括前面的所有数字都要变成0。对于某一数字x,他出现的位置比如说是2 5 8,那么8前面的所有数都要变为0。
因此我们需要维护每一个数字出现至少两次并且不是连续的最后一次出现的位置。
然后找到最大值,就是我们第一步找到的0和非0的分界线。其次剩下的数一定在数组的后缀上并且都仅仅出现一次。我们从后往前,找到第一个a[i] < a[i - 1]的位置,那么再次更新界限为i - 1即可。
struct node {
	int x, y;
	friend bool operator < (node a, node b){
		if(a.x != b.x)
			return a.x < b.x;
		return a.y < b.y;
	}
};
 
void solve() {
	int n;
	cin >> n;
	priority_queue<node> q;
	for(int i = 1 ; i <= n ; i ++) {
		int x;
		cin >> x;
		q.push({x, i});
	}
	int pos = n;
	int last = -1;
	int res = 0, cnt = 0, pre = -1;
	while(q.size()) {
		auto t = q.top();
		q.pop();
		if(t.x == last) continue;
		if(t.y == pos) {
			pos --;
			if(t.x == pre) cnt ++;
			else cnt = 1;
		}
		else {
			if(t.x == pre) pos += cnt, cnt = 1;
			last = t.x;
			res ++;
		}
		pre = t.x;
	}
	cout << res << "\n";
}

D题

这个题出的超级妙。
题意:
给定一长度为\(n\)的数组,你可以进行\(k\)次操作任意改变数组的一个值,用该数组构建一张无向稠密图,对于任意\([l, r]\)两点之间的路径长度为数组\(a\)\([l, r]\)范围内的最小值。求操作后该图上两个点之间最短路的最大值。

思路:
首先,设数组最小值为\(minv\),位置为\(pos\),任意两点的一条可行的路径,都是\(minv * 2\),因为任意一点到\(pos\)的距离都是\(minv\),我们显然可以把它当做中介去到达其它任何点。
在这个原则下,我们显然应该使最小值尽可能大,因此我们先把\(k - 1\)个最小值都变\(10^9\)
因为接下来有两种情况:
在只变\(k - 1\)个值的情况下,我们可以留着最后一次改变,来变和最大值(假设是\(a[n]\))相邻的点。
这样子,最大值到隔壁改变这个点的距离就是\(min(10^9, a[n]) = a[n]\).
那么此时我们的最短路就是\(min(2 * minv, a[n])\)
在变\(k\)个值的情况下,显然最小值区间越小,值越大,那么我们只考虑两两相邻的点,它们的边里面找一条最小值\(x\)
然后此时答案就是\(min(2 * minv, x)\)
然后两种情况答案取\(max\)就行。
我在赛中就漏了先只变\(k - 1\)个最小值的情况。

int a[N];
int n, k;
 
bool cmp(pii a, pii b) {
	return a.x < b.x;
}
 
 
void solve() {
	cin >> n >> k;
	
	vector<pii> v;
	for(int i = 1 ; i <= n ; i ++) {
		cin >> a[i];
		v.push_back({a[i], i});
	}
	sort(v.begin(), v.end(), cmp);
 
	// k-1个数 
	for(int i = 1 ; i < k ; i ++) {
		a[v[i - 1].y] = 1e9; 
		v[i - 1].x = 1e9;
	}
	// 当前最小值的两倍和最大值取min 
	sort(v.begin(), v.end(), cmp);
	int res1 = min(v[0].x * 2, v[n - 1].x);
	
	// 第k个数 
	a[v[0].y] = 1e9; 
	v[0].x = 1e9;
	
	int maxd = 0;
	for(int i = 1 ; i < n ; i ++) maxd = max(maxd, min(a[i], a[i + 1]));
	// 当前最小值的两倍和相邻边最大值取min
	int res2 = min(v[1].x * 2, maxd);
	
	cout << max(res1, res2) << "\n";
}

E2题

题意:
给定\(q\)次询问(\(q <= 10^5\))。
每次询问给出\(L, R\) \((L, R <= 10^5)\),问有多少三元组\((i, j, k)\)满足\(L <= i < j < k <= R, lcm(i, j, k) >= i + j + k\)

思路:
直接求\(lcm(i, j, k) >= i + j + k\), 不好求,那么我们用总方案减去\(lcm(i, j, k) < i + j + k\)的方案数。
因为 \(i < j < k\), 所以 \(i + j + k < 3k\),并且\(lcm(i, j, k)\)的结果只能是\(k\)的倍数 ,那么我们只需要求\(lcm(i, j, k) <= 2k\)的方案数。
其中 \(lcm(i, j, k) = k < i + j + k\) 必然成立
\(lcm(i, j, k) = 2*k\)\(i + j + k\)的关系,就需要讨论一下了。
有个玄学,\(2*k\)的情况下,满足条件的只有\(i, j, k\)\(3, 4, 6\)\(6, 10, 15\)
这边具体怎么解决,直接看代码吧,都有注释。

正常解法

int L[MAXN], R[MAXN];
vector<int>fac[MAXN];
vector<int>g[MAXN];
ll ans[MAXN];
ll tr[MAXN];

int lowbit(int x) {
	return x & -x;
}

void add(int x, int c) {
	for(int i = x ; i < MAXN ; i += lowbit(i))
		tr[i] += c;
}

ll query(int x) {
	ll res = 0;
	for(int i = x ; i ; i -= lowbit(i))
		res += tr[i];
	return res;
}

ll sum(int l, int r) {
	return query(r) - query(l - 1);
}



void solve() {
	for (int i = 1; i <= 4e5; i++) {
		for (int j = i; j <= 4e5; j += i) {
			fac[j].push_back(i);
		}
	}
	int q;
	cin >> q;
	for (int i = 1; i <= q; i++) {
		cin >> L[i] >> R[i];
		g[R[i]].push_back(i);
		ll len = R[i] - L[i] + 1;
		ans[i] = (len)*(len - 1)*(len - 2) / 6;
	}
	for (int k = 1; k <= 2e5; k++) {
		for(auto j : fac[2 * k]) {	// 枚举 2k 的约数
			// j 不是 k 的约数  那么lcm(i, j, k) = 2k  此时如果  j * 2 <= k 那么  lcm(i, j, k) > i + j + k
			if (k % j && j * 2 <= k) continue;
			if (j >= k) break;
			for(auto i : fac[2 * k]) {
				if (i >= j) break;
				// i 或者 j 不是 k 的约数
				if (k % j || k % i) {
					if(2 * k < j + i + k)
						add(i, 1);
				}
				else add(i, 1);	// 都是 k 的约数   lcm(i, j, k) = k <  i + j + k 必然成立
			}
		}

		for (int i : g[k]) {	//	更新终点为当前k的答案 	k 是最大值  i属于区间[L, R], 即区间[L, R]有多少方案数
			ans[i] -= sum(L[i], R[i]);
		}
	}
	for (int i = 1; i <= q; i++) cout << ans[i] << "\n";
}

玄学解法

int check(vector<int>&v, int L, int R) {	//查询落在区间[L,R]中有多少个数
    if (L > R)return 0;
    return upper_bound(v.begin(), v.end(), R) - lower_bound(v.begin(), v.end(), L);
}
 
void solve() {
    for (int i = 1; i <= 2e5; i++) {
        for (int j = i; j <= 2e5; j += i) {
            fac[j].push_back(i);
        }
    }  
    int q; cin >> q;
    for (int i = 1; i <= q; i++) {
        cin >> L[i] >> R[i];
        g[R[i]].push_back(i);
        int len = R[i] - L[i] + 1;
        ans[i] = (len)*(len - 1)*(len - 2) / 6;			// 直接去掉了 k = 2的情况  只要如下情况 
        ans[i] -= max(0ll, R[i] / 6 - (L[i] - 1) / 3);	// 这里是去掉了3 4 6 和 6 10 15 的情况
        ans[i] -= max(0ll, R[i] / 15 - (L[i] - 1) / 6);	// [1,R]有多少个6的倍数,减去[1,L-1]里面3的倍数
    }
    for (int k = 1; k <= 2e5; k++) {
        for (int i : fac[k]) {	// 枚举 k 的因子 
            int cnt = check(fac[k], i + 1, k - 1); // fac[k] 在范围 [i + 1, k - 1] 有多少数 = 满足条件的 i的个数 
            // 因子 i 在当前的 k 下有 cnt 个答案, i 是最小值 
            add(i, cnt);
        }
        for (int i : g[k]) {	//	更新终点为当前k的答案 	k 是最大值  i属于区间[L, R], 即区间[L, R]有多少方案数 
            ans[i] -= sum(L[i], R[i]);
        }
    }
    for (int i = 1; i <= q; i++)cout << ans[i] << "\n";
}
posted @ 2022-08-15 17:08  beatlesss  阅读(37)  评论(0编辑  收藏  举报