2025“钉耙编程”中国大学生算法设计暑期联赛(2)02/06/08/09/12
个人做题顺序/大致按照难度排序
1. 1002 数上的图
知识点:二进制拆分,二进制相关函数
如果这两个数相等,则零次转化
如果这两个数的 \(count\) 或 \(lowbit\) 相等,则可以一次转化过去
否则,剩下的任何情况,一定可以从两种方式之一转换过去:
- \(x\) 先转换为 \(count(y)\) ,再转换为 \(y\)
- \(x\) 先转换为 \(lowbit(y)\) ,再转换为 \(y\)
其中 \(lowbit\) 函数需要手写,固定写法。
\(count\) 函数:__builtin_popcountll (对应long long)或 __builtin_popcount (对应int) 本题需要用 ll
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
const ll inf = 1e18;
const int mod = 998244353;
int lowbit(int x){
return x&-x;
}
void solve(){
int n,x,y;
cin>>n>>x>>y;
if(x==y){
cout<<0<<endl;
return;
}
if(__builtin_popcountll(x)==__builtin_popcountll(y) || lowbit(x)==lowbit(y)){
cout<<1<<endl;
}
else{
cout<<2<<endl;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
2. 1008 井
知识点:数学期望,概率
首先,需要找到任一被选中的格子,最优的方式是沿着对角线一个一个找。这一步最少找 \(1\) 次,最多找 \(n\) 次,期望是 \((1+n) / 2\)
找到任一格子后,还需要确定是行还是列,最优方式是翻开这一格子上面的和左面的,期望是 \((1+2)/2\)
剩下 \(n-2\) 个格子已经确定,期望是 \((n-2)\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
double n;
cin>>n;
double ans=(1+n)/2+1.5+n-2;
printf("%.4lf\n",ans);
}
signed main(){
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
3. 1006半
知识点:树状数组
假设第 \(i\) 个人的排名分别是 \(a_i,b_i\)
则答案为:\(a_i\) 前面的人数 + \(b_i\) 前面的人数 - 两部分共有的人数
我们从前往后枚举 \(a\) 数组,并将 \(a[1] ~到~ a[i-1]\) 所有人在 \(b\) 数组中的排名放进树状数组。
枚举到 \(a[i]\) 时查询树状数组中有多少个位于 \(1 ~到~ rkb[i]\) 的元素即可
#include<bits/stdc++.h>
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
struct TreeArray {
int n;
vector<ll> tr;
TreeArray(int _n) { init(_n); }
TreeArray() {}
// 初始化,开辟大小为 n+1 的数组,下标从 1 开始使用
void init(int _n) {
n = _n;
tr.assign(n + 1, 0);
}
// lowbit 函数,获取最低位的 1
inline int lowbit(int x) {
return x & -x;
}
// 在下标 x 增加值 c
void insert(int x, ll c) {
for (int i = x; i <= n; i += lowbit(i)) {
tr[i] += c;
}
}
// 获取前缀和 [1..x]
ll sum(int x) {
ll ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
ans += tr[i];
}
return ans;
}
ll query(int l,int r){
return sum(r)-sum(l-1);
}
// 二分查找:返回最大的 x,使得 sum(x) <= k
// 如果所有前缀和都大于 k,则返回 0
int select(ll k) const {
int x = 0;
ll cur = 0;
// i 从最高 2^⌊lg n⌋ 开始
for (int i = 1 << (31 - __builtin_clz(n)); i > 0; i >>= 1) {
if (x + i <= n && cur + tr[x + i] <= k) {
x += i;
cur += tr[x];
}
}
return x;
}
};
void solve(){
int n;
cin>>n;
vector<int> a(n+1),b(n+1);
vector<int> cnt(n+1);
vector<int> pa(n+1),pb(n+1);
vector<int> ans(n+1);
int now=0;
TreeArray tr(n);
for(int i=1;i<=n;i++){
cin>>a[i];
pa[a[i]]=i;
}
for(int i=1;i<=n;i++){
cin>>b[i];
pb[b[i]]=i;
}
for(int i=1;i<=n;i++){
ans[i]=pa[i]-1+pb[i]-1;
}
for(int ra=1;ra<=n;ra++){
int id=a[ra];
int rb=pb[id];
ans[id]-=tr.query(1,rb);
tr.insert(rb,1);
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<" ";
}
cout<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
4. 1009 苹果树
知识点:树链剖分,线段树
做本题需要掌握树链剖分的性质。
对于查询操作,显然要使用树链剖分
对于修改操作,如果暴力修改,复杂度显然不可接受
考虑对点 \(u\) 修改时,会被影响的点可以分成三类:
- \(fa[u]\)
- \(son[u]\) (\(u\) 的重子节点)
- \(u\) 的轻子节点
对于1,2, 在每次修改是可以 O(1) 的直接修改,而对于轻子节点,根据重链剖分的性质,每个轻子节点都是一条重链的头节点。
而熟练剖分在查询时,最多会经过 log(n) 条重链。所以对于这些轻子节点,我们可以在查询经过重链头节点时,顺便计算出来。
同时因为点权只增不减,所以可以无脑取 MAX。
注意查询操作计算重链头节点时不能修改原值,因为会和其他修改/查询操作冲突。
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
class SegmentTree{
public:
#define lc u<<1
#define rc u<<1|1
struct Node{
int l,r,mx;
int add;
};
int n;
vector<int> w;
vector<Node> tr;
SegmentTree(int n){
init(n);
}
void init(int n){
this->n=n;
w.resize(n+10);
tr.resize(4*n+10);
}
void pushup(int u){
tr[u].mx=max(tr[rc].mx,tr[lc].mx);
}
void pushdown(int u){
if(tr[u].add){
tr[rc].add+=tr[u].add;
tr[lc].add+=tr[u].add;
tr[rc].mx+=tr[u].add;
tr[lc].mx+=tr[u].add;
tr[u].add=0;
}
return;
}
void build(int u,int l,int r){
if(l==r) tr[u]={l,r,w[r],0};
else{
tr[u]={l,r};
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(u);
}
}
int query(int u,int l,int r){
if(l<=tr[u].l && r>=tr[u].r) return tr[u].mx;
else{
pushdown(u);
int mx=0,mid=tr[u].l+tr[u].r>>1;
if(l<=mid) mx=query(lc,l,r);
if(r>mid) mx=max(mx,query(rc,l,r));
return mx;
}
}
void modify(int u,int l,int r,int k){
if(l<=tr[u].l && r>=tr[u].r){
tr[u].mx+=k;
tr[u].add+=k;
}
else{
pushdown(u);
int mid=tr[u].r+tr[u].l>>1;
if(l<=mid) modify(lc,l,r,k);
if(r>mid) modify(rc,l,r,k);
pushup(u);
}
}
};
struct HLD {
int n, root;
vector<vector<int>> adj; // 邻接表
vector<int> sz, dep, fa, son; // 子树大小、深度、父节点、重儿子
vector<int> top, id, rev, seq, w; // 链顶、DFN编号、逆向映射、权值线性化数组、原权值
int timer;
SegmentTree seg;
vector<int> tag;
// 构造函数,传入节点数
HLD(int _n = 0) : n(_n), seg(_n) {
adj.assign(n+1, {});
sz.assign(n+1, 0);
dep.assign(n+1, 0);
fa.assign(n+1, 0);
son.assign(n+1, 0);
top.assign(n+1, 0);
id.assign(n+1, 0);
rev.assign(n+1, 0);
seq.assign(n+1, 0);
w.assign(n+1, 0);
tag.assign(n+1,0);
timer = 0;
}
// 添加一条无向边 u-v
void addEdge(int u, int v){
adj[u].push_back(v);
adj[v].push_back(u);
}
// 设置节点 u 的初始权值
void setWeight(int u, int v){ w[u] = v; }
/**
* dfs1:计算每个节点的子树大小和重儿子
* u:当前节点,p:父节点
*/
void dfs1(int u, int p){
fa[u] = p;
dep[u] = dep[p] + 1;
sz[u] = 1;
son[u] = 0;
for (int v : adj[u]) if (v != p) {
dfs1(v, u);
sz[u] += sz[v];
// 选出最大的子树作为重儿子
if (!son[u] || sz[v] > sz[son[u]]) son[u] = v;
}
}
/**
* dfs2:沿重链深入,分配 DF
* u:当前节点,t:当前链顶节点
*/
void dfs2(int u, int t){
top[u] = t;
id[u] = ++timer;
rev[timer] = u;
seq[timer] = w[u];
// 先遍历重儿子,保持链上连续
if (son[u]) dfs2(son[u], t);
// 再遍历其他轻儿子,各自开新链
for (int v : adj[u]) if (v != fa[u] && v != son[u]) {
dfs2(v, v);
}
}
/**
* build:完成树链剖分并建线段树
* _root:指定根节点编号
*/
void build(int _root){
root = _root;
dep[0] = 0;
dfs1(root, 0);
dfs2(root, root);
// 将线性化后的权值拷贝给线段树
for (int i = 1; i <= n; i++) seg.w[i] = seq[i];
seg.build(1, 1, n);
}
/**
* modifyPath:对路径 u-v 上的所有节点加上 k
*/
void modifyPath(int u, int v, int k){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]]) swap(u, v);
seg.modify(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
seg.modify(1, id[v], id[u], k);
}
/**
* queryPath:查询路径 u-v 上所有节点的权值和
*/
int queryPath(int u, int v) {
int res = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res = max(res, seg.query(1, id[top[u]], id[u]));
if (fa[top[u]]) { // fa[root] is 0
res = max(res, seg.query(1, id[top[u]], id[top[u]]) + tag[fa[top[u]]]);
}
u = fa[top[u]];
}
// 此刻 u 和 v 在同一条链上
if (dep[u] < dep[v]) swap(u, v);
// 3) 查询最后一条链上的区间最大值
res = max(res, seg.query(1, id[v], id[u]));
//!!!!!!!!!!! 单独修正 LCA 节点(v)的值,因为它可能是一个未被修正过的链顶
if (top[v] == v && fa[v] != 0) {
res = max(res, seg.query(1, id[v], id[v]) + tag[fa[v]]);
}
return res;
}
};
void solve(){
int n,m;
cin>>n>>m;
HLD tr(n);
int b=sqrt(n)+1;
for(int i=1;i<=n;i++){
int x;
cin>>x;
tr.setWeight(i,x);
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
tr.addEdge(u,v);
}
tr.build(1);
while(m--){
int op;
cin>>op;
if(op==1){
int x,y;
cin>>x>>y;
cout<<tr.queryPath(x,y)<<endl;
}
else{
int x,z;
cin>>x>>z;
if(tr.fa[x]) tr.modifyPath(tr.fa[x],tr.fa[x],z);
if(tr.son[x]) tr.modifyPath(tr.son[x],tr.son[x],z);
tr.tag[x]+=z;
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
5. 1012 子集
知识点:线性基,高斯消元
题目要求取值不能相邻,所以取值的间隔要么是1,要么是2,间隔没有必要是更大值。
假设有五个数,如果选第1个数,且下一个数是第5个数(此时间隔是3),把这两个数插入线性基
此时则可以再把第3个数插入线性基,结果一定不劣
所以 \(DFS\) 枚举所有可能情况即可,中间需要动态插入线性基
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
class XorBasis {//基于高斯消元,逐个插入值
public:
const int BITS = 62;
vector<long long> basis; // 存储线性基的基底
int k; // 基底中向量的个数
bool has_zero; // 标记原数集中是否能异或出 0
XorBasis() : basis(BITS + 1, 0), k(0), has_zero(false) {}
void insert(long long num) {
for (int i = BITS; i >= 0; i--) {
if (!((num >> i) & 1)) {
continue;
}
if (!basis[i]) {
basis[i] = num;
k++;
return;
}
num ^= basis[i];
}
// 如果 num 最终变为 0,说明 num 可以由基底中的数异或表示
// 这意味着原集合中存在线性相关的向量,可以异或出 0
has_zero = true;
}
long long queryMax() const {
long long ans = 0;
for (int i = BITS; i >= 0; i--) {
ans = max(ans, ans ^ basis[i]);
}
return ans;
}
/**
* @brief 查询线性基能表示的最小非零异或和。
*
* @return 最小非零异或和。如果线性基为空,返回 0。
*/
long long queryMin() const {
if(k == 0) return 0; // 如果线性基为空,没有非零异或和
for(int i = 0; i <= BITS; ++i) {
if(basis[i] != 0) {
return basis[i];
}
}
return 0; // 理论上不会执行到这里,除非 k 的维护有问题
}
//判断一个数是否可以被当前线性基中的数异或表示出来。
bool can_xor_out(long long num) const {
for (int i = BITS; i >= 0; i--) {
if (((num >> i) & 1)) {
num ^= basis[i];
}
}
return num == 0;
}
/**
* @brief 将线性基转换为简化行阶梯形式。
* 这一步是为了高效地查询第 k 小值。
* @return 转换后的基底向量数组。
*/
vector<long long> getSimplifiedBasis() const {
vector<long long> temp_basis;
for(int i = 0; i <= BITS; ++i){
if(basis[i] != 0){
temp_basis.push_back(basis[i]);
}
}
// 高斯消元,转换成对角矩阵形式,方便查询第 k 小
for (size_t i = 0; i < temp_basis.size(); ++i) {
for (size_t j = i + 1; j < temp_basis.size(); ++j) {
temp_basis[j] = min(temp_basis[j], temp_basis[j] ^ temp_basis[i]);
}
}
return temp_basis;
}
/**
* @brief 查询线性基能表示的第 rank 小的异或和。
*
* @param rank 排名 (从 1 开始)。
* @return 第 rank 小的异或和。如果 rank 超出范围,返回 -1。
*/
long long queryKth(long long rank) const {
// 如果原数集可以异或出 0,那么第 1 小是 0。
// 我们需要调整 rank 来查询非零异或和中的第 (rank-1) 小。
if (has_zero) rank--;
// 如果调整后的 rank 是 0,说明查询的是第 1 小,且 0 可以被凑出。
if (rank == 0) return 0;
// 能表示出的不同异或和的数量是 2^k,如果 rank 超出范围,则无法表示。
if (rank >= (1LL << k)) return -1;
vector<long long> simplified_basis = getSimplifiedBasis();
long long ans = 0;
for (size_t i = 0; i < simplified_basis.size(); ++i) {
if ((rank >> i) & 1) {
ans ^= simplified_basis[i];
}
}
return ans;
}
};
void solve(){
int n;
cin>>n;
vector<int> a(n);
for(int i=0;i<n;i++){
cin>>a[i];
}
int ans=0;
auto dfs=[&](auto dfs,int now,XorBasis &xb)->void {
ans=max(ans,xb.queryMax());
if(now+2<n){
XorBasis nxb=xb;
nxb.insert(a[now+2]);
dfs(dfs,now+2,nxb);
}
if(now+3<n){
xb.insert(a[now+3]);
dfs(dfs,now+3,xb);
}
};
for(int i=1;i<=2;i++){//至少有 i 个元素
if(n>=i){
XorBasis xb;
xb.insert(a[i-1]);
dfs(dfs,i-1,xb);
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}

浙公网安备 33010602011771号