A - 最小生成树
思路:贪心
首先,是完全图;其次,权值与点有关。
为了边权尽可能小,那么在联通的基础上找个点权最小的把其他点都连一起就可以了。
将点权排序,最小的点权*n-1加上其他点权和即为答案,记得开ll
时间复杂度:
反思
看见带权无向图求最小生成树别直接上来就写模板,写了半天完事后看通不过傻眼了。不说别的,就连存边的数组都开不下来,还怎么找最小树。
代码
#include <stdio.h> #include <algorithm> using namespace std; typedef long long ll; const int N = 1e5+20; int a[N]; int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; ++i) scanf("%d", &a[i]); sort(a + 1, a + n + 1); ll ans = 1ll * a[1] * (n - 2); for(int i = 1; i <= n; ++i) ans += 1ll * a[i]; printf("%lld\n", ans); return 0; }
B - 病毒感染
思路:树的重心,dfs
涉及树的重心,可参考以下资料:
简而言之,对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。
性质之一:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
用dfs找一下子树中最大的节点数,最小的拿出来更新重心,注意重心不止一个
时间复杂度:
反思
最开始想到Dijkstra,但是如果对每个节点作为根计算一遍距离和找最小的,时间复杂度绝对会超时,后来看到重心才知道正解。
代码
#include <stdio.h> #include <vector> #include <algorithm> using namespace std; const int N = 5e4+10; vector<int> g[N], ans; int sz[N], n, mn = 1e8; void dfs(int v, int f) { sz[v] = 1; int mx = 0; for(auto u : g[v]) { if(u != f) { dfs(u, v); sz[v] += sz[u]; mx = max(mx, sz[u]); } } mx = max(mx, n - sz[v]); if(mx < mn) { mn = mx; ans.clear(); ans.push_back(v); } else if(mx == mn) ans.push_back(v); } int main() { int m; scanf("%d%d", &n, &m); for(int i = 1; i <= m; ++i) { int a, b; scanf("%d%d", &a, &b); g[a].push_back(b); g[b].push_back(a); } dfs(1, 0); for(auto v : ans) printf("%d ", v); printf("\n"); return 0; }
C - Shopping
思路:贪心
根据凳子数和购物车数之间的大小关系,使尽可能多的物品可以半价购入,最多为cnt=min(num, m),按a值排序后把后cnt个贵的物品按半价计算,其余全价计算
时间复杂度:
代码
#include <stdio.h> #include <algorithm> using namespace std; const int N = 1e3+10; struct Dz{ int a, b; bool operator < (const Dz& d) const { return a < d.a; } }dz[N]; int main() { int t; scanf("%d", &t); while(t--) { int n, m, num = 0; scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++i) { scanf("%d%d", &dz[i].a, &dz[i].b); if(dz[i].b) num++; } sort(dz + 1, dz + n + 1); double ans = 0; int now = n; for(int i = 0; i < min(num, m); ++i) ans += (double)dz[now--].a / 2; while(now) ans += (double)dz[now--].a; printf("%.1f\n", ans); } return 0; }
D - 铺地毯
思路:暴力
虽然暴力就可以过,但是别啥都暴力,比如别真开个N*N的数组挨个维护地毯编号n次,完全没必要,那样做明显超时
正确的暴力做法为先将n张地毯记下来,读入坐标后再遍历所有地毯,边界符合的最后一个即为答案,当然也可以反向遍历,那样答案就是第一个边界符合的地毯编号
时间复杂度:
代码
#include <stdio.h> const int N = 1e4+20; struct Dt{ int a, b, g, k; }dt[N]; int main() { int n, x, y, ans; scanf("%d", &n); for(int i = 1; i <= n; ++i) scanf("%d%d%d%d", &dt[i].a, &dt[i].b, &dt[i].g, &dt[i].k); scanf("%d%d", &x, &y); for(int i = 1; i <= n; ++i) { int x1 = dt[i].a, y1 = dt[i].b, x2 = x1 + dt[i].g, y2 = y1 + dt[i].k; if(x1 <= x && x2 >= x && y1 <= y && y2>= y) ans = i; } printf("%d\n", ans); return 0; }
E - 金币馅饼
思路:动态规划
dp[i][j]表示在(i,j)位置可获得的最大金币数,由题意,状态由前一列相邻的3个位置更新而来:
注意由于初始站在(1,1)位置,所以在第2列到第r列间的更新要单独拿出来,毕竟dp[2][2]只能由dp[1][1]而不可能由dp[2][1]和dp[3][1]更新过来,也就是说除了边界外还不能超过某个限制,而后续的更新就只有边界限制了
时间复杂度:
代码
#include <stdio.h> #include <algorithm> using namespace std; const int N = 1e2+10; int a[N][N], dp[N][N]; int main() { int r, c; scanf("%d%d", &r, &c); for(int i = 1; i <= r; ++i) for(int j = 1; j <= c; ++j) { scanf("%d", &a[i][j]); dp[i][j] = 0; } dp[1][1] = a[1][1]; for(int j = 2; j <= r; ++j) for(int i = 1; i <= j; ++i) for(int k = max(i - 1, 1); k <= min(i + 1, j); ++k) dp[i][j] = max(dp[i][j], dp[k][j - 1] + a[i][j]); for(int j = r + 1; j <= c; ++j) for(int i = 1; i <= r; ++i) for(int k = max(i - 1, 1); k <= min(i + 1, r); ++k) dp[i][j] = max(dp[i][j], dp[k][j - 1] + a[i][j]); printf("%d\n", dp[r][c]); return 0; }