2025“钉耙编程”中国大学生算法设计暑期联赛(2)
树上的图
题目大意、
有正整数 n,给定起点 x 和终点 y(都在 1 到 n 之间 ),要通过最少操作把 x 转成 y。操作有两种:
- 找数
i(1≤i≤n),若x和i二进制里1的个数(count)相同,x可转成i。 - 找数
i(1≤i≤n),若x和i二进制里最低位1及后面0组成的数(lowbit)相同,x可转成i。
求x变y最少操作次数 。
数据范围
有多组测试数据,第一行输入 T(T 是 1 到 \(10^5\)) 的正整数 ),表示测试数据的组数 。每组测试数据占一行,包含三个正整数 n、x、y,满足 \(1 \leq x,y \leq n \leq 10^{15}\) ,n 是数值上界,x 是起点,y 是终点 。
思路
- x=y,则不需要操作
- count(x)=count(y) or lowbit(x)=lowbit(y), 仅需要一次操作
- 先count()转化,再lowbit转化,两次操作
点击查看代码
//2025“钉耙编程”中国大学生算法设计暑期联赛(2)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll lowbit(bitset<100> t) {
return t._Find_first();//找到第一个1的位置
}
void solve() {
ll n, x, y;
cin >> n >> x >> y;
bitset<100>a(x),b(y);
ll xx = a.count();
ll yy = b.count();
if (x == y) {
cout << 0 << "\n";
} else if (xx == yy || lowbit(x) == lowbit(y)) {
cout << 1 << "\n";
} else {
cout << 2 << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _ = 1;
cin >> _;
while (_--) {
solve();
}
return 0;
}
井
题目大意
有一个 ( n \times n ) 的网格,其中恰好有一行 或 一列全为 1,其余为 0。网格状态是从 2n 种可能(选一行或一列全置 1)中等概率随机选一种。
操作与目标
网格初始全部覆盖,你按策略依次翻格子。当所有 1 被翻出时结束,要找 最优策略,让翻开所有 1 的期望次数最小,并输出这个最小期望。
数据范围
- 第一行输入 T ( $1 \leq T \leq 100 $),表示测试用例组数。
- 每组用例输入一个 n ($ 2 \leq n \leq 10^9 $),即网格规模。
思路
最有策略,先翻对角线,再翻行或列,最少翻n次,最多翻2n次,取平均3n/2次
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
double n;
cin >> n;
cout << fixed << setprecision(4) << n * 3 / 2 << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
cin >> _;
while (_--) {
solve();
}
return 0;
}
半
题目大意
- 有两场比赛,都有
n名选手(编号1~n),已知两场比赛各自的排名(a数组、b数组,均是1~n的排列,无并列)。 - 你作为其中一名选手(假设是
i),可以禁赛其他选手,禁赛后剩余选手相对排名不变。
对每个选手 i(1 ≤ i ≤ n ),计算让自己在两场比赛中都拿第一名,最少需要禁赛多少人。
数据范围
- 第一行输入
T(最多20组),表示测试用例数。 - 每组用例:
- 先输入
n(选手总数,最多 \(10^6\) )。 - 再输入两场比赛的排名数组
a、b(都是1~n的排列 )。
- 先输入
思路
思路是一目了然的,对于一个选手i找出,求出在a,b数组大于i排名的并集大小
先枚举a[i],记录小于a[i]的数,算出在b中小于a[i]的数,但不属于前i个a[],再加上i-1,
点击查看代码
//2025“钉耙编程”中国大学生算法设计暑期联赛(2)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n;
const int maxn=1e6+100;
int a[maxn],b[maxn];
int tr[maxn<<1];
int ans[maxn];
int lowbit(int x){
return x&-x;
}
void add(int x){
while(x<n){
tr[x]++;
x+=lowbit(x);
}
return ;
}
int sum(int x){
int res=0;
while(x){
res+=tr[x];
x-=lowbit(x);
}return res;
}
void solve() {
cin>>n;
for(int i=0;i<=n;++i) tr[i]=0;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=n;++i){
int x;
cin>>x;
b[x]=i;
}
for(int i=1;i<=n;++i){
int x=a[i];
int w=b[x]-1-sum(b[x]-1)+i-1;
ans[x]=w;
add(b[x]);
}
for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
cout<<endl;
return ;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _ = 1;
cin >> _;
while (_--) {
solve();
}
return 0;
}
苹果树
题目大意
有若干组树结构的数据,每组数据里:
- 给一棵
n个节点的树,每个节点有初始权值。 - 进行
m次操作,操作分两种:- 查 两节点路径上的最大权值;
- 给某个节点
x,把x所有直接相连节点的权值都+z。
数据范围
- 第一行:数据组数
T。 - 每组数据:
- 第一行:节点数
n、操作数m。 - 第二行:
n个初始权值a₁~aₙ。 - 接着
n-1行:每行两个数x、y,表示树的无向边。 - 接着
m行:每行三个数opt、x、y、z(opt=1时是查询路径x-y;opt=2时是给x的直接相连节点权值+z)。
- 第一行:节点数
所有测试数据的 n 总和、m 总和都不超 4×10⁵,
思路
题目的设问很明显涉及树链剖分,但是难点在于更新的复杂度太高
可以思考得到,在树链剖分中,一个点修改,会涉及到点是三部分,父节点,重儿子,其他节点
而当在查询的时候,我们注意到,我们只查询一条链上的所有点的最大值,而对于非链顶节点,其父节点,重儿子均在同一链上,于是我们可以考虑直接更新
而查询链顶端节点时,其必然为对应链上的“其他节点”,其节点值,等于,父节点相邻节点需要加的值+本身的值
于是我们考虑给x作标记,父节点,子节点直接更新,其他节点,改为tag[x]+=z,这样实现高效更新
注意,对于父节点更新时,需要同时更新父节点在原数组中的值,以便正确查询每个链定节点的值
点击查看代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define int long long
int n,m,r;
const int maxn=1e5+10;
int w[maxn],son[maxn];
int dep[maxn],siz[maxn];
int f[maxn],tag[maxn<<1];
int d[maxn],val[maxn];
int ans[maxn<<2];
struct node{
int v,next,val;
}e[maxn<<1];
int head[maxn<<1];
int top[maxn];
int cnt=0,tot=0;
void dfs1(int u,int fa,int deep){
dep[u]=deep;
siz[u]=1;
f[u]=fa;
int maxson=-1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
if(v==fa) continue;
dfs1(v,u,deep+1);
siz[u]+=siz[v];
if(maxson<siz[v]) maxson=siz[v],son[u]=v;
}
}
void dfs2(int u,int topf){
d[u]=++tot;
val[tot]=w[u];
top[u]=topf;
if(!son[u]) return ;
dfs2(son[u],topf);
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
if(v==f[u] || v==son[u]) continue;
dfs2(v,v);
}
}
void push_up(int p){
ans[p]=max(ans[p<<1],ans[p<<1|1]);
}
void build(int p,int l,int r){
if(l==r){
ans[p]=val[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
push_up(p);
}
void update(int p,int l,int r,int nl,int nr,int k){
if(nl<=l && r<=nr){
ans[p]=ans[p]+k;
return ;
}
int mid=(l+r)>>1;
if(nl<=mid) update(p<<1,l,mid,nl,nr,k);
if(nr>mid) update(p<<1|1,mid+1,r,nl,nr,k);
push_up(p);
}
int query(int p,int l,int r,int nl,int nr){
int res=0;
if(nl<=l && r<=nr) return ans[p];
int mid=l+r>>1;
if(nl<=mid) res=max(res,query(p<<1,l,mid,nl,nr));
if(nr>mid )res=max(res,query(p<<1|1,mid+1,r,nl,nr));
return res;
}
void qupdate(int x,int y,int z){
update(1,1,n,d[x],d[y],z);
}
int qrange(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=max(res,query(1,1,n,d[top[x]],d[x]));//链上
res=max(res,w[top[x]]+tag[f[top[x]]]);//链顶权值
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
res=max(res,query(1,1,n,d[x],d[y]));
if(x==top[x]) res=max(res,w[x]+tag[f[x]]);
return res;
}
void ad(int u,int v){
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int _;
cin>>_;
while(_--){
tot=cnt=0;
for(int i=1;i<=n*2;++i) tag[i]=head[i]=0,son[i]=0;
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>w[i];
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
ad(u,v);ad(v,u);
}
dfs1(1,0,1);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=m;++i){
int op;
cin>>op;int x,y,z;
if(op==1){
cin>>x>>y;
cout<<qrange(x,y)<<endl;
}
else {
cin>>x>>z;
//更新重链
//修改父节点
if(f[x]) qupdate(f[x],f[x],z),w[f[x]]+=z;//w[f[x]]+z保持与线段树中的值同步,在查询时会用到
//修改重儿子节点
if(son[x]) qupdate(son[x],son[x],z);//w[son[x]]不需要+z,因为访问到链顶时,必然不是w[son[x]]这个点
//其他相连的节点打标记
tag[x]+=z;
}
}
}
return 0;
}

浙公网安备 33010602011771号