2021 BNU Winter Training 4 (The 14th Jilin Provincial Collegiate Programming Contest)

2021 BNU Winter Training 4 (The 14th Jilin Provincial Collegiate Programming Contest)

题目链接

A. Trie

  • AC自动机+线段树+树状数组,比较难。

B. Queue

  • 这个题就是动态求逆序对的问题。但是交换的范围比较小,只有100,因此可以暴力。
  • 有两个地方很容易错。一个是树状数组不可以处理0,如果有0的话会陷入死循环;第二个是,树状数组的范围是数据的最大值,并不是数组的个数。
#include
#include
#include
using namespace std;
const int maxn = 100010;
typedef long long ll;
int N, trn, P, a[maxn], tr[maxn];
int lowbit(int x) {
	return x & -x;
}
void add(int id, int c) {
	for (int i = id; i <= trn; i += lowbit(i)) tr[i] += c;
}
int sum(int id) {
	int res = 0;
	for (int i = id; i; i -= lowbit(i)) {
		res += tr[i];
	}
	return res;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &N);
		trn = 0;
		for (int i = 1; i <= N; i++) {
			scanf("%d", &a[i]);
			a[i]++;
			trn = max(trn, a[i]);
		}

		ll ans = 0;
		for (int i = 1; i <= N; i++) {
			add(a[i], 1);
			ans += i - sum(a[i]);
		}
		scanf("%d", &P);
		
		ll res = ans;

		memset(tr, 0, sizeof tr);

		while(P--){
			int l, r;
			scanf("%d%d", &l, &r);
			for (int i = l, k = 1; i <= r; i++, k++) {
				add(a[i], 1);
				res -= k - sum(a[i]);
			}
			//printf("*** %lld\n", res);
			for (int i = l; i <= r; i++) add(a[i], -1);

			swap(a[l], a[r]);
			for (int i = l, k = 1; i <= r; i++, k++) {
				add(a[i], 1);
				res += k - sum(a[i]);
			} 
			for (int i = l; i <= r; i++) add(a[i], -1);
			ans = min(ans, res);
			//printf("### %lld\n", res);
		}

		printf("%lld\n", ans);
	}
	return 0;
}

C. Curious

  • 题意:计算 ∑ i = 1 n ∑ j = 1 n [ g c d ( a i , a j ) = x ] \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(a_i, a_j) = x] i=1nj=1n[gcd(ai,aj)=x]
  • 这个题的公式很好推的,我还犹豫那么长时间,觉得不会那么简单…
  • 莫比乌斯反演。我们设 f ( x ) = ∑ i = 1 n ∑ j = 1 n [ g c d ( a i , a j ) = x ] f(x) = \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(a_i, a_j) = x] f(x)=i=1nj=1n[gcd(ai,aj)=x],则 F ( x ) = ∑ d ∣ n f ( d ) = ∑ i = 1 n ∑ j = 1 n [ x ∣ g c d ( a i , a j ) ] F(x) = \sum\limits_{d|n}f(d) = \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[x|gcd(a_i, a_j)] F(x)=dnf(d)=i=1nj=1n[xgcd(ai,aj)],设 m = ∑ i = 1 n x ∣ a i m = \sum\limits_{i=1}^{n}x|a_i m=i=1nxai,则 F ( x ) = m 2 F(x) = m^2 F(x)=m2
  • 因此,令 d ′ = d x , m ′ = ∑ i = 1 n d ′ x ∣ a i d' = \frac{d}{x}, m'=\sum\limits_{i=1}^{n}d'x | a_i d=xd,m=i=1ndxai. 则 f ( x ) = ∑ x ∣ d μ ( d x ) m 2 = ∑ d ′ = 1 ∞ μ ( d ′ ) m ′ 2 f(x) = \sum\limits_{x|d}\mu(\frac{d}{x})m^2 = \sum\limits_{d'=1}^{\infty}\mu(d')m'^2 f(x)=xdμ(xd)m2=d=1μ(d)m2.
  • 有一个细节,就是求m,那个地方有一个小 trick,就是先筛出来 1~1e5 范围内的数的约数。这个也很好写。
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;

const int maxn = 100010;
int prime[maxn], mu[maxn], cnt, a[maxn], st[maxn];

vector<int> divisors[maxn];

void sieve(int N) {
	mu[1] = 1;
	for (int i = 2; i <= N; i++) {
		if (!st[i]) st[i] = prime[cnt++] = i, mu[i] = -1;
		for (int j = 0; prime[j] <= N / i; j++) {
			st[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) break;
			mu[i * prime[j]] = -mu[i];
		}
	}
	for (int i = 1; i <= N; i++) {
		for (int j = i; j <= N; j += i) {
			divisors[j].push_back(i);
		}
	}
}

int main() {
	sieve(maxn - 1);
	int T;
	scanf("%d", &T);
	while (T--) {
		unordered_map<int, int> times;
		unordered_map<int, ll> ans;
		int N, M, K;
		scanf("%d%d%d", &N, &M, &K);
		for (int i = 1; i <= N; i++) {
			scanf("%d", &a[i]);
			for (auto p : divisors[a[i]]) {
				times[p]++;
			}
		}
		while (K--) {
			int x;
			scanf("%d", &x);
			if (ans.count(x)) {
				printf("%lld\n", ans[x]);
				continue;
			}
			for (int i = 1; i * x <= M; i++) {
				ll m = times[i * x];
				ans[x] += (ll)mu[i] * m * m;
			}
			printf("%lld\n", ans[x]);
		}
	}
	return 0;
}

D. World Tree

  • 树形dp,挖坑

E. Situation

  • 对抗搜索
  • 两个人井字棋,直到把棋盘摆满才算结束,结果是第一个人的得分减第二个人的得分。第一个人让结果尽可能大,第二个人让结果尽可能小。
  • 这是一个对抗搜索题目,就是在博弈树上面的搜索。在两种情况下,一种取max,另一种取min。
  • 这个题有一个小 trick,就是说可以保存下来状态。因为我们发现最后的答案之和最终状态有关。
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
char str[5][5];
int dp[2][60000];
int Hash() {
	int res = 0;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			int n = i * 3 + j, d;
			if (str[i][j] == '.') d = 0;
			else if (str[i][j] == 'O') d = 1;
			else d = 2;
			res += (int)(d * pow(3, n) + 0.5);
		}
	}
	return res;
}
int cal() {
	int res = 0;
	for (int i = 0; i < 3; i++) {
		if (str[i][0] == str[i][1] && str[i][1] == str[i][2]) {
			if (str[i][0] == 'O') res++;
			else res--;
		}
	}
	for (int j = 0; j < 3; j++) {
		if (str[0][j] == str[1][j] && str[1][j] == str[2][j]) {
			if (str[0][j] == 'O') res++;
			else res--;
		}
	}
	if (str[0][0] == str[1][1] && str[1][1] == str[2][2]) {
		if (str[0][0] == 'O') res++;
		else res--;
	}
	if (str[0][2] == str[1][1] && str[1][1] == str[2][0]) {
		if (str[0][2] == 'O') res++;
		else res--;
	}
	return res;
}
//用记忆化搜索的方式优化
int dfs(int st, int opt, int cnt) {
	
	if (dp[opt][st] != INF && dp[opt][st] != -INF) return dp[opt][st];
	if (cnt == 0) {
		return cal();
	}
	//printf("@@@ %d\n", opt);
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			if (str[i][j] != '.') continue;
			if (opt) {
				str[i][j] = 'O';
				//printf("### %s %s\n", str[0], str[1]);
				int nst = Hash();
				dp[opt][st] = max(dp[opt][st], dfs(nst, !opt, cnt - 1));
			}
			else {
				str[i][j] = 'X';
				//printf("*** %s %s\n", str[0], str[1]);
				int nst = Hash();
				dp[opt][st] = min(dp[opt][st], dfs(nst, !opt, cnt - 1));
				//printf("!!! %d\n", dp[opt][st]);
			}
			str[i][j] = '.';
		}
	}
	//printf("^^^ %d\n", dp[opt][st]);
	return dp[opt][st];
}
int main() {
	int T;
	scanf("%d", &T);
	int opt;
	fill(dp[0], dp[0] + 60000, INF);
	fill(dp[1], dp[1] + 60000, -INF);
	while (T--) {
		//这里的目的是做标记,看看那些没有算过
		//但是要注意的是,opt = 0初始化为INF,opt = 1初始化为-INF
		
		scanf("%d", &opt);
		int cnt = 0;
		for (int i = 0; i < 3; i++) {
			scanf("%s", str[i]);
			for (int j = 0; j < 3; j++) cnt += (str[i][j] == '.');
		}
		
		printf("%d\n", dfs(Hash(), opt, cnt));
	}
	return 0;
}

F. Forager

  • 费用流,挖坑

你可能感兴趣的:(ACM题目整理)