2025“钉耙编程”中国大学生算法设计暑期联赛(2)

树上的图

题目大意、

有正整数 n,给定起点 x 和终点 y(都在 1n 之间 ),要通过最少操作把 x 转成 y。操作有两种:

  • 找数 i1≤i≤n ),若 xi 二进制里 1 的个数(count)相同,x 可转成 i
  • 找数 i1≤i≤n ),若 xi 二进制里最低位 1 及后面 0 组成的数(lowbit)相同,x 可转成 i
    xy 最少操作次数 。

数据范围

有多组测试数据,第一行输入 TT 是 1 到 \(10^5\)) 的正整数 ),表示测试数据的组数 。每组测试数据占一行,包含三个正整数 nxy,满足 \(1 \leq x,y \leq n \leq 10^{15}\)n 是数值上界,x 是起点,y 是终点 。

思路

  1. x=y,则不需要操作
  2. count(x)=count(y) or lowbit(x)=lowbit(y), 仅需要一次操作
  3. 先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 ),可以禁赛其他选手,禁赛后剩余选手相对排名不变。

对每个选手 i1 ≤ i ≤ n ),计算让自己在两场比赛中都拿第一名,最少需要禁赛多少人。

数据范围

  • 第一行输入 T(最多 20 组),表示测试用例数。
  • 每组用例:
    • 先输入 n(选手总数,最多 \(10^6\) )。
    • 再输入两场比赛的排名数组 ab(都是 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 次操作,操作分两种:
    1. 两节点路径上的最大权值
    2. 给某个节点 x,把 x 所有直接相连节点的权值都 +z

数据范围

  • 第一行:数据组数 T
  • 每组数据
    • 第一行:节点数 n、操作数 m
    • 第二行:n 个初始权值 a₁~aₙ
    • 接着 n-1 行:每行两个数 x、y,表示树的无向边。
    • 接着 m 行:每行三个数 opt、x、y、zopt=1 时是查询路径 x-yopt=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;
}




posted @ 2025-08-26 20:09  归游  阅读(51)  评论(0)    收藏  举报