三化二叉树trick
三选一化二叉
套路概述
这个套路是针对某一建模题的。
三选一其实可以扩展到N选一,模型具体如下。
发现某种状态可以扩展出
建模-跳跳棋
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有三颗棋子,分别在
跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过一颗棋子。
写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。
分析
考虑一个三元组
这四种操作里面,容易发现三四操作最多只能存在一种。
容易发现,操作具有可逆性。而操作1,2都导致
突然发现,如果
我们可以由一组
1次:
2次:
好像,它形成了一个树形结构?也即中间棋子向左跳为左儿子,向右跳为右儿子,向内跳的情况为父亲,这样就由
此时,由于操作的可逆性,容易发现初始状态和终态有解当且仅当他们所在树的树根相同。然后最少操作次数就是二者在树上的距离。
但是容易发现这棵树好像无穷大,根本不可能建立出来好吧。我们先来考虑如何快速求三元组
数据范围
考虑这样一个过程,我们优化掉连续向某个方向跳的操作,这个可以通过公式计算:
设
然后
这是一个类似于计算欧几里得算法的过程,到最终肯定会有一个终态。于是这部分复杂度被优化到了
再接着,由于计算树上路径需要LCA,而这棵树的性质决定了我们不可能使用常用LCA算法优化这个过程。但倍增的思想告诉我们,我们可以自底向上地倍增!具体的,先将两个节点调整到同一高度。由于这个算法的存在,可以在
事实上,这个复杂度应该远远跑不满才对。
#include<iostream>
#include<cstdio>
using namespace std;
struct node{
int a,b,c;
void init(){
cin>>a>>b>>c;
if(a>b)swap(a,b);if(a>c)swap(a,c);if(b>c)swap(b,c);
}
bool operator==(node x){
return a==x.a&&b==x.b&&c==x.c;
}
}a,b,fa,fb;
int cnt1,cnt2,ans,l1,l2;
void bezero(node &x){
if(x.a>x.b)swap(x.a,x.b);
if(x.a>x.c)swap(x.a,x.c);
if(x.b>x.c)swap(x.b,x.c);
}
node get(node a,int k,int &cnt){//得到a的k级父亲
node ans=a;
cnt=0;
while(k){
bezero(ans);
int x1=ans.b-ans.a,x2=ans.c-ans.b,s;
if(x1<x2){
s=min(k,(x2-1)/x1);
ans.a+=s*x1;
ans.b+=s*x1;
k-=s;
}
else if(x1>x2){
s=min(k,(x1-1)/x2);
ans.b-=s*x2;
ans.c-=s*x2;
k-=s;
}
else {
return ans;
}
cnt+=s;
}
return ans;
}
int main(){
a.init();b.init();
fa=get(a,0x3f3f3f3f,cnt1);
fb=get(b,0x3f3f3f3f,cnt2);
if(!(fa==fb)){
cout<<"NO\n";
return 0;
}
cout<<"YES\n";
if(cnt1>cnt2)swap(a,b),swap(cnt1,cnt2);
ans+=cnt2-cnt1;
b=get(b,cnt2-cnt1,cnt2);
if(a==b){
cout<<ans;
return 0;
}
for(int i=29;i>=0;--i){
node x=get(a,1<<i,l1),y=get(b,1<<i,l2);
if(!(x==y)){
a=x,b=y;
ans+=1<<(i+1);
}
}
cout<<ans+2;
}
清华集训的题都是神题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!