2025牛客寒假算法基础集训营2
题目链接:2025牛客寒假算法基础集训营2
总结:排名:113 10题 803罚时
- 分层图最短路
A. 一起奏响历史之音!
tag:语法
void solve(){ set<int> st; st.insert(1); st.insert(2); st.insert(3); st.insert(5); st.insert(6); for (int i = 0; i < 7; i ++){ int x; cin >> x; if (st.find(x) == st.end()){ cout << "NO\n"; return; } } cout << "YES\n"; }
B. 能去你家蹭口饭吃吗
tag:中位数
void solve(){ int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i ++){ cin >> a[i]; } sort(a.begin(), a.end()); cout << a[n / 2] - 1 << endl; }
C. 字符串外串
tag:构造
Description:给定一个n和m,构造一个长度为n,可爱度为m的字符串。如果不能输出-1。
Solution:从左往右构造先构造长度为m的串,然后让第m个字母在末尾出现,那么剩余n - m个字母不能出现两次,因此n - m <= 26。
- 但是为了避免从右往左看可爱度过大,我们需要周期性的构造。
void solve(){ int n, m; cin >> n >> m; if (n == m || n > m + 26) { cout << "NO\n"; } cout << "YES\n"; int now = -1; for (int i = 0; i < m; i++){ now = (now + 1) % 26; char c = 'a' + now; cout << c; } char last = 'a' + now; for (int i = m; i + 1 < n; i++){ now = (now + 1) % 26; char c = 'a' + now; cout << c; } cout << last; cout << "\n"; }
D. 字符串里串
tag:思维
Description:定义可爱度为一个最大的整数k,使得存在长度为a的连续子串、长度为b的不连续子序列,满足a == b。给定一个字符串,求该字符串的可爱度。不连续子序列至少由两段不相邻的非空子串构成。
Solution:仔细观察一下,在确定a之后,b最好的选择是在a第一个字符前面找一个和a[1]一样的字符或者在a的最后一个字符后面找一个和a[n]一样的字符。
- 那么就变成了找每一个字符出现两次时,串的最大长度。
void solve(){ int n; cin >> n; string s; cin >> s; s = '$' + s; set<int> st; int ans = 0; for (int i = 1; i <= n; i ++){ // 从前往后 if (st.find(s[i]) != st.end()){ ans = max(ans, n - i + 1); } st.insert(s[i]); } st.clear(); for (int i = n; i >= 1; i --){ // 从后往前 if (st.find(s[i]) != st.end()){ ans = max(ans, i); } st.insert(s[i]); } if (ans == 1) // b的长度至少为2 ans = 0; cout << ans << endl; }
E. 一起走很长的路!
tag:前缀和,st表
Description:给定一个长度为n的数组,给定k次询问,每次询问给定l,r需要给出操作次数,每次操作可以在一个位置进行+1或者-1操作。
- 每次操作时:推倒a[l],如果a[l] >= a[l + 1],则推倒a[l + 1],如果a[l] + a[l + 1] >= a[l + 2],则推倒a[l + 2]...,需要给出最少操作多少次能推倒给定区间的所有牌。
Solution:显然+1操作比-1操作好,显然将+1操作放在最前面好。
- 一个数想要被推到需要前缀和大于该数。考虑记录每个数减去其前缀和,那么需要的操作等于区间最大值再加上一段前缀和(该段前缀和并不影响区间最大值)。
int st[N][21]; int n, k; void init() { for (int j = 1; j <= 20; j ++){ for (int i = 1; i + (1 << j) - 1 <= n; i ++) { st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } } } int query(int l, int r){ int k = log2(r - l + 1); return max(st[l][k], st[r - (1 << k) + 1][k]); } void solve(){ cin >> n >> k; vector<int> a(n + 1), s(n + 1); for (int i = 1; i <= n; i ++) { cin >> a[i]; s[i] = s[i - 1] + a[i]; st[i][0] = a[i] - s[i - 1]; } init(); while (k --){ int l, r; cin >> l >> r; if (l == r) { cout << 0 << endl; continue; } else{ // 最大值询问从l + 1开始,因为a[l]一开始就被推倒 cout << max(0ll, s[l - 1] + query(l + 1, r)) << endl; } } }
F. 一起找神秘的数!
tag:打表
Solution:打表看x + y = (x or y) + (x and y)+ (x xor y)
的数的性质,发现当x == y时,才成立。
void solve(){ int l, r; cin >> l >> r; cout << r - l + 1 << endl; for (int i = 0; i < 1000; i ++) for (int j = i; j < 1000; j ++){ if (i + j == (i | j) + (i & j) + (i ^ j)){ debug(i, j); } } }
G. 一起铸最好的剑!
tag:模拟
void solve(){ int n, m; cin >> n >> m; int t = m; int ans = 1; int mi = abs(n - m); while (1){ if (mi > abs(n - m * t)){ ans ++; m *= t; mi = abs(n - m); } else break; } cout << ans << endl; }
H. 一起画很大的圆!
tag:数学
Description:给定一个矩形区域,在其中找出三个整数点,使得过这三个点画出的圆的半斤最大。
Solution:三个不共线的点可以确定一个圆,如果三点越接近一条直线,这个圆最大。
- 猜一下一个点在边界,那么另外两个点就很好猜了。在长边上选一个与边界最近的点,在另一个条边上选一个离长边最近的点。
void solve(){ int a, b, c, d; cin >> a >> b >> c >> d; if (b - a > d - c){ cout << a << " " << d << endl; cout << a + 1 << " " << d << endl; cout << b << " " << d - 1 << endl; } else{ cout << a << " " << d << endl; cout << a << " " << d - 1 << endl; cout << a + 1 << " " << c << endl; } }
I. 一起看很美的日落!
tag:树形DP
Description:给定一颗n个节点的树,每个节点的权值为
Solution:求异或和可以转化为按位求每一个01的个数。
dp[u]
:包含u这个节点的子树的所有答案。g[u]
:包含u这个节点的子树的连通块个数。f[u][0/1]:
保护u这个节点的0的贡献次数和1的贡献次数。g[u] = g[u] + g[u] * g[v]
。f[u][0/1] = f[u][0/1] + f[u][0/1] * g[v] + f[v][0/1] * g[u]
。dp[u] = dp[u] + dp[u] * g[v] + dp[v] * g[u] + f[u][0/1] * f[v][1/0] + f[v][0/1] * f[u][1/0]
。
const int N = 1e5 + 10; int n; int dp[N], g[N]; int f[N][31][2]; void solve(){ cin >> n; vector e(n + 1, vector<int>()); vector<int> a(n + 1); for (int i = 0; i < n; i ++) { cin >> a[i + 1]; } for (int i = 0; i < n - 1; i ++) { int u, v; cin >> u >> v; e[u].pb(v), e[v].pb(u); } int ans = 0; function<void(int, int)> dfs = [&](int u, int fa) { for (int i = 0; i < 31; i ++) { if ((a[u] >> i) & 1) { f[u][i][1] = 1; } else f[u][i][0] = 1; } g[u] = 1; for (auto v : e[u]) { if (v == fa) continue; dfs(v, u); // 先计算好儿子的答案 dp[u] = (dp[u] + dp[u] * g[v] % mod + dp[v] * g[u] % mod) % mod; for (int i = 0; i < 30; i ++) { dp[u] = (dp[u] + (1ll << i) * f[u][i][0] % mod * f[v][i][1] % mod + (1ll << i) * f[v][i][0] % mod * f[u][i][1] % mod) % mod; f[u][i][0] = (f[u][i][0] + f[u][i][0] * g[v] % mod + f[v][i][0] * g[u] % mod) % mod; f[u][i][1] = (f[u][i][1] + f[u][i][1] * g[v] % mod + f[v][i][1] * g[u] % mod) % mod; if (i == 0 || i == 1) { debug(i, u, f[u][i][0], f[u][i][1]); } } g[u] = (g[u] + g[u] * g[v]) % mod; } ans = (ans + dp[u]) % mod; }; dfs(1, 0); cout << (ans * 2) % mod << endl; }
J. 数据时间?
tag:模拟
Solution:注意观察细节,可以减少很多码量。
void solve(){ int n; string h, m; cin >> n >> h >> m; if (m.size() == 1){ m = '0' + m; } string x, y, z; map<string, int> cnt1, cnt2, cnt3; for (int i = 0; i < n; i++){ cin >> x >> y >> z; if (y.substr(0, 4) != h) continue; if (y.substr(5, 2) != m) continue; string z2 = z.substr(0, 2); if (z2 == "07" || z2 == "08" || z == "09:00:00" || z2 == "18" || z2 == "19" || z == "20:00:00"){//cnt1 cnt1[x]++; }else if (z2 == "11" || z2 == "12" || z == "13:00:00"){ cnt2[x]++; }else if (z2 == "22" || z2 == "23" || z2 == "00" || z == "01:00:00"){ cnt3[x]++; } } cout << cnt1.size() << " " << cnt2.size() << " " << cnt3.size() << "\n"; }
K. 可以分开吗?
tag:dfs
Solution:dfs找出所有连通块,同时将连通块周围的灰块放入set去重。
Competing:将字符串当做整数读了(o(╥﹏╥)o)。
int dir[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; void solve(){ int n, m; cin >> n >> m; vector a(n + 1, vector<pii>(m + 1)); for (int i = 1; i <= n; i ++){ string s; cin >> s; s = '$' + s; for (int j = 1; j <= m; j ++){ a[i][j].fi = s[j] - '0'; } } int ans = n * m; set<pii> st; function<void(int, int)> dfs = [&](int x, int y){ for (int i = 0; i < 4; i ++){ int tx = x + dir[i][0], ty = y + dir[i][1]; if (tx < 1 || tx > n || ty < 1 || ty > m || a[tx][ty].se != 0) continue; if (a[tx][ty].fi == 0){ st.insert({tx, ty}); continue; } a[tx][ty].se = 1; dfs(tx, ty); } }; for (int i = 1; i <= n; i ++){ for (int j = 1; j <= m; j ++){ if (a[i][j].fi == 1 && a[i][j].se == 0){ st.clear(); dfs(i, j); ans = min(ans, (int)st.size()); } } } cout << ans << endl; }
M. 那是我们的影子
tag:计数 + 组合数学
Description:给定一个3 * n的表格,里面有一些数字和一些问号,你可以在问号处填任意数字(0-9),问满足任意3 * 3的表格都是数独的方案数?。
Solution:模拟一下发现j mod 3相同的列里面填的数字一定相同(顺序可以不一样)。
- 一个列集出现的数字大于3个不合法。
- 一个数字出现在多个列集不合法。
- 一列出现相同的数字不合法。
- 对于合法的状态:
- 当前还剩n个数字可用,第一列集还可以填
ne[0] = 3 - st[0].size()
个数,......。 - 那么方案数为
。 - 一列有x个问号,代表着x个数的顺序可以任意交换,即
。
- 当前还剩n个数字可用,第一列集还可以填
struct Comb{ int n, mod; Comb(int n, int mod){ this->n = n; this->mod = mod; init(n); }; LL qmi(LL a, LL k, LL mod){ LL res = 1; while (k){ if (k & 1) res = res * a % mod; k >>= 1; a = a * a % mod; } return res; } vector<LL> fact, infact, inv; void init(int n){ fact.resize(n + 1); // 存储阶乘 infact.resize(n + 1); // 存储阶乘的逆元 inv.resize(n + 1); // 存储自然数的逆元 fact[0] = infact[0] = inv[1] = 1; for (int i = 1; i <= n; i ++) fact[i] = fact[i - 1] * i % mod; infact[n] = qmi(fact[n], mod - 2, mod); for (int i = n - 1; ~i; i --) infact[i] = infact[i + 1] * (i + 1) % mod; for (int i = 2; i <= n; i ++) inv[i] = (mod - mod / i) * inv[mod % i] % mod; } LL C(LL n, LL m){ if (m < 0 || n - m < 0) return 0; return fact[n] * infact[m] % mod * infact[n - m] % mod; } LL A(LL n, LL m){ if (n < m || m < 0) return 0; return fact[n] * infact[n - m] % mod; } }; void solve(){ int n; cin >> n; Comb comb(20, mod); vector g(4, vector<char>(n + 1)); set<int> st[3]; for (int i = 1; i <= 3; i ++) for (int j = 1; j <= n; j ++){ cin >> g[i][j]; if (g[i][j] == '?') continue; st[j % 3].insert(g[i][j] - '0'); } if (st[0].size() > 3 || st[1].size() > 3 || st[2].size() > 3){ // 某一列集出现3个以上的数 cout << 0 << endl; return; } for (int i = 1; i <= 9; i ++){ bool flag = false; for (int j = 0; j < 3; j ++){ if (st[j].find(i) != st[j].end() && flag){ // 不同列集包含同一个数 cout << 0 << endl; return; } if (st[j].find(i) != st[j].end()){ flag = true; } } } for (int i = 1; i <= n; i ++) { vector<int> cnt(10); for (int j = 1; j <= 3; j ++) { // 同一个数在同一列出现两次 if (g[j][i] == '?') continue; cnt[g[j][i] - '0'] ++; if (cnt[g[j][i] - '0'] > 1){ cout << 0 << endl; return; } } } vector<int> f(n + 1); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= 3; j ++) { // 每一列有多少个问号 if (g[j][i] == '?') { f[i] ++; } } vector<int> ne(3); // 每一列还剩多少数可以用 int now_use = 0; // 一共还有多少数可以用 for (int j = 0; j < 3; j ++) { ne[j] = 3 - st[j].size(); now_use += ne[j]; } // 每一列能够填的数字是哪些 int ans = comb.C(now_use, ne[0]) * comb.C(now_use - ne[0], ne[1]) % mod; for (int i = 1; i <= n; i ++) // 一列选择的数确定了,那么有几个问号代表可以交换它们的位置 ans = ans * comb.A(f[i], f[i]) % mod; cout << ans << endl; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!