【题解】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;
} 
posted @ 2025-12-28 13:08  TJUHuangTao  阅读(6)  评论(0)    收藏  举报