【题解】2025年“HyperAI杯”天津大学程序设计竞赛(新生赛)
前言
碰巧这几天回学校遇到新生赛,虽然没有奖品,很多其他队员都不想打,但是我瘾大还是报了,顺便看看能不能再捧杯一次。一开始还以为能够靠题数获胜,就打算所有题攒着最后一次性交,上演一波超级反转。但是没带板子被J卡了很久,最后剩两题看着也是不太能做,就直接交了跑路,结果前面的题还有几个写挂了,罚时也大爆炸,大输特输,如果换到一年前的我,正常开题是不是结果会不一样呢,春风若有怜花意,可否许我再少年。
A.郑东图书馆的搬书日
思路
二分经典题,直接二分答案,check的时候贪心每一段,看最后的总段数是否小等于 \(m\)
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e6 + 10;
const int mod = 998244353;
int arr[maxn], n, m;
int ck(int mid) {
int now = 0, duan = 1;
for (int i = 1; i <= n; i++) {
now += arr[i];
if (now > mid) {
now = arr[i];
duan++;
}
}
return duan <= m;
}
void solve() {
cin >> n >> m;
int mx = 0;
for (int i = 1; i <= n; i++) cin >> arr[i], mx = max(mx, arr[i]);
int l = mx, r = 1e10, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (ck(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
B.二元运算
思路
看题面明显是线段树的题,主要分析一下运算 \(f\) 的性质。发现先异或 \(a\) 再循环右移 \(b\) 等价于先循环右移 \(b\) 后再异或上跟着循环右移的 \(a\),所以这种运算我们是可以推出结合关系的。或者说这种线段树的题肯定是需要结合律,主要去分析出如何结合,可以假设有一个 \(a_1\) \(b_1\)之后再算 \(a_2\) \(b_2\),这样会等价于直接如何操作,推出来后就直接暴力码线段树即可。
代码
#include<bits/stdc++.h>
#define int unsigned int
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=3e5 + 10;
const int mod = 998244353;
int cal(int y, int k) {
int res = (y << k) | (y >> (32 - k));
return res;
}
int aa[maxn], bb[maxn];
struct Node {
int sum = 0, yihuo = 0;
};
struct seg {
#define ls rt << 1
#define rs rt << 1 | 1
#define mid ((l + r) >> 1)
Node t[maxn << 2];
Node Merge(Node a, Node b) {
Node res;
res.sum = (a.sum + b.sum) % 32;
res.yihuo = cal(a.yihuo, b.sum) ^ b.yihuo;
return res;
}
void push_up(int rt) {
t[rt] = Merge(t[ls], t[rs]);
}
void build(int rt, int l, int r) {
if (l == r) {
t[rt].yihuo = cal(aa[l], bb[l]), t[rt].sum = bb[l];
return;
}
build(ls, l, mid), build(rs, mid + 1, r);
push_up(rt);
}
void modify(int rt, int l, int r, int pos, int a, int b) {
if (l == r) {
t[rt].yihuo = cal(a, b), t[rt].sum = b;
return;
}
if (pos <= mid) modify(ls, l, mid, pos, a, b);
else modify(rs, mid + 1, r, pos, a, b);
push_up(rt);
}
Node query(int rt, int l, int r, int p, int q) {
if (p <= l && r <= q) return t[rt];
if (q <= mid) return query(ls, l, mid, p, q);
else if (p > mid) return query(rs, mid + 1, r, p, q);
else return Merge(query(ls, l, mid, p, mid), query(rs, mid + 1, r, mid + 1, q));
}
} seg;
void solve() {
int n, q; cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> aa[i];
for (int i = 1; i <= n; i++) cin >> bb[i];
seg.build(1, 1, n);
while (q--) {
int op; cin >> op;
if (op == 1) {
int pos, a, b; cin >> pos >> a >> b;
seg.modify(1, 1, n, pos, a, b);
} else {
int l, r, x; cin >> l >> r >> x;
Node tem = seg.query(1, 1, n, l, r);
// cout << tem.sum << " " << tem.yihuo << "\n";
int res = cal(x, tem.sum) ^ tem.yihuo;
cout << res << "\n";
}
}
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
C.北洋园的位运算挑战
思路
按位与有一个性质就是只会减小,这一点题目里面也强调了。所以考虑最终答案两个集合,大的那个肯定是只有一个数,因为添加更多的数只会让按位与的结果变小,另一个集合就是剩下所有数。所以直接枚举单个数,计算剩下数的按位与结果,遍历一下取个最值。这里可以利用按位与的结合性,用前后缀按位与数组来快速计算剩下所有数的按位与。(这题一开始没想到,以为肯定单个数就是最大值,但是这样不能保证最小值,有可能选第二个大的数,然后最大数跟其他的与完让最小值很小,导致怒wa n发)
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e6 + 10;
const int mod = 998244353;
int arr[maxn], pre[maxn], suf[maxn];
void solve() {
int n; cin >> n;
for (int i = 1; i <= n; i++) {
cin >> arr[i], pre[i] = suf[i] = arr[i];
}
for (int i = 2; i <= n; i++) pre[i] &= pre[i - 1];
for (int i = n - 1; i >= 1; i--) suf[i] &= suf[i + 1];
int ans = max(abs(arr[1] - suf[2]), abs(arr[n] - pre[n - 1]));
for (int i = 2; i < n; i++) {
ans = max(ans, abs(arr[i] - (pre[i - 1] & suf[i + 1])));
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
D.若叶睦的黄瓜田
思路
因为浇水的顺序不重要,所以可以从左往右贪心,对于第一格,如果需要浇,一定只能浇在它右边那格,然后按顺序模拟一遍浇水结果即可。
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=2e5 + 10;
const int mod = 998244353;
int arr[maxn];
void solve() {
int n; cin >> n;
for (int i = 1; i <= n; i++) cin >> arr[i];
for (int i = 1; i <= n - 2; i++) {
if (arr[i] <= 0) continue;
arr[i + 1] -= 2 * arr[i];
arr[i + 2] -= arr[i];
arr[i] = 0;
}
for (int i = 1; i <= n; i++)
if (arr[i] != 0) {
cout << "no\n";
return;
}
cout << "yes\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
E.字符串
思路
可以画图画一下补全后作为回文串的结果,容易发现结论就是要找原串的最长回文后缀,然后补全剩下的前缀。就可以用马拉车,哈希,PAM等各种办法找最长回文后缀。而且最长回文后缀根据定义就是PAM里面extend到最后一个字符时候对应位置的 \(len[i]\),不过比赛不让带板子,所以最后还是哈希过去了。
代码一(PAM)
#include <bits/stdc++.h>
// #define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int, int>
#define tii tuple<int, int, int>
#define db double
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 1e6 + 10;
const int mod = 998244353;
struct PAM {
int tot, last, fail[maxn], c[maxn][27], len[maxn], cnt[maxn];
char s[maxn]; // 下标从1开始的字符串
PAM() { tot = last = fail[0] = 1, len[1] = -1; }
int getfail(int x, int i) {
while (s[i - len[x] - 1] != s[i])
x = fail[x];
return x;
}
void extend(char ch, int i) { // 插入位置 i 的字符 ch
s[i] = ch;
int x = getfail(last, i), v = ch - 'a';
if (!c[x][v]) {
len[++tot] = len[x] + 2;
int tem = getfail(fail[x], i);
fail[tot] = c[tem][v];
c[x][v] = tot; // 必须放最后
cnt[tot] = cnt[fail[tot]] + 1;
}
last = c[x][v];
}
} pam;
void solve() {
int n;
cin >> n;
string str;
cin >> str;
for (int i = 1; i <= n; i++) {
pam.extend(str[i - 1], i);
}
int ans = n - pam.len[pam.last];
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
代码二(哈希)
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e6 + 10;
const int mod = 998244353;
unsigned long long base = 131;
unsigned long long hash1[maxn], hash2[maxn];
unsigned long long bei[maxn];
unsigned long long get_hash(unsigned long long hash[], int l, int r) {
return hash[r] - hash[l - 1] * bei[r - l + 1];
}
void solve() {
int n; cin >> n;
string str; cin >> str;
bei[0] = 1;
for (int i = 1; i < maxn; i++) bei[i] = bei[i - 1] * base;
for (int i = 1; i <= n; i++) hash1[i] = hash1[i - 1] * base + str[i - 1];
for (int i = 1; i <= n; i++) hash2[i] = hash2[i - 1] * base + str[n - i];
// cout << get_hash(hash1, 3, 3) << "\n";
for (int i = n; i >= 1; i--) {
unsigned long long tem1 = get_hash(hash1, n - i + 1, n), tem2 = get_hash(hash2, 1, i);
// cout << tem1 << " " << tem2 << "\n";
if (tem1 == tem2) {
cout << n - i << "\n";
return;
}
}
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
F.子串数目
思路
分析一下,发现包含1的字串情况太复杂,所以转而求总子串数量减去只包含0的子串数量。去推理一下怎么安排0的数量能让只包含0的子串数量最少,从而发现应该让0的连续段尽可能均匀。所以根据0、1的数量,推一下总的有几个连续0,除一下算一下就好。
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e5 + 10;
const int mod = 998244353;
int cal(int x) {
return x * (x + 1) / 2;
}
void solve() {
int n, m; cin >> n >> m;
int tot = cal(n);
int cnt0 = n - m, cnt1 = m;
int tem = cnt0 / (cnt1 + 1);
int tem2 = cnt0 % (cnt1 + 1);
int res = tot - tem2 * cal(tem + 1) - (cnt1 + 1 - tem2) * cal(tem);
cout << res << "\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
G.博士的无理改造
思路
差分模板题,一开始没注意数据范围,想写vector形式的了,最后发现可以直接用数组,有点简单了。 bonus:如果时间范围改成 \(10^9\) ,并统计超载的总时间,该怎么做?
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=2e5 + 10;
const int mod = 998244353;
int sub[maxn];
void solve() {
int n, w; cin >> n >> w;
for (int i = 1; i <= n; i++) {
int s, t, p; cin >> s >> t >> p;
sub[s] += p;
sub[t] -= p;
}
for (int i = 1; i < maxn; i++) sub[i] += sub[i - 1];
int ok = 1;
for (int i = 1; i < maxn; i++) ok &= sub[i] <= w;
if (ok) cout << "Yes\n";
else cout << "No\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
I.北洋园的计算器
思路
签到,没什么好说的,分类讨论一下,不知道py是不是有函数可以直接计算表达式来着
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e5 + 10;
const int mod = 998244353;
void solve() {
int a, b; char ch;
cin >> a >> ch >> b;
if (ch == '+') cout << a + b << "\n";
else if (ch == '-') cout << a - b << "\n";
else cout << a * b << "\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
J.最爱的古钱币
思路
题面询问都是针对子树,且可以离线,一眼 dsu on tree模板题,套dsu on tree模板主要考虑增加删除一个点的贡献,以及查询的时候怎么处理。考虑对于答案维护一个桶,表示数量为 \(i\) 的颜色目前有 \(tong[i]\) 个,那么对于一个询问,就是求这个桶的某个后缀和,所以需要单点修改,区间查询的桶,也就是再套一个树状数组。增加删除一个点的贡献也就是在树状数组里面单点加一减一啥的,难度在于不让带板子,太久没写忘记咋写了,最后磨了半天才把记忆里的板子拼凑出来。
代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn=1e5 + 10;
const int mod = 998244353;
vector<int> G[maxn];
int dfn[maxn], mp[maxn], col[maxn], ncnt, ans[maxn], L[maxn], R[maxn], son[maxn], siz[maxn], cnt[maxn];
vector<pii> query[maxn];
void dfs0(int u, int f) {
L[u] = ++ncnt, mp[ncnt] = u;
siz[u] = 1;
for (auto v : G[u]) {
if (v == f) continue;
dfs0(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
R[u] = ncnt;
}
struct BIT {
int t[maxn];
int lowbit(int x) {
return x & (-x);
}
void update(int i, int x) {
for (int pos = i; pos < maxn; pos += lowbit(pos))
t[pos] += x;
}
int query(int i) {
int res = 0;
for (int pos = i; pos; pos -= lowbit(pos))
res += t[pos];
return res;
}
} bit;
int ask(int k) {
return bit.query(1e5) - bit.query(k - 1);
}
void add(int u) {
if (cnt[col[u]])
bit.update(cnt[col[u]], -1);
cnt[col[u]]++;
bit.update(cnt[col[u]], 1);
}
void del(int u) {
bit.update(cnt[col[u]], -1);
cnt[col[u]]--;
if (cnt[col[u]])
bit.update(cnt[col[u]], 1);
}
void dfs(int u, int f, int keep) {
for (auto v : G[u])
if (v != son[u] && v != f)
dfs(v, u, 0);
if (son[u])
dfs(son[u], u, 1);
for (auto v : G[u])
if (v != son[u] && v != f)
dfs(v, u, 1);
add(u);
for (auto it : query[u]) {
int id = it.first, k = it.second;
ans[id] = ask(k);
}
if (!keep) {
for (int i = L[u]; i <= R[u]; i++)
del(mp[i]);
}
}
void solve() {
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> col[i];
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
for (int i = 1; i <= m; i++) {
int v, k; cin >> v >> k;
ans[i] = -1;
query[v].push_back(pii{i, k});
}
dfs0(1, 0);
dfs(1, 0, 0);
for (int i = 1; i <= m; i++) cout << ans[i] << "\n";
}
/*
3 1
1 1 1
1 2
1 3
1 3
2 1
1 1
1 2
1 2
*/
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}

【题解】2025年“HyperAI杯”天津大学程序设计竞赛(新生赛)
浙公网安备 33010602011771号