写在前沿:这场小白赛难度不大,但是模拟题太多太多了,码量巨大,并不是很喜欢。
因为模拟太多赛后进行代码改良许多参考了码量很少的hx073269的代码。
A、拼三角
组给出个数,询问能不能构成两个三角形。
三种做法都是暴力,不可以通过去比较更小的三个和更大的三个,这里给出出题人的例子。
3 8 12 15 16 16 3 15 16一组 8 12 16一组
暴力就容易了,可以写,也可以写二进制枚举,也可以写。我给我写的二进制枚举的做法吧。
#include <bits/stdc++.h> using namespace std; #define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) #define all(__vv__) (__vv__).begin(), (__vv__).end() #define endl "\n" #define pai pair<int, int> #define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__)) #define rep(i, sta, en) for(int i=sta; i<=en; ++i) #define repp(i, sta, en) for(int i=sta; i>=en; --i) typedef long long ll; typedef unsigned long long ull; typedef long double ld; inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar()) s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; } inline void print(ll x, int op = 10) { if (!x) { putchar('0'); if (op) putchar(op); return; } char F[40]; ll tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); if (op) putchar(op); } inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; } ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans *= a; b >>= 1; a *= a; } return ans; } ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; } const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} }; const int MOD = 1e9 + 7; const int INF = 0x3f3f3f3f; struct Node { ll val; int id; bool operator < (const Node& opt) const { return val < opt.val; } }; const int N = 1e6 + 7; ll n, m; ll p[10]; bool calc(int i, int j, int k) { return p[i] + p[j] > p[k]; } void solve() { rep(i, 0, 5) p[i] = read(); sort(p, p + 6); m = (1 << 6) - 1; rep(i, 0, m) { // 1代表在第一个集合,0代表在第二个集合 if (__builtin_popcount(i) != 3) continue; vector<int> a, b; rep(j, 0, 5) { if (i >> j & 1) a.push_back(j); else b.push_back(j); } if (calc(a[0], a[1], a[2]) and calc(b[0], b[1], b[2])) { puts("Yes"); return; } } puts("No"); } int main() { int T = read(); while (T--) solve(); return 0; }
B、相对分子质量
给出你每个原子的相对原子质量,叫你求给出的分子的相对分子质量。
因为答案保证在范围内,所以还好不用开高精度,那么这种题目就是开栈去模拟了,因为给出的分子式一定合法,所以最后栈顶的元素就是答案了。
但是注意,它这里给出的原子可能会有个字符的,所以有两种处理办法,一种是把个字符的也看着字符串,下面去拿到哪个字符串去比较,还有一种就是我的做法,新开了一个表,统计这种有个字符的原子,因为它们都有一个特点就是第二个字符一定是小写的,依据这个判断就可成立。
const int N = 1e5 + 7; string s; stack<ll> st; unordered_map<char, int> mp; unordered_map<string, int> mp2; int main() { js; string ch; int num, n, m; cin >> n >> m; for (int i = 1; i <= n; ++i) { cin >> ch >> num; if (ch.size() == 1) mp[ch[0]] = num; else mp2[ch] = num; } while (m--) { while (st.size()) st.pop(); cin >> s; s = "#(" + s + ")#"; st.push(0); int n = s.size(); for (int i = 0; i < n; ++i) { if (s[i] == '(') st.push(0); else if (s[i] == ')') { ll m = st.top(), base = 0; st.pop(); while (i < n and isdigit(s[i + 1])) { base = 10 * base + (s[i + 1] - '0'); ++i; } st.top() += m; if (base) st.top() += m * (base - 1); } else if (isalpha(s[i])) { if (s[i + 1] >= 'a' and s[i + 1] <= 'z') { st.top() += mp2[s.substr(i, 2)]; ++i; } else st.top() += mp[s[i]]; } else if (isdigit(s[i])) { if (s[i - 1] >= 'A' and s[i - 1] <= 'Z') { char ch = s[i - 1]; ll base = 0; while (i < n and isdigit(s[i])) { base = 10 * base + (s[i] - '0'); ++i; } --i; st.top() += (base - 1) * mp[ch]; } else { ll base = 0; while (i < n and isdigit(s[i])) { base = 10 * base + (s[i] - '0'); ++i; } --i; st.top() += (base - 1) * mp2[s.substr(i - 2, 2)]; } } } cout << st.top() << endl; } return 0; }
C、消除整数
发现我们可以减的数是,也就是,很容易想到转换二进制去考虑。
那么对于每次操作,我们只能把减数变大,并且很显然的结论对于每个减数我们一定只会减次或者次,所以我们把写出二进制形式,对于它二进制中每一个我们只需要减掉一次即可,对于它二进制中的每一个我们要减次,如果只减一次,下一次把减数变成两倍,那么这个位置就变成并且永远无法消除。
const int N = 1e6 + 7; int n, m; ll p[N]; void solve() { n = read(); int cnt = 0; m = 1; while (n) { if (n & m) { ++cnt; n -= m; } else { cnt += 2; n -= 2 * m; } m <<= 1; } print(cnt); }
D、消灭星星
给出的地图,星号代表敌人,里面存在种大写字母,每次你做消灭敌人操作都会消灭某一行或者某一列,相对的字母也会被你消灭,问有没有一种方法使得至少存在种字母完全不受影响情况下消灭全部星星。完全不受影响就是这种字母一个都没少的留在了地图上。
根据数据范围我们发现地图中的总字母数都较少,所以我们可以二进制的枚举全部留下的字符,再去遍历地图转化为判定这种方法是不是存在。时间复杂度。
const int N = 20 + 7; ll n, m, k, t; char s[N][N]; bool x[N], y[N], vis[1 << 11]; bool check(int bit) { ms(x, 0); ms(y, 0); rep(i, 0, m) { if (bit >> i & 1) { if (!vis[i]) return 0; rep(j, 0, n - 1) rep(k, 0, n - 1) if (s[j][k] == 'A' + i) x[j] = 1, y[k] = 1; } } rep(i, 0, n - 1) { rep(j, 0, n - 1) { if (s[i][j] == '*' and x[i] and y[j]) return 0; } } return 1; } void solve() { ms(vis, 0); n = read(), m = read(), k = read(); rep(i, 0, n - 1) { scanf("%s", s[i]); rep(j, 0, n - 1) { if (isalpha(s[i][j])) vis[s[i][j] - 'A'] = 1; } } t = (1 << m) - 1; rep(i, 0, t) { if (__builtin_popcount(i) < m - k) continue; // 统计i的二进制表示中1的个数 if (check(i)) { puts("yes"); return; } } puts("no"); }
E、春游
你带领着一共个人,你要把他们全部安排去划船,你可以花费安排一个双人船,你也可以花费安排一个三人船。问安排全部人都有船坐的最小花费是多少?
首先我们可以计算双人船和三人船中每个玩家的花费,我们要在尽可能的情况下选择单价少的船优先安排。
如果,说明我们要先尽可能安排双人船坐满,如果恰好,那就不需要额外安排船,如果剩下一个人,那么就考虑是给他新添一艘船或者拿掉一个,和之前个人一起去坐人船。
如果,说明我们要先尽可能安排三人船坐满,那么与上分类似,我们可能留下三种情况,方便做一次取最值操作即可。
ll n, a, b; void solve() { n = read(); a = read(), b = read(); if (n <= 2) { print(min(a, b)); return; } ll val1 = a / 2, val2 = b / 3; ll ans; if (val1 < val2) { ans = (n / 2) * a; if (n & 1) { ans = min({ ans + min(a,b),ans - a + b }); } } else { ans = (n / 3) * b; if (n % 3 == 1) { ans = min({ ans + min(a,b),ans - b + 2 * a }); } else if (n % 3 == 2) { ans = min({ ans + min(a,b),ans - b + 3 * a }); } } print(ans); }
F、五连珠
模拟题,看谁代码写的简单漂亮了,考虑数组传参写的简单一些。
const int N = 6; struct Node { ll val; int id; bool operator < (const Node& opt) const { return val < opt.val; } }; ll n, m; int a[N][N], b[N][N]; bool check(int c[N][N]) { int cnt = 0; rep(i, 1, 5) { cnt = 0; rep(j, 1, 5) cnt += c[i][j]; // 行 if (!cnt) return 1; cnt = 0; rep(j, 1, 5) cnt += c[j][i]; // 列 if (!cnt) return 1; } cnt = 0; rep(i, 1, 5) cnt += c[i][i]; // 主对角线 if (!cnt) return 1; cnt = 0; rep(i, 1, 5) cnt += c[5 - i + 1][i]; // 副对角线 return cnt == 0; } void solve() { rep(i, 1, 5) rep(j, 1, 5) a[i][j] = read(); rep(i, 1, 5) rep(j, 1, 5) b[i][j] = read(); int ans = INF; rep(_, 1, 25) { int x = read(); if (ans != INF) continue; rep(i, 1, 5) rep(j, 1, 5) if (a[i][j] == x) a[i][j] = 0; rep(i, 1, 5) rep(j, 1, 5) if (b[i][j] == x) b[i][j] = 0; bool flaga = check(a), flagb = check(b); if (flaga and flagb) ans = 0; else if (flaga) ans = 1; else if (flagb) ans = 2; } print(ans); }
G、有始有终
给出的地图,每个格点有一定的高度,你只可以再高度不大于之间的格点自由移动,如果你要跨越一个高度大于的相邻格点那么就要花费一次技能,把一个格点变成电梯这样就可以忽略它和其他全部相邻点的高度差,问从起始点去到终点最少的技能使用次数。
广搜最短路的题目,如果我们把全部可以不用技能走到的点边权看成,走不过去的点边权看作,那么就变成了从起点到终点的最短路了,第一步我们首先得到从起点出发不使用一次技能可以去到哪些点,记录下这些点花费都是,接下来,我们从这些点继续出发去到我们不能去到的点,并把这些点花费记成,这里注意我们选择变成电梯的点一定是我们下一步要走的点,这样我们就可以上去并且再下一步的选择更多,接下来再去从这些变成电梯的点继续往下合并点,直到合并到了终点。
const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} }; const int N = 30 + 7; pai be, ed; #define x first #define y second int n, m, d; int a[N][N]; int dis[N][N], tot; queue<pai> q; pai b[N * N]; void dfs(pai now, bool limit) { rep(i, 0, 3) { int dx = now.x + dir[i][0], dy = now.y + dir[i][1]; if (dx<1 or dx>n or dy<1 or dy>m) continue; if (dis[dx][dy] != -1) continue; if (limit and abs(a[dx][dy] - a[now.x][now.y]) > d) continue; dis[dx][dy] = dis[now.x][now.y]; q.push({ dx,dy }); dfs({ dx,dy }, 1); } } void solve() { ms(dis, -1); while (q.size()) q.pop(); m = read(), n = read(); be.y = read(), be.x = read(); ed.y = read(), ed.x = read(); d = read(); rep(i, 1, n) rep(j, 1, m) a[i][j] = read(); dis[be.x][be.y] = 0; q.push(be); dfs(be, 1); // 把不用技能可以去到的点都走一遍 while (dis[ed.x][ed.y] == -1 and q.size()) { tot = 0; while (q.size()) { b[++tot] = q.front(); q.pop(); } rep(i, 1, tot) { rep(j, 0, 3) { int dx = b[i].x + dir[j][0], dy = b[i].y + dir[j][1]; if (dx<1 or dx>n or dy<1 or dy>m) continue; if (dis[dx][dy] == -1) { q.push({ dx,dy }); dis[dx][dy] = dis[b[i].x][b[i].y] + 1; } } } tot = 0; while (q.size()) { b[++tot] = q.front(); q.pop(); } rep(i, 1, tot) dfs(b[i], 0); } print(dis[ed.x][ed.y]); }
H、匹配矩阵
再次模拟。。直接上代码了吧,仔细一点就可以了。
const int N = 20 + 7; struct Node { ll val; int id; bool operator < (const Node& opt) const { return val < opt.val; } }; ll n, m; char a[N][N], b[4][N][N]; int n1, n2, m1, m2; int calc(char s[N][N], int n2, int m2) { int res = 0; rep(i, 1, n1 - n2 + 1) { rep(j, 1, m1 - m2 + 1) { bool flag = 1; for (int x = 1; flag and x <= n2; ++x) for (int y = 1; flag and y <= m2; ++y) if (s[x][y] != '#' and s[x][y] != a[i + x - 1][j + y - 1]) flag = 0; res += flag; } } return res; } void solve() { n1 = read(), m1 = read(); rep(i, 1, n1) scanf("%s", a[i] + 1); n2 = read(), m2 = read(); rep(i, 1, n2) scanf("%s", b[0][i] + 1); rep(i, 1, n2) rep(j, 1, m2) b[1][j][n2 - i + 1] = b[0][i][j]; rep(i, 1, n2) rep(j, 1, m2) b[2][n2 - i + 1][m2 - j + 1] = b[0][i][j]; rep(i, 1, n2) rep(j, 1, m2) b[3][m2 - j + 1][i] = b[0][i][j]; int ans = calc(b[0], n2, m2); // 0度 ans += calc(b[1], m2, n2); // 90度 ans += calc(b[2], n2, m2); // 180度 ans += calc(b[3], m2, n2); // 270度 print(ans); }
I、螺旋矩阵
题目意思比较简单,实现4个方向的转移可以比较取巧,发现我们在走到端点才要换方向,那么端点都有一个共同的特性,那就是一定有个点没被填写过,起点比较特殊他有个点没被写过,但是不妨碍我们换方向。
下一步我们要往数组左边填数,我们考虑进行坐标平移,考虑一下数据范围的字符串最大也就是,所以我们选择做为我们新的起点依次往下填数即可。
const int dir[][2] = { {0,1},{-1,0},{0,-1},{1,0},{1,1},{1,-1},{-1,1},{-1,-1} }; ll n, m; char s[5007]; char ans[200][200]; void solve() { ms(ans, -1); scanf("%s", s); n = strlen(s); int maxx = 100, minx = 100, maxy = 100, miny = 100; int op = 3; int x = 100, y = 100; rep(i, 0, n - 1) { ans[x][y] = s[i]; maxx = max(maxx, x); maxy = max(maxy, y); minx = min(minx, x); miny = min(miny, y); int cnt = 0; rep(j, 0, 3) { int dx = x + dir[j][0], dy = y + dir[j][1]; if (ans[dx][dy] == -1) ++cnt; } if (cnt >= 3) op = (op + 1) % 4; x += dir[op][0]; y += dir[op][1]; } char p[207]; rep(i, minx, maxx) { rep(j, miny, maxy) if (ans[i][j] == -1) p[j - miny] = ' '; else p[j - miny] = ans[i][j]; p[maxy - miny + 1] = '\0'; puts(p); } } int main() { int T = read(); while (T--) { solve(); if (T) puts(""); } return 0; }
J、统计个数
做法1、直接暴力,枚举全部中间点,依次求解数量即可,因为我们最终要把三角形数量乘,通过转化发现并不需要以外其他操作,时间复杂度做法参考hc唐宇群。
const int N = 200 + 7; ll n, m; bool mp[N][N]; int tri = 0, line = 0; void dfs(int k) { rep(i, 1, n) { if (mp[i][k]) { rep(j, 1, n) { if (k == j) continue; if (mp[i][j]) { ++line; // 一条线 if (mp[k][j]) ++tri; // 一个三角 } } } } } void solve() { ms(mp, 0); n = read(), m = read(); rep(i, 1, m) { int u = read(), v = read(); mp[u][v] = mp[v][u] = 1; } tri = line = 0; rep(i, 1, n) dfs(i); if (!tri) puts("0/1"); else { /* line /= 2; tri /= 6; tri *= 3; */ int tmp = gcd(tri, line); tri /= tmp, line /= tmp; printf("%d/%d\n", tri, line); } }
做法2、优化边权关系,我们发现我们要用去判断每每点之间是不是有关系,这时候就要想起我们卡常大师,用它去优化我们的边权关系,可以在加快查询速度,听说可以把出到。
const int N = 200 + 7; ll n, m; bool mp[N][N]; int du[N]; bitset<N> b[N], p[N]; // b代表图中关系,p保证选取的三角形(a,b,c) a<b<c void solve() { ms(mp, 0); ms(du, 0); n = read(), m = read(); rep(i, 1, n) b[i].reset(); // 清空 rep(i, 1, m) { int u = read(), v = read(); mp[u][v] = mp[v][u] = 1; ++du[u], ++du[v]; b[u][v] = b[v][u] = 1; } int tri = 0, line = 0; rep(i, 1, n) line += du[i] * (du[i] - 1) / 2; // 固定中点,出度中选择2个不同的点 rep(i, 1, n) { rep(j, i + 1, n) { if (mp[i][j]) tri += (b[i] & b[j] & p[j]).count(); } } if (!tri) { puts("0/1"); } else { tri *= 3; int tmp = gcd(tri, line); tri /= tmp, line /= tmp; printf("%d/%d\n", tri, line); } } int main() { rep(i, 1, N - 1) rep(j, i + 1, N - 1) p[i][j] = 1; int T = read(); while (T--) solve(); return 0; }