2021 BNU Winter Training 10 (2020 CCPC绵阳)

2021 BNU Winter Training 9 (2020 China Collegiate Programming Contest - Mianyang Site)

训练网址

A. Building Blocks

在这里插入图片描述

  • 题意: T T T组数据,每组数据给你三个正整数 n , m , k n,m,k n,m,k,其中 n , m n,m nm分别为积木的长和宽(积木由若干个 1 × 1 × 1 1\times1\times1 1×1×1的小方块组成),再给你左前视图(如图所示)每一部分的最终高度 a i a_i ai(共 n + m n+m n+m部分),接下来 k k k行,每行三个正整数 x , y , h x,y,h x,y,h,表示第 x x x y y y列的高度指定为正整数 h h h,问你合法的积木总数,对 1 0 9 + 7 10^9+7 109+7 取模。
  • 我们设 c n t i cnt_i cnti i i i i + 1 i+1 i+1间夹的部分,高度随意的木块儿的数量, p i p_i pi 为固定的第 i i i 部分木块儿的最大高度。
  • 函数 c a l ( h , c n t ) cal(h,cnt) cal(h,cnt) 表示搭 cnt 个木块儿,最高高度为 h,且至少有一个达到 h 的方案数。 p o w e r ( h , c n t ) power(h, cnt) power(h,cnt) 表示搭 cnt 个木块儿,最高高度为 h,可以没有木块儿达到 h。
  • f ( i , 0 / 1 ) f(i, 0/1) f(i,0/1) 表示第 i 个部分,0 没有到达最大高度,1 达到最大高度。而这个部分的与 a i a_i ai a i − 1 a_{i-1} ai1 的限制有关。
  • 有一个地方容易想错,就是说,n个位置,至少有一个高度为h(每个位置最小高度为1),求方案数。可以这样考虑,选择 k k k 个位置高度到 h h h,那么 n − k n-k nk 个位置最多到 h − 1 h-1 h1,那么方案数为 ∑ k = 1 n C n k ( h − 1 ) n − k = ∑ k = n C n k ( h − 1 ) n − k − ( h − 1 ) n = k n − ( k − 1 ) n \sum\limits_{k=1}^nC_{n}^{k}(h-1)^{n-k} = \sum\limits_{k=}^nC_{n}^{k}(h-1)^{n-k}-(h-1)^n = k^n-(k-1)^n k=1nCnk(h1)nk=k=nCnk(h1)nk(h1)n=kn(k1)n. 这样子的话,样例1就讲通了。
    题解
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200010;
typedef long long ll;
const ll mod = 1e9 + 7;
int kase, len, N, M, K;
ll f[maxn][2], p[maxn], a[maxn], cnt[maxn];

ll power(ll h, ll cnt) {
    
    
	ll res = 1;
	while (cnt) {
    
    
		if (cnt & 1) res = res * h % mod;
		h = h * h % mod;
		cnt >>= 1;
	}
	return res;
}
ll cal(ll h, ll cnt) {
    
    
	return (power(h, cnt) - power(h - 1, cnt) + mod) % mod;
}
ll solve() {
    
    
	scanf("%d%d%d", &N, &M, &K);
	len = N + M;
	for (int i = 0; i <= len; i++) f[i][0] = f[i][1] = p[i] = a[i] = cnt[i] = 0;
	for (int i = 1; i <= len; i++) {
    
    
		scanf("%lld", &a[i]);
		cnt[i + 1] = min(min(N, M), min(i, N + M - i));
	}
	for (int i = 1; i <= K; i++) {
    
    
		int x, y, h;
		scanf("%d%d%d", &x, &y, &h);
		p[x + y] = max(p[x + y], (ll)h);
		p[x + y - 1] = max(p[x + y - 1], (ll)h);
		cnt[x + y]--;
	}
	//for (int i = 1; i <= len; i++) printf("%lld ", cnt[i]);
	f[1][0] = f[1][1] = 1;
	if (a[1] == p[1]) f[1][0] = 0;
	else f[1][1] = 0;
	for (int i = 2; i <= len; i++) {
    
    
		//这个是循环每一斜对角线的方块儿
		if (p[i] > a[i]) return 0;
		if (p[i] < a[i]) {
    
    
			if (a[i - 1] >= a[i]) {
    
    
				f[i][1] = (f[i][1] + f[i - 1][1] * cal(a[i], cnt[i]) % mod) % mod;
			}
			if (a[i - 1] == a[i]) {
    
    
				f[i][1] = (f[i][1] + f[i - 1][0] * cal(a[i], cnt[i]) % mod) % mod;
			}
			f[i][0] = (f[i][0] + f[i - 1][1] * power(min(a[i - 1], a[i] - 1), cnt[i]) % mod) % mod;
			if (a[i - 1] < a[i]) {
    
    
				f[i][0] = (f[i][0] + f[i - 1][0] * cal(a[i - 1], cnt[i]) % mod) % mod;
			}
		}
		if (p[i] == a[i]) {
    
    
			f[i][1] = (f[i][1] + f[i - 1][1] * power(min(a[i - 1], a[i]), cnt[i]) % mod) % mod;
			if (a[i - 1] <= a[i]) {
    
    
				f[i][1] = (f[i][1] + f[i - 1][0] * cal(a[i - 1], cnt[i]) % mod) % mod;
			}
			f[i][0] = 0;
		}
	}
	return f[len][1];
}
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		printf("Case #%d: %lld\n", ++kase, solve());
	}
	return 0;
}

B. Code a Trie

  • Trie+LCA

C. Escape from the Island

  • 图论+dp

D. Fracture Ray

  • 树套树

E. Game of Cards

  • 博弈:你有 c 0 c_0 c0 张值为0的卡, c 1 c_1 c1 张值为1的卡, c 2 c_2 c2 张值为2的卡, c 3 c_3 c3 张值为3的卡,每次操作你可以选取俩张和小于等于3的卡,让他们融合为一张卡,融合的卡的值为他们俩张卡的和,rabbit先手,不能操作的人输。
  • 三种必输态:
  1. c 0 = 0 , c 1 = 0 c_0 = 0, c_1 = 0 c0=0,c1=0.
  2. c 0 = 1 , c 2 , c 3 , c 4 = 0 c_0=1, c_2, c_3, c_4 = 0 c0=1,c2,c3,c4=0.
  3. c 0 = 0 , c 1 = 1 , c 2 = 0 c_0 = 0, c_1 = 1, c_2 = 0 c0=0,c1=1,c2=0.
  • 有三种变化
  1. c 0 , c 1 − 1 , c 2 , c 3 c_0, c_1-1, c_2, c_3 c0,c11,c2,c3
  2. c 0 , c 1 − 1 , c 2 − 1 , c 3 + 1 c_0, c_1 - 1, c_2 - 1, c_3 + 1 c0,c11,c21,c3+1.
  3. c 0 , c 1 − 2 , c 2 + 1 , c 3 c_0, c_1 - 2, c_2 + 1, c_3 c0,c12,c2+1,c3
  • 接着我们发现,只要 c 2 ≥ 1 c_2 \ge 1 c21,无论先手如何,后手都可以让 c 0 c_0 c0 减 2 或者 c 1 c_1 c1 减3. 因此, c 2 = 0 c_2 = 0 c2=0 需要特判,其他的直接深搜就行。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int kase;
int dfs(int c0, int c1, int c2, int c3) {
    
    
	if (c0 == 0 && c1 == 0) return 0;
	//if (c0 == 1 && c1 == 0 && c2 == 0 && c3 == 0) return 0;   
	/*
	这一行判断是错误的,比如 1 3 0 0 这个数据。
	错误的原因在于,最开始已经判断过 c0 0 0 0 这样的数据。如果进入到dfs,必然意味着后面有数据不为0.
	这就意味着,c1 >= 3 的话,c3不会等于0. 但是取模省略了这一过程。
	*/
	
	if (c0 && !dfs(c0 - 1, c1, c2, c3)) return 1;
	if (c1 && c2 && !dfs(c0, c1 - 1, c2 - 1, c3 + 1)) return 1;
	if (c1 >= 2 && !dfs(c0, c1 - 2, c2 + 1, c3)) return 1;
	return 0;
}
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		int c0, c1, c2, c3;
		scanf("%d%d%d%d", &c0, &c1, &c2, &c3);
		int win = -1;
		if (c1 == 0 && c2 == 0 && c3 == 0) {
    
    
			//小心这里,这里和dfs不一样。的c0剩1张时,是输的,然后c0仍然是一次减少1张。
			if (c0 == 0) win = 0;
			else if (c0 & 1) win = 0;
			else win = 1;
		}
		else win = dfs(c0 % 2, c1 % 3, c2, c3);
		printf("Case #%d: %s\n", ++kase, win ? "Rabbit" : "Horse");
	}
	return 0;
}

F. Hide and Seek

  • 题意:二维格点平面上有两个点,给定这两个点各自离原点的曼哈顿距离(记为 d 01 d_{01} d01, d 02 d_{02} d02)以及两点之间的曼哈顿距离(记为 d 12 d_{12} d12),问这两个点有多少种可能的位置 pair。
  • 分类讨论,分类的方式不同,讨论的难度完全不一样。
    讲的不错
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int kase;
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		ll d1, d2, d, ans;
		scanf("%lld %lld %lld", &d1, &d2, &d);
		if (d1 > d2) swap(d1, d2);
		if (d1 == 0) {
    
    
			if (d2 == d && d > 0) ans = 4 * d;
			else if (d2 == d && d == 0) ans = 1;
			else ans = 0;
		}
		else {
    
    
			if (d == 0) {
    
    
				if (d1 == d2) ans = 4 * d1;
				else ans = 0;
			}
			ll dmin = d2 - d1, dmax = d2 + d1;
			if (d < dmin) ans = 0;
			else if (d == dmin) {
    
    
				ans = 4 * (d + d1 * (d + 1));
			}
			else if (d < dmax) {
    
    
				if ((d + dmin) & 1) ans = 0;
				else {
    
    
					ans = 4 * (d + d1 + d2);
				}
			}
			else if (d == dmax) {
    
    
				ans = 4 * (d2 + d1 * (d2 + 1));
			}
			else {
    
    
				ans = 0;
			}
		}
		printf("Case #%d: %lld\n", ++kase, ans);
	}
	return 0;
}

G. Lottery

  • 题意:给出n个盒子,每个盒子俩个参数a[i]和x[i],表示盒子里面有x[i]个价值为 2 a [ i ] 2^{a[i]} 2a[i] 的球,你可以在每个盒子里面取任意个球,求你最后可能取的球的价值总和的种类是多少。
  • 这个题思路很好想,就是看写成二进制之后,能拆成一段一段的,然后把这些值乘起来。但是有一个难点不好实现,就是如何拆成一段一段的。
  • 可以考虑用map维护,map确实是可以在遍历的过程中更新的。可以类比多重背包的处理,拆成二进制的形式,即 112121012111…,这样子你会发现,每一位不超过2的话,可以拆成一块一块的,不用担心进位导致两块儿连起来的问题(就算有一个2进位使得中间的1个0变成1,但是两块儿仍然是单独的,因为后面的块儿无论如何也无法进位到前面的块儿去)。
  • 这样子做的话有点卡常。因此去掉了快速幂,把 l o n g long long l o n g long long 换成 i n t int int.
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
const int maxn = 100010;
typedef long long ll;
const ll mod = 1e9 + 7;

int N, kase;
map<int, int> mp;


int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		scanf("%d", &N);
		mp.clear();
		for (int i = 1; i <= N; i++) {
    
    
			int x, n;
			scanf("%d%d", &x, &n);
			mp[x] = n;
		}
		
		for (auto p : mp) {
    
    
			int x = p.first, n = p.second;
			mp[x] = 0;
			for (int i = 0; (1 << i) <= n; i++) mp[x + i]++, n -= (1 << i);
			for (int i = 0; (1 << i) <= n; i++) {
    
    
				if((n >> i) & 1)
					mp[x + i]++, n -= (1 << i);
			}
		}

		int ans = 1, res = 1, base = 1;
		for (auto p : mp) {
    
    
			//printf("*** %lld %lld\n", p.first, p.second);
			int x = p.first, n = p.second;
			if (mp[x - 1] == 0) {
    
    
				ans = (ll)ans * (ll)res % mod;
				res = 1, base = 1;
			}
			res = (res + (ll)base * (ll)n % mod) % mod;
			base = base * 2 % mod;
			
		}
		ans = (ll)ans * (ll)res % mod;

		printf("Case #%d: %d\n", ++kase, ans);
	}
	return 0;
}
/*
3
3
1 1
2 1
3 1
3
1 1
2 2
3 3
6
1 1
2 2
3 3
6 1
7 2
8 3

*/

猜你喜欢

转载自blog.csdn.net/qq_45812711/article/details/114275904