Codeforces Round #757 (Div. 2) 題解


Codeforces Round #757 (Div. 2) 題解

A:Divan and a Store

題目大意

給你若干個數,然后你要選若干個數,每個數都在 l~r 之間,你要在盡可能選多的數的同時保證所有你選的數的和不超過一個值。

思路

就直接貪心,把可以選的數拿出來排個序,從小到大能選就選即可。

代碼

#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

int T, now, ans, x;
int n, l, r, k, a[101];

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d %d", &n, &l, &r, &k);
		a[0] = 0; now = 0; ans = 0;
		for (int i = 1; i <= n; i++) {
			scanf("%d", &x);
			if (l <= x && x <= r) a[++a[0]] = x;
		}
		sort(a + 1, a + a[0] + 1);
		for (int i = 1; i <= a[0]; i++) {
			now += a[i]; ans++;
			if (now > k) {
				now -= a[i]; ans--;
				break;
			}
		}
		printf("%d\n", ans);
	}
	
	return 0;
}

B:Divan and a New Project

題目大意

給你一個直線,然后起點在 0 位置,然后有 n 個地方可以讓你選放在這個直線的 n 個地方(1~n 位置),然后每個地方要有去的次數。
每次去這個地方的費用是 \(2|x0-xi|\)(就是兩個地方的距離差乘二),然后要你最小化費用並給出放置方案。

思路

顯然又是一個貪心,就盡可能的把要去次數多的放的靠近 0 位置即可。
(也是一個排序的事情)

代碼

#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	int x, id;
}a[200001];
int T;
int n, ans[200001];
ll answ, bas;

bool cmp(node x, node y) {
	return x.x < y.x;
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		for (int i = 1; i <= n; i++) scanf("%d", &a[i].x), a[i].id = i;
		sort(a + 1, a + n + 1, cmp);
		answ = 0; bas = 1;
		bool op = 0;
		ans[0] = 0;
		for (int i = n; i >= 1; i--) {
			ans[a[i].id] = bas;
			if (op) ans[a[i].id] = -bas;
			answ += 2ll * a[i].x * bas;
			if (op == 0) op = 1;
				else {
					bas++; op = 0;
				}
		}
		printf("%lld\n", answ);
		for (int i = 0; i <= n; i++)
			printf("%d ", ans[i]);
		printf("\n");
	}
	
	return 0;
}

C:Divan and bitwise operations

題目大意

要你構造一個長為為 n 的數組,然后有一些限制條件:某一段區間的數與起來的結果是某個數。
然后要你輸出你構造出的數組的權值,定義這個權值是它所有子序列的異或和的和。
然后多種構造方法輸出任意一個的答案即可。

思路

考慮把構造和計算權值分開。

給出一種簡單的構造方法:
首先二進制拆分,然后一開始默認全部都是 \(1\),然后如果有一個限制條件要求某一段區間是 \(0\),那就把他們都標記為 \(0\)。(這個可以用差分標記)

然后接着計算權值當然也是對於二進制每一位算貢獻。
那我們就可以枚舉子序列有多少個這一位是 1 的,那就只有奇數個數的會被統計。

那我們考慮如何統計。(\(s0\) 是這一位是 \(0\) 的個數,\(s1\) 是這一位是 \(1\) 的個數)
首先是如果是這一位是 \(0\) 的就隨便,直接 \(2^{s0}\),接下來就看是 \(1\) 的。
(好像直接組合數 \(\binom{s1}{i}\) 也可以,但我當時以為會超時)
然后我比賽的時候就想了個遞推的,就直接設 \(f_{i,0/1}\) 為當前到第 \(i\) 個,然后當前的異或值是 \(0/1\) 的方案數。

代碼

#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define mo 1000000007

using namespace std;

struct node {
	int l, r, x;
}a[200001];
int TT, n, m, num[31];
ll jc[200001], inv[200001];
ll F[200001][2];

int b[31][200002];

ll ksm(ll x, ll y) {
	ll re = 1;
	while (y) {
		if (y & 1) re = re * x % mo;
		x = x * x % mo;
		y >>= 1;
	}
	return re;
}

ll C(ll n, ll m) {
	return jc[n] * inv[m] % mo * inv[n - m] % mo;
}

int main() {
	jc[0] = 1;
	for (int i = 1; i <= 200000; i++)
		jc[i] = jc[i - 1] * i % mo;
	inv[200000] = ksm(jc[200000], mo - 2);
	for (int i = 200000 - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1) % mo; 
	
	scanf("%d", &TT);
	while (TT--) {
		scanf("%d %d", &n, &m);
		for (int i = 1; i <= m; i++) {
			scanf("%d %d %d", &a[i].l, &a[i].r, &a[i].x);
			for (int j = 0; j <= 30; j++) {
				if (!((a[i].x >> j) & 1)) {//差分
					b[j][a[i].l]++; b[j][a[i].r + 1]--;
				}
			}
		}
		
		F[0][0] = 1;
		for (int i = 1; i <= n; i++) {
			F[i][1] = (F[i - 1][1] + F[i - 1][0]) % mo;
			F[i][0] = (F[i - 1][0] + F[i - 1][1]) % mo;
		}
		
		ll ans = 0;//計數
		for (int i = 0; i < 30; i++) {
			int num1 = 0;
			for (int j = 1; j <= n; j++) {
				b[i][j] += b[i][j - 1];
				if (!b[i][j]) num1++;
			}
			int num0 = n - num1;
			ans = (ans + 1ll * (1 << i) * ksm(2, num0) % mo * F[num1][1] % mo) % mo;
		}
		printf("%lld\n", ans);
		
		for (int i = 0; i <= 30; i++)
			for (int j = 1; j <= n + 1; j++)
				b[i][j] = 0;
	}
	
	return 0;
}

D1:Divan and Kostomuksha (easy version)

題目大意

給你一個數組,然后你要重新排列它們,使得:
\(\sum\limits_{i=1}^n\gcd(a_1,a_2,...a_i)\) 這個值最大,輸出這個值。

思路

首先看到你從 \(i=1\sim n\),它每次貢獻的值是不會變大,只會不變或者變小,而且每次變小都會變成它的因子。

那我們考慮反過來這個過程,從 \(1\) 不斷變大。(那你就算最后不是到 \(1\) 你也可以相當於沒有值參與到變 \(1\) 的過程,貢獻可以直接抵消掉)

不難想到我們可以用一個記憶化搜索:
\(dfs(x)\) 為當前 \(\gcd\)\(x\),之后能有的貢獻。

首先你算當前能有的所有數都用 \(x\) 來貢獻。
(那我們也就是要處理出每個數有多少個數是它的倍數,這個不難處理,每個數用質數遍歷因子加即可)
那你就枚舉下一次變成的倍數。

那如果當前有的數是 \(nx\),當前枚舉到的倍數是 \(y\),這個 \(y\)\(ny\) 個數是它的倍數。
那我們首先要減去這 \(ny\)\(x\) 個貢獻,再加上 \(dfs(y)\) 的結果,我們要最大化這個部分。

然后就可以了。

代碼

#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

int n, a[100001];
int pl[5000001];
int np[5000001], prime[5000001];
int cnt[10001], cntpl[10001];
ll num[5000001], rem[5000001];

void yz(int now, int va, int xx) {
	if (now > cntpl[0]) {
		num[va] += xx; return ;
	}
	yz(now + 1, va, xx);
	for (int i = 1; i <= cnt[now]; i++) {
		va *= cntpl[now];
		yz(now + 1, va, xx);
	}
}

ll dfs(int now) {
	if (rem[now]) return rem[now];
	ll re = 0;
	for (int i = now + now; i <= 5000000; i += now) {
		if (!num[i]) continue;
		re = max(re, dfs(i) - num[i] * now);
	}
	return rem[now] = (re + num[now] * now);
}

int main() {
	for (int i = 2; i <= 5000000; i++) {
		if (!np[i]) {
			prime[++prime[0]] = i;
			pl[i] = prime[0];
		}
		for (int j = 1; j <= prime[0] && i * prime[j] <= 5000000; j++) {
			np[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	for (int l = 1; l <= n; l++) {
		int r = l;
		while (r < n && a[r + 1] == a[l]) r++;
		
		int noww = a[l];
		cntpl[0] = 0;
		while (noww) {
			if (noww == 1) break;
			cnt[++cntpl[0]] = 0;
			cntpl[cntpl[0]] = (np[noww] ? np[noww] : noww);
			while (noww % cntpl[cntpl[0]] == 0) {
				noww /= cntpl[cntpl[0]];
				cnt[cntpl[0]]++;
			}
		}
		yz(1, 1, r - l + 1);
		
		l = r; 
	}
	
	printf("%lld\n", dfs(1));
	
	return 0;
}

D2:Divan and Kostomuksha (hard version)

題目大意

同 D1,但是每個數的大小更大,不能用 \(O(V\log V)\) 的復雜度解決。(\(V\) 是數的最大值)

思路

考慮會超時的部分,發現是你 \(dfs\) 里面枚舉倍數不行,別的地方都可以。
考慮改進。

會發現你乘一個 \(z\) 的倍數,如果 \(z=x*y\),你完全可以先走到 \(x\) 倍,再走到 \(x*y\) 倍。

那你就只需要枚舉它乘上每個質數,就可以了。
復雜度就降到了 \(O(V\log\log V)\)

代碼

#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

int n, a[100001];
int pl[20000001];
int np[20000001], prime[20000001];
int cnt[100001], cntpl[100001];
ll num[20000001], rem[20000001];

void yz(int now, int va, int xx) {
	if (now > cntpl[0]) {
		num[va] += xx; return ;
	}
	yz(now + 1, va, xx);
	for (int i = 1; i <= cnt[now]; i++) {
		va *= cntpl[now];
		yz(now + 1, va, xx);
	}
}

ll dfs(int now) {
	if (rem[now]) return rem[now];
	ll re = 0;
	for (int i = 1; i <= prime[0] && 1ll * now * prime[i] <= 20000000; i++) {
		if (!num[now * prime[i]]) continue;
		re = max(re, dfs(now * prime[i]) - num[now * prime[i]] * now);
	}
	return rem[now] = (re + num[now] * now);
}

int main() {
	for (int i = 2; i <= 20000000; i++) {
		if (!np[i]) {
			prime[++prime[0]] = i;
			pl[i] = prime[0];
		}
		for (int j = 1; j <= prime[0] && i * prime[j] <= 20000000; j++) {
			np[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	for (int l = 1; l <= n; l++) {
		int r = l;
		while (r < n && a[r + 1] == a[l]) r++;
		
		int noww = a[l];
		cntpl[0] = 0;
		while (noww) {
			if (noww == 1) break;
			cnt[++cntpl[0]] = 0;
			cntpl[cntpl[0]] = (np[noww] ? np[noww] : noww);
			while (noww % cntpl[cntpl[0]] == 0) {
				noww /= cntpl[cntpl[0]];
				cnt[cntpl[0]]++;
			}
		}
		yz(1, 1, r - l + 1);
		
		l = r; 
	}
	
	printf("%lld\n", dfs(1));
	
	return 0;
}

E:Divan and a Cottage

題目大意

給你一個序列,然后對於這個序列的每個前綴,有一些詢問,詢問給你一個初始數字,然后被這個前綴操作后的結果是什么。
操作是你依次看這個數組的每個位置,如果當前的數字小於它這個位置的值,就把當前的數字加一,如果是大於就減一,如果等於就不變。
強制在線。

思路

首先可以發現一個性質:
對於初始數字 \(x<y\),它的結果一定會滿足 \(t_x\leqslant t_y\)

那你考慮每次操作數組多了一點會怎么樣,假設新的是 \(x\)
那左邊一段前綴(考慮把每個初始數組的答案弄成一個數組)的答案就加一,后面一段后綴的答案就減一。

然后由於這個數組長度 \(1e9\),我們考慮用值域線段樹來搞(就動態開點)。
然后你維護一下數組最大值就可以搞了。

代碼

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

int n, a[200001], q;
int x, lstans, rt;

struct XD_tree {
	int val_max[400001 << 7];
	int ls[400001 << 7], rs[400001 << 7];
	int lzy[400001 << 7], tot;
	
	void up(int now) {
		val_max[now] = max(val_max[rs[now]], val_max[ls[now]]);
	}
	
	void down(int now, int l, int r) {
		int mid = (l + r) >> 1;
		if (!ls[now]) {
			ls[now] = ++tot;
			val_max[ls[now]] = mid;
		}
		if (!rs[now]) {
			rs[now] = ++tot;
			val_max[rs[now]] = r;
		}
		if (!lzy[now]) return ;
		val_max[ls[now]] += lzy[now]; val_max[rs[now]] += lzy[now];
		lzy[ls[now]] += lzy[now]; lzy[rs[now]] += lzy[now];
		lzy[now] = 0;
	}
	
	int query(int now, int l, int r, int pl) {
		if (l == r) return val_max[now];
		down(now, l, r);
		int mid = (l + r) >> 1;
		if (pl <= mid) return query(ls[now], l, mid, pl);
			else return query(rs[now], mid + 1, r, pl);
	}
	
	void insert(int &now, int l, int r, int L, int R, int va) {
		if (L > R) return ;
		if (!now) {
			now = ++tot;
			val_max[now] = r;
		}
		if (L <= l && r <= R) {
			lzy[now] += va;
			val_max[now] += va;
			return ;
		}
		down(now, l, r);
		int mid = (l + r) >> 1;
		if (L <= mid) insert(ls[now], l, mid, L, R, va);
		if (mid < R) insert(rs[now], mid + 1, r, L, R, va);
		up(now);
	}
	
	int query_l(int now, int l, int r, int pl) {
		if (l == r) return l;
		down(now, l, r);
		int mid = (l + r) >> 1;
		if (val_max[ls[now]] >= pl) return query_l(ls[now], l, mid, pl);
			else return query_l(rs[now], mid + 1, r, pl);
	}
}T;

int main() {
	T.insert(rt, 0, 1e9, 0, 1e9, 0);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		
		int maxn = T.query(rt, 0, 1e9, 1e9);
		int pl = T.query_l(rt, 0, 1e9, a[i] + 1);
//		printf("%d \n", maxn);
		if (maxn > a[i]) T.insert(rt, 0, 1e9, pl, 1e9, -1), maxn--;
		if (T.query(rt, 0, 1e9, 0) < a[i]) {
			int pl;
			if (a[i] > maxn) pl = 1e9;
				else pl = T.query_l(rt, 0, 1e9, a[i]) - 1;
//			printf("%d\n", pl);
			T.insert(rt, 0, 1e9, 0, pl, 1);
		}
		
		scanf("%d", &q);
		while (q--) {
			scanf("%d", &x);
			x = (x + lstans) % 1000000001;
			lstans = T.query(rt, 0, 1e9, x);
			printf("%d\n", lstans);
		}
	}
	
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM