https://www.codechef.com/START73A?order=desc&sortBy=successful_submissions
手玩一下走哈密顿路的过程,一定是,走一条树上路径,然后走过新加的边,再到另一条树上路径,然后走完,然后再走新加的边。
为啥?因为你一条路径走完了,那你不可能走回头路,所以你只能通过新加的边到达另一条路径。
再者,我们注意到,树上路径的任意两条之间点集无交,且所有的路径的点集的并为全集 n n 。
也就是说,每个点仅存在于一条路径当中。
考虑最小化新加边,因为有路径数-1=新加边,因此即为最小化路径数。
考虑一个点,要么孤立作为一条路径,要么为路径的两端,要么为路径的中间。其度数对应 0 , 1 , 2 0 , 1 , 2 。考虑两条路径如果要并起来,显然应该是他们存在两个端点的父亲是一样的,然后通过父亲把他们串起来,因此是可以树上 dp 的,因为我们对于当前点,只需要考虑孤立/连接以某个儿子为端点的一条路径,当前点作为端点/当前点作为中继点,串联起两个儿子为端点的路径。
考虑设 d p ( u , 0 / 1 / 2 ) d p ( u , 0 / 1 / 2 ) 为 u u 的子树内,当前 u u 点对应哪种情况,所划分出的最小路径数。
转移就很显然啦。
时间显然有可二分性。
一个人假如在中间换乘,会更优吗?考虑原先乘坐的是电梯 A A ,现在要换乘电梯 B B ,那么你考虑我二分相当于有 m m 容量为 L i m L i m 的盒子,但是你一个人你就塞了 4 个 H H 来贡献,显然是不会优的,你要是换乘 B B 更优,那为什么不在一开始的时候就乘坐 B B 呢?因为你一开始就乘坐 B B 与之后换乘 B B ,B B 的运行时间都是一样的,而 A A 的结束时刻提前了。因此,不存在换乘的情况。
那么一个人一定是只坐一个电梯,从 A i A i 到 B i B i 。
考虑电梯的运行结束由所载的人的出去的最高楼层,记为 M M ,总共载的人数 k k ,那么结束时刻为 M − 1 + 2 H k M − 1 + 2 H k 。
考虑 B B 最大的人,显然他会贡献到答案的 M M ,这时候你显然按 B B 从大到小填人,能减小这一段 B B 对后面电梯的 M M 的贡献。
因此,贪心判定是否合法就很显然了。从大到小排 B B ,然后能塞就塞。
答案上界即为 n + m − 2 n + m − 2 。
考虑特殊点的走法,一种贪心的是,我能走 ( i + 1 , j + 1 ) ( i + 1 , j + 1 ) 就走,绝对不走存在 − 1 − 1 的。
考虑证明:要是走了 − 1 − 1 的更优,理应是能走更多的特殊点。
图中设在第一段中途的点为 a a ,之后假如 a + 1 a + 1 到特殊点,然后再回退过去,步数是 a + 2 a + 2 ,然后接上第二段有特殊点的路径。那么不如你在 a a 时,直接普通走法走过去,同样步数为 a + 2 a + 2 。那么显然不优于我第一段的时候中途推出,然后走普通的过去,然后接上第二段,显然是不优于的,因为走了 − 1 − 1 的多了一个累赘步。
这是一种更优的情况。
综上,你只需要选出最长特殊点序列 P P ,后面的点的 x , y x , y 均大于前面的点的 x , y x , y ,即能跳 ( i + 1 , j + 1 ) ( i + 1 , j + 1 ) 即可。
不难发现是个 LIS,直接做就好了。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=(int )(2e5 +5 ),inf=(int )(2e18 );
struct node {
int x,y;
}a[N];
int n,m,K,f[N],lsh[N],tot;
bool cmp (const node &x,const node &y) {
return x.x==y.x?x.y<y.y:x.x<y.x;
}
#define ls ((cur)<<1)
#define rs ((ls)|1)
int mx[N<<2 ];
void build (int cur,int l,int r) {
if (l>r) return ;
mx[cur]=-inf;
if (l==r) return ;
int mid=(l+r)>>1 ;
build (ls,l,mid); build (rs,mid+1 ,r);
}
void push_up (int cur) {
mx[cur]=max (mx[ls],mx[rs]);
}
void upt (int cur,int l,int r,int p,int v) {
if (l==r) return mx[cur]=max (mx[cur],v),void ();
int mid=(l+r)>>1 ;
if (p<=mid) upt (ls,l,mid,p,v);
else upt (rs,mid+1 ,r,p,v);
push_up (cur);
}
int qry (int cur,int l,int r,int cl,int cr) {
if (cl>cr) return -inf;
if (cl<=l&&r<=cr) return mx[cur];
int mid=(l+r)>>1 ;
if (cr<=mid) return qry (ls,l,mid,cl,cr);
if (cl>mid) return qry (rs,mid+1 ,r,cl,cr);
return max (qry (ls,l,mid,cl,cr),qry (rs,mid+1 ,r,cl,cr));
}
void clr () {
tot=0 ;
for (int i=0 ;i<=K;i++) f[i]=0 ;
for (int i=0 ;i<=K*4 ;i++) mx[i]=-inf;
}
void sol () {
cin>>n>>m>>K; tot=0 ;
for (int i=1 ;i<=K;i++) {
cin>>a[i].x>>a[i].y;
lsh[++tot]=a[i].y;
}
sort (lsh+1 ,lsh+1 +tot); tot=unique (lsh+1 ,lsh+1 +tot)-lsh-1 ;
build (1 ,1 ,tot);
sort (a+1 ,a+1 +K,cmp);
f[0 ]=0 ; int las=0 ;
for (int i=1 ;i<=K;i++) {
f[i]=1 ;
int p=lower_bound (lsh+1 ,lsh+1 +tot,a[i].y)-lsh;
f[i]=max (f[i],1 +qry (1 ,1 ,tot,1 ,p-1 ));
if (i==K) continue ;
while (a[las+1 ].x<a[i+1 ].x) {
p=lower_bound (lsh+1 ,lsh+1 +tot,a[las+1 ].y)-lsh;
upt (1 ,1 ,tot,p,f[las+1 ]);
++las;
}
}
int ans=0 ;
for (int i=1 ;i<=K;i++) {
if (a[i].x<n&&a[i].y<m) ans=max (ans,f[i]);
}
cout<<n+m-2 -ans<<'\n' ;
clr ();
}
signed main () {
cin.tie (0 ); ios::sync_with_stdio (false );
int T; cin>>T; while (T--) sol ();
return 0 ;
}
考虑二进制下从高位到低位钦定答案,显然我们在满足高位最小的情况下,能使当前位为 0 0 ,那么不管后面咋样,就一定当前位为 0 0 。
那么就变成维护一个集合,那么你到当前位的时候,显然能使用的数都是满足比当前位高的位的限制的了,然后你钦定(按当前位 0/1 钦定)完肯定是一些点不能用的,然后判断当前钦定下的点集能否扫到 n n 点。如果能,删去其他未被钦定到的点。否则,你当前位只能为 1 1 ,集合不必任何修改。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】