1121考试总结

1121考试

T1

​ 水题.

T2

​ 题目大意:

​ 小林和亮亮在桃园里一起玩游戏。桃园里的桃树成行成列,刚好构成一个 N×M 的矩阵,亮亮在某些桃树下放置了一些小礼物,要求小林把所有树下的礼物全部收集起来。小林从左上角的桃树(1,1)出发,走到右下角的桃树(N,M)。他只能沿着路径向下或者向右走,某些桃树下有礼物,他必须到达所有有礼物的树下并把礼物收集起来。小林在出发前,想请你帮他计算一下,他有多少种不同的走法。由于答案可能很大,你只需要输出答案模 100000000(10^8)后的值即可。

\(n, m <= 30000, k <= 10000\)

​ 扩展卢卡斯, 阶乘分解.

​ 首先转化问题, 这\(k\)个必须经过的点会把整个图框处\(k + 1\)个小矩形, 对于每一个\(ln * lm\)小矩形它的走法有\(C_{ln + lm - 2}^{lm - 1}\)种, 然后用乘法原理把每个矩形的答案乘起来就好.

​ 然后一看模数不是质数, 那么扩展卢卡斯可做.不过这里用的是机房dalao给我讲的另一种方法, 叫做阶乘分解.

​ 假设当前我们要求\(C_n^m\), 那么我们可以知道得数一定是一个整数. 是整数就可以用唯一分解定理.

\(C_n^m = p_1^{k_1}*p_2^{k _2}*...p_c^{k_c} = \frac{n!}{m!(n - m)!}\).

​ 可以知道结果里的\(k_1\)就等于\(n!\)\(p_1\)的个数减去\(m!,(n-m)!\)\(p_1\)的个数.

\(n!\)\(p_1\)的个数可以这么算:\(\displaystyle \sum_{i = 1}^{p^i <= n} \lfloor \frac{n}{p^i}\rfloor\).可以在log的时间复杂度内求出.

​ 然后我们就可以算出\(k_1, k_2, ... , k_c\), 就没啦.

​ 复杂度分析:小于\(n\)的质数大概有\(\frac{n}{ln \ n}\), 然后算出每个质数出现的次数就是在乘上个lg, 近似与\(O(n)\).然后要算\(k+1\)个小矩形, 那么复杂度就是\(O(kn)\).在枚举质数的时候我们不必把所有的质数都枚举完, 复杂度远远达不到\(O(kn)\).事实上这个程序最大的数据点只跑了300多毫秒.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
	long long s = 0, f = 1; char ch;
	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
	return s * f;
}

const int N = 1e4 + 5, M = 1e5 + 5, mod = 1e8;
int n_, m_, k_, ans_, cnt;
int prime[M], is_prime[M], cnt_[M];
struct node { int x, y; } a[N];

int cmp(node a, node b) { return a.x == b.x ? a.y < b.y : a.x < b.x; }

void make_prime() {
	for(int i = 2;i < M; i++) {
		if(!is_prime[i]) { prime[++ cnt] = i; }
		for(int j = 1;j <= cnt && i * prime[j] < M; j++) {
			is_prime[i * prime[j]] = 1;	
			if(!(i % prime[j])) break;
 		}
	}
}

void calc_cnt_(int x, int f) {
	for(register int i = 1;i <= cnt; i++) { 
		if(prime[i] > x) break;
		for(int now = prime[i];now <= x; now *= prime[i]) {
			cnt_[prime[i]] += (x / now) * f;
		}
	}
}

int ksm(int x, int y) {
	int res = 1;
	while(y) { if(y & 1) res = 1ll * res * x % mod; x = 1ll * x * x % mod; y >>= 1; }
	return res;
}

int C(int n, int m) {
	int res = 1;
	for(register int i = 1;i <= cnt; i++) {
		if(prime[i] > n) break;
		cnt_[prime[i]] = 0;
	}
	calc_cnt_(n, 1); calc_cnt_(m, -1); calc_cnt_(n - m, -1);
	for(register int i = 1;i <= cnt; i++) {
		if(prime[i] > n) break;
		res = 1ll * res * ksm(prime[i], cnt_[prime[i]]) % mod;
	}
	return res;
}

int calc(int now) {
	int ln = a[now].x - a[now - 1].x + 1, lm = a[now].y - a[now - 1].y + 1;
	return C(ln + lm - 2, lm - 1);
}

int main() {

	n_ = read(); m_ = read(); k_ = read(); make_prime();
	for(register int i = 1;i <= k_; i++) a[i].x = read(), a[i].y = read();
	k_ ++; a[k_].x = n_; a[k_].y = m_; a[0].x = 1; a[0].y = 1;
	sort(a, a + k_ + 1, cmp);
	for(int i = 1;i <= k_; i++) {
		if(a[i].y < a[i - 1].y) { printf("0\n"); return 0; }
	} ans_ = 1;
	for(int i = 1;i <= k_; i++) ans_ = 1ll * ans_ * calc(i) % mod;
	printf("%d", ans_);
	
	return 0;
}

T3

​ 题目大意:

​ 亮亮在梦中游历了魔法城堡后,对此心驰神往,于是用自己制造的法杖,创造了一片魔法森林。这片森林中一开始有 n 个节点,没有边相连,若想要在第 i 个点和第 j 个点之间建立一条双向通路,则需花费 Cij 的魔法值。
​ 每个结点上住着一个魔法居民,若两个节点间有边直接相连,则他们就成为了邻居。居民一共有三种类型:
​ ①村民:他们只能通过道路拜访自己的邻居。
​ ②巫师:他们可以拜访自己的邻居以及邻居的邻居。
​ ③大魔法师:由于他们拥有法力,因此可以拜访所有与自己连通的人。
​ 亮亮不希望有人孤单,因此他保证了每种类型的居民要么不出现,否则至少出现两个。同时,他又希望大家能建立良好的关系,所以他决定花费魔法值为魔法森林修路,使得任意居民都可以拜访其他所有的居民。他想知道,最少需要建立多少条道路才能达成自己的心愿。在道路数目最少的前提下,花费的魔法值最小又是多少。

\(n <= 250, Cij <= 1e9\).

​ 乱搞.

​ 我们可以分情况来做.

​ 如果全部都是3类型点, 那么直接最小生成树.

​ 如果存在1类型点, 那么1类型的点肯定要和所有人都连一条边.连完之后发现2, 3类型点也满足了.

​ 剩下的情况就是一定存在2类型的点, 可能存在3类型的点的情况了.

​ 我们知道菊花图, 也就是一个点为中心, 剩下所有点与它连边, 我们发现这个图是满足这种情况的条件的, 那我们直接找一个边权和最小的菊花图就好啦.但是有特殊情况, 如果只存在两个2类型的点, 别忘了考虑将这两个点连一下边, 剩下点连与这两个点距离较小的那一条边的情况.那么为什么2类型的点大于两个是不用考虑这种情况呢?因为至少会有两个2类型的点距离为2.

​ 其实这题数据再大一点也是可以的.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
	long long s = 0, f = 1; char ch;
	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
	return s * f;
}

const int N = 300;
int n, tag, num, cnt;
long long ans;
int a[N], fa[N], vus[N], dis[N][N], vis[N][N];
struct edge { int u, v, w; } e[N * N];

void add(int x, int y, int z) {
	e[++ cnt].u = x; e[cnt].v = y; e[cnt].w = z;
}

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

int cmp(edge a, edge b) { return a.w < b.w; }

void work1() {
	for(int i = 1;i <= n; i++) 
		for(int j = 1, x;j <= n; j++) {
			x = read(); 
			if(j > i) add(i, j, x);
		}
	sort(e + 1, e + cnt + 1, cmp);
	for(int i = 1;i <= n; i++) fa[i] = i;
	for(int i = 1;i <= cnt; i++) {
		int fx = find(e[i].u), fy = find(e[i].v);
		if(fx == fy) continue;
		fa[fx] = fy; num ++; ans += e[i].w;
		if(num == n - 1) break;
	}
	printf("%d %lld", n - 1, ans);
}

void work2() {
	for(int i = 1;i <= n; i++)
		for(int j = 1;j <= n; j++) dis[i][j] = read();
	for(int k = 1;k <= n; k++) 
		if(a[k] == 1) {
			for(int i = 1;i <= n; i++) 
				if(!vis[i][k] && i != k) {
					ans += dis[i][k]; vis[i][k] = vis[k][i] = 1;
					num ++;
				}
			}
	printf("%d %lld", num, ans);
}

void work3() {
	ans = 1e18;
	for(int i = 1;i <= n; i++)
		for(int j = 1;j <= n; j++) dis[i][j] = read();
	for(int i = 1;i <= n; i++) if(a[i] == 2) num ++, vus[i] = 1;
	for(int i = 1;i <= n; i++) {
		long long res = 0;
		for(int j = 1;j <= n; j++) res += dis[i][j];
		ans = min(ans, res);
	}
	if(num == 2) {
		int cnt = 0, p[3];
		for(int i = 1;i <= n; i++) if(a[i] == 2) p[++ cnt] = i;
		long long res = dis[p[1]][p[2]];
		for(int i = 1;i <= n; i++) {
			if(i != p[1] && i != p[2]) res += min(dis[i][p[1]], dis[i][p[2]]);
		}
		ans = min(res, ans);
	}
	printf("%d %lld", n - 1, ans);
}

int main() {
	
	n = read();
	for(int i = 1;i <= n; i++) {
		a[i] = read();
		if(a[i] != 3 && tag != 2333) tag = 1;
		if(a[i] == 1) tag = 2333;
	}
	if(!tag) work1();
	else if(tag == 2333) work2();
	else work3();
    
	return 0;
}
posted @ 2020-11-22 17:40  C锥  阅读(148)  评论(0编辑  收藏  举报