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\n", 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\n", ans);
	}
	return 0;
}

1006

額。。。這也是我先看到的題, 我不知道怎么想的, 覺得這道題可寫, 但是確實沒啥思路。。。到最后竟然沒有人寫出來, 我還是先說一下思路把(能不能寫出來還是一個問題。。。)通過打表或者猜測得知, 這個題的答案不會很大, 最大大概是17(當時應該想到的。。), 那么一定有一個矩形的差是不超過8的, 知道答案很小之后就可以枚舉了!於是可以枚舉小的那個矩陣的長寬的差值x和寬的值w ,那么這個矩形的長顯然是x+w,由於面積不超過n,所以這個枚舉的次數是\(8\sqrt{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\n", x, y, v);
//	printf("%d %d\n", 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\n", x, y);
//	printf("cnt[1] = %d\n", 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\n", i, j, l[i][j]);
//				printf("r[%d][%d] = %d\n", i, j, r[i][j]);
			}
		printf("%d\n", 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}^n\frac{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\n", 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\n", 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\n", b[a[l]]); 
			}
			root[i] = insert(root[i - 1], 1, num, a[i]);
		}
	}
	return 0;
}

總的來說, 這次的CCPC還算可以, 希望能夠好好補補自己知識的不足, 並且和隊伍磨合的更好。
我失去了天堂,但我不墜地獄, 加油!!


免責聲明!

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



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