2021CCPC河南省赛

最终A了8道题, 河南省排名第四, 喜提一金, 也是在意料之中。 第一次三个队友集中在一起打比赛, 也体验了一下线下的氛围, 还是比较赞的, 自己也不是说毫无作用, 帮助团队做了几道题, 还是挺满意的。

题目的下载地址

1002

emmmmm, 我当时是正着看的题, 所以率先看到了这道题, 我感觉是能写, 但刚开始这道题A的并不多, 并且我的数论就。。。。, 最后让给了队友, 最后还是A掉了。 首先我们看到, 总的方案数肯定是(m^n), 这就是最后的分母,在看我们对boss的伤害其实对技能没有关系, 只是跟技能的次数有关系,所以我们不妨枚举每个技能出现的次数所对boss造成的伤害, 最终乘以m即可。 假如第一个技能用了i次, 那能够造成的伤害总量是多少呢。 首先这个技能位置的选择是(C_n^i), 其他技能的选择就是((m-1)^{n-i}), 不难看出,这个技能对答案的贡献是(sum_{i = 1}^nC_n^i*(m-1)^{n-i}*i^2), 当然, 把这个数乘以m就是我们的最终答案。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

#define int long long
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
const int maxn = 1e5; 

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int T, n, m, ans = 0;
int jc[N], inv[N];

inline int power(int a, int b) {
	int ans = 1;
	while (b) {
		if (b & 1) ans = (ans * a) % mod;
		a = a * a % mod;
		b >>= 1; 
	}
	return ans;
}

inline void pre() {
	jc[0] = 1;
	for (int i = 1; i <= maxn; ++i) jc[i] = jc[i - 1] * i % mod;
	inv[maxn] = power(jc[maxn], mod - 2);
	for (int i = maxn - 1; i >= 0; --i)
		inv[i] = inv[i + 1] * (i + 1) % mod;
}

inline int C(int n, int m) {
	return jc[n] * inv[m] % mod * inv[n - m] % mod;
}

signed main() {
	read(T);
	pre();
	while (T--) {
		read(n), read(m);
		ans = 0;
		for (int i = 1; i <= n; ++i) 
			ans = (ans + m * C(n, i) % mod * power(m - 1, n - i) % mod * i % mod * i % mod) % mod;
		ans = ans * power(power(m, n), mod - 2) % mod;
		printf("%lld
", ans);
	}
	return 0;
} 

1003

这是我独立A掉的一道题, 感觉还可以, 看到这张图上只有17个点, 考虑状压, 并且最优情况下的话, 每一次都会投出6, 结合最短路, 设dis[x][z],表示在第x个点, 状压的状态为z的所剩的最大骰子数。 跑一遍Dijkstra即可, 当然, 这道题的细节很多, 首先, dis的初值一定要是-1, 因为有些点根本就达不到, 并且, 题目上说的是严格大于这个格子的数值。 最后, 在你占领某个格子以后显然可以随意的来回, 不需要消耗糖果。 这个问题当时卡了我一下。最后我想出办法, 当我把队头的某个点取出后, 枚举当前所占领的点, 如果此时的dis[y][z]还没有更新的话, 我们可以让他回去, 及dis[y][z] = dis[x][z], 这样便于更新接下来答案。

1005

这是开的第一题, 也经历一些波折之后写过去了, 其实还是挺简单的, 设dp[i][j]表示前i个事件所剩体力为j所能得到的最大价值, 然后双重循环即可, 如果结束的话就是在每回合直接更新一下ans, 就相当于到这一轮结束了, 这道题要用滚动数组, 而且初始化一定要是最小值, 而不是0

点击查看代码
#include <bits/stdc++.h>

using namespace std;

#define int long long
const int N = 6e3 + 10;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int T, n, H, ans;
int a[N], b[N], c[N], d[N];
int f[2][N];

signed main() {
	read(T);
	while (T--) {
		read(n), read(H);
		for (int i = 1; i <= n; ++i) 
			read(a[i]), read(b[i]), read(c[i]), read(d[i]);
		memset(f, 0xcf, sizeof(f));
		f[0][0] = 0; ans = 0;
		int u = 1;
		for (int i = 1; i <= n; ++i) {
			for (int j = H; j >= 0; --j) {
				if (j >= a[i]) f[u][j] = max(f[u][j], f[u ^ 1][j - a[i]] + b[i]);
				if (j >= c[i]) f[u][j] = max(f[u][j], f[u ^ 1][j - c[i]] + d[i]);
				ans = max(ans, f[u][j]);
			}
			memset(f[u ^ 1], 0xcf, sizeof(f[u ^ 1]));
			u ^= 1;
		}
		printf("%lld
", ans);
	}
	return 0;
}

1006

额。。。这也是我先看到的题, 我不知道怎么想的, 觉得这道题可写, 但是确实没啥思路。。。到最后竟然没有人写出来, 我还是先说一下思路把(能不能写出来还是一个问题。。。)通过打表或者猜测得知, 这个题的答案不会很大, 最大大概是17(当时应该想到的。。), 那么一定有一个矩形的差是不超过8的, 知道答案很小之后就可以枚举了!于是可以枚举小的那个矩阵的长宽的差值x和宽的值w ,那么这个矩形的长显然是x+w,由于面积不超过n,所以这个枚举的次数是(8sqrt{n})的枚举了其中一个矩阵,就知道了另一个矩阵的面积(S),由于已经知道答案会很小,另一个矩阵的长宽之差肯定也很小,枚举矩阵的宽(从(sqrt{S})逐渐减小的枚举),当长宽之差不小于lim- x就可以停止了,后面肯定没有要找的答案。加上一系列的优化应该会跑的很快。(这道题暂时待定把。。。。)

1007

这道题没写出来挺可惜的其实, 大概的思路都想到了其实, 就是说让每个点都作为中心点, 然后主副对角线不同元素取个最小值。 但是在统计的时候没有细想,哎。不想打了, 直接粘一波题解把。。
image
其实统计个数的时候完全可以开个数组来判断某个数是否出现过, 只是当时想的是行枚举, 其实按对角线枚举的话就轻而易举了。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;
const int M = 1e6 + 10;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar(); 
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	} 
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

ll ans = 0;
int T, n, a[N][N];
int l[N][N], r[N][N]; //×óбºÍÓÒб 
int cnt[M];

inline bool check1(int x, int y, int v) {
	if (x - v <= 0 || x + v > n || y - v <= 0 || y + v > n) return false;
	if (cnt[a[x - v][y - v]] || cnt[a[x + v][y + v]]) return false;
	if (a[x - v][y - v] == a[x + v][y + v]) return false;
	++cnt[a[x - v][y - v]];
	++cnt[a[x + v][y + v]];
	return true;
}

inline bool check2(int x, int y, int v) {
	if (x - v <= 0 || x + v > n || y - v <= 0 || y + v > n) return false;
//	printf("x = %d y = %d v = %d
", x, y, v);
//	printf("%d %d
", cnt[a[x + v][y - v]], cnt[a[x - v][y + v]]);
	if (cnt[a[x + v][y - v]] || cnt[a[x - v][y + v]]) return false;
	if (a[x + v][y - v] == a[x - v][y + v]) return false;
	++cnt[a[x + v][y - v]];
	++cnt[a[x - v][y + v]];
	return true;
}

inline void solve1(int x, int y) {
	++cnt[a[x][y]];
	++x, ++y;
	while (x <= n && y <= n) {
//	printf("x = %d y = %d
", x, y);
//	printf("cnt[1] = %d
", cnt[1]);
		if (r[x - 1][y - 1] == 1) {
			++cnt[a[x][y]];
			int v = 1;
			--cnt[a[x - 1][y - 1]];
			while (check1(x, y, v)) ++v;
			r[x][y] = v;
		} else {
			int v = r[x - 1][y - 1] - 1;
			--cnt[a[x - v][y - v]];
			--cnt[a[x - v - 1][y - v - 1]];
			while (check1(x, y, v)) ++v;
			r[x][y] = v;
		}
		++x, ++y;
	}	
	--x, --y;
	--cnt[a[x][y]];
}

inline bool solve2(int x, int y) {
	++cnt[a[x][y]];
	++x, --y;
	while (x <= n && y <= n && x > 0 && y > 0) {
		if (l[x - 1][y + 1] == 1) {
			++cnt[a[x][y]];
			int v = 1;
			--cnt[a[x - 1][y + 1]];
			while (check2(x, y, v)) ++v;
			l[x][y] = v;
		} else {
			int v = l[x - 1][y + 1] - 1;
			--cnt[a[x - v][y + v]];
			--cnt[a[x - v - 1][y + v + 1]];
			while (check2(x, y, v)) ++v;
			l[x][y] = v;
		}
		++x, --y;
	}
	--x, ++y;
	--cnt[a[x][y]];
}

int main() {
	read(T);
	while (T--) {
		read(n);
		ans = 0;
		for (int i = 1; i <= n; ++i) 
			for (int j = 1; j <= n; ++j) {
				read(a[i][j]);
				l[i][j] = r[i][j] = 1;
			}
		solve1(1, 1); // ÓÒб 
		for (int i = 2; i <= n; ++i) {
			solve1(1, i);
			solve1(i, 1);
		}
		solve2(1, 1); //×óб
		for (int i = 2; i <= n; ++i) {
			solve2(1, i);
			solve2(i, n);
		} 
		for (int i = 1; i <= n; ++i) 
			for (int j = 1; j <= n; ++j) {
				ans += min(l[i][j], r[i][j]);
//				printf("l[%d][%d] = %d
", i, j, l[i][j]);
//				printf("r[%d][%d] = %d
", i, j, r[i][j]);
			}
		printf("%d
", ans);
	}
	return 0;
}

1008

又是一道数论题, 当然是交给圣元了, 但是他的那种方法好像被卡了, 最后也没有过。 这道题的方法还是挺多的, 数论也要好好学呀。。。
介于我数论的垃圾性, 我现在只学了一种做法, 还是写出来把。。
首先当x>y时,x%y=k显然等价于x=ny+k;(n≥1)那么我们想到, 对于每个y和k,都有固定的x与之对应, 比如y+k, 2y+k...那么我们可以枚举出每一个k和y的x的取值, 复杂度为(O(k*n*sum_{x=1}^nfrac{n}{x})), 由著名的调和级数得知, 复杂度为(O(knlogn)), 是可以过去的,我们枚举每一种方案数是, 可以用数组f[i][j]表示k为i, x为j的方案数, 那么求一个前缀和就是n为j的方案数。
再想一下, 当x<y时,如果k=0, 显然无解, 当k>0时, x只能是k, y可以取[k+1, n]之间的任意数, 特判一下即可。。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

#define int long long
const int N = 1e5 + 10;
const int maxn = 1e5;
const int mod = 23333;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int T, n, k, ans = 0;
int f[11][N];

inline int power(int a, int b) {
	int ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

signed main() {
	// x % y == k
	for (int i = 0; i <= 10; ++i) { // 枚举k 
		for (int j = i + 1; j <= maxn; ++j) // 枚举 y
			for (int k = 1; k * j + i <= maxn; ++k) // k*j+i是x 
				++f[i][k * j + i];
		for (int j = 1; j <= maxn; ++j) f[i][j] = (f[i][j] + f[i][j - 1]) % mod;
	}
	//当x>=k时,x%y==k等价(x-k)%y==0 
	//上面都是保证x>y的 
	read(T);
	while (T--) {
		read(n), read(k);
		int cnt = f[k][n];
		if (k != 0) cnt += max(n - k, 0ll); 
		// 这里表示当x<y时,x只能时k, y可以是[k+1, n]之间的人任何数
		cnt %= mod;
		cnt = cnt * power(n * n % mod, mod - 2) % mod;
		ans ^= cnt;
	}
	printf("%lld
", ans);
	return 0;
}

1009

我和古晨峰合伙过的一道题, 首先对于每个背包i,都有l[i]的物品是必取的, 那我们显然是需要把这些物品全取出来, 把前k个值翻倍(不足k个全部翻倍), 这就是取最少物品的答案, 而对于每个背包i而言, 还可以取r[i]-l[i]个物品, 那我们把这些物品全部取出来, 从大到小排序, 每次取出最大的数显然就是当前方案的最大值, 这时, 我们需要动态维护一个前K大的值, 因为这些物品需要翻倍, 一个小根堆显然可以, 保证这个小根堆的size要≤k, 如果等于k的话, 就把最小的值取出,与当前的这个数比较, 如果当前的这个数比较大, 就把堆顶出栈, 这个数加进去, 还是挺容易实现的。

1010

额。。我和古晨峰神奇的讨论出主席树+二分的做法, 交了一发还真过了, 他的码力是真强呀。。。。
先说一下我们的做法把, 首先离散化, 然后建一颗权值主席树, 从当前这个点去判断左边是否存在k个比它大的点, 如果存在的话就去二分这个节点, 总复杂度(nlog^2n), 与k没有关系。。,但是k只有50, 并且数据是随机生成的, 所以暴力出奇迹, 暴力直接A

暴力
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int T, n, k, a[N];

int main() {
	read(T);
	while (T--) {
		read(n), read(k);
		for (int i = 1; i <= n; ++i) read(a[i]);
		for (int i = 1; i <= n; ++i) { 
			if (i <= k) puts("-1");
			else {
				int cnt = 0;
				for (int j = i - 1; j >= 0; --j) {
					if (j + cnt < k) {
						puts("-1");
						break;
					}
					if (a[j] > a[i]) ++cnt;
					if (cnt == k) {
						printf("%d
", a[j]);
						break;
					}
				}
			}
		}
	}
	return 0;
}
主席树+二分
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int T, n, k, tot = 0, a[N], b[N], root[N];
struct tree {
	int l, r;
	int cnt;
}t[N * 21];

inline int build(int l, int r) {
	int p = ++tot;
	t[p].l = t[p].r = t[p].cnt = 0; 
	if (l == r) return p;
	int mid = l + r >> 1;
	t[p].l = build(l, mid);
	t[p].r = build(mid + 1, r);
	return p; 
}

inline int query(int p, int l, int r, int num) {
	if (l == r) return t[p].cnt;
	int mid = l + r >> 1;
	int ans = 0;
	if (mid <= num) return query(t[p].r, mid + 1, r, num);
	else return query(t[p].l, l, mid, num) + t[t[p].r].cnt;
} 

inline int insert(int p, int l, int r, int k) {
	int q = ++tot;
	t[q] = t[p];
	if (l == r) {
		++t[q].cnt;
		return q; 
	}
	int mid = l + r >> 1;
	if (k <= mid) t[q].l = insert(t[p].l, l, mid, k);
	else t[q].r = insert(t[p].r, mid + 1, r, k);
	t[q].cnt = t[t[q].l].cnt + t[t[q].r].cnt;
	return q;
}

int main() {
	read(T);
	while (T--) {
		read(n), read(k);
		tot = 0;
		for (int i = 1; i <= n; ++i) {
			read(a[i]);
			b[i] = a[i];
		}
		sort(b + 1, b + n + 1);
		int num = unique(b + 1, b + n + 1) - b - 1;
		for (int i = 1; i <= n; ++i) 
			a[i] = lower_bound(b + 1, b + num + 1, a[i]) - b;
		root[0] = build(1, num); 
		for (int i = 1; i <= n; ++i) {
			int cnt = query(root[i - 1], 1, num, a[i]);
			if (cnt < k) puts("-1");
			else {
				int l = 1, r = i - 1;
				while (l < r) {
					int mid = l + r >> 1;
					if (cnt - query(root[mid], 1, num, a[i]) >= k) l = mid + 1;
					else r = mid;
				}
				printf("%d
", b[a[l]]); 
			}
			root[i] = insert(root[i - 1], 1, num, a[i]);
		}
	}
	return 0;
}

总的来说, 这次的CCPC还算可以, 希望能够好好补补自己知识的不足, 并且和队伍磨合的更好。
我失去了天堂,但我不坠地狱, 加油!!

原文地址:https://www.cnblogs.com/AK-ls/p/15494401.html