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;
}