codechef Starters73

https://www.codechef.com/START73A?order=desc&sortBy=successful_submissions

Hamiltonian Tree

手玩一下走哈密顿路的过程,一定是,走一条树上路径,然后走过新加的边,再到另一条树上路径,然后走完,然后再走新加的边。

为啥?因为你一条路径走完了,那你不可能走回头路,所以你只能通过新加的边到达另一条路径。

再者,我们注意到,树上路径的任意两条之间点集无交,且所有的路径的点集的并为全集 n

也就是说,每个点仅存在于一条路径当中。

考虑最小化新加边,因为有路径数-1=新加边,因此即为最小化路径数。

考虑一个点,要么孤立作为一条路径,要么为路径的两端,要么为路径的中间。其度数对应 0,1,2。考虑两条路径如果要并起来,显然应该是他们存在两个端点的父亲是一样的,然后通过父亲把他们串起来,因此是可以树上 dp 的,因为我们对于当前点,只需要考虑孤立/连接以某个儿子为端点的一条路径,当前点作为端点/当前点作为中继点,串联起两个儿子为端点的路径。

考虑设 dp(u,0/1/2)u 的子树内,当前 u 点对应哪种情况,所划分出的最小路径数。

转移就很显然啦。

#include <bits/stdc++.h> #define int long long #define pb push_back #define cal(x) (min(dp[(x)][0],min(dp[(x)][1],dp[(x)][2]))) using namespace std; const int N=(int)(2e5+5),inf=(int)(2e9); vector<int>g[N]; int n,dp[N][3]; void clr() { for(int i=1;i<=n;i++) g[i].clear(),dp[i][0]=dp[i][1]=dp[i][2]=0; } void dfs(int x,int ff) { for(int y:g[x]) { if(y==ff) continue ; dfs(y,x); } dp[x][0]=dp[x][1]=dp[x][2]=0; int res=0; for(int y:g[x]) { if(y==ff) continue ; res+=cal(y); } dp[x][0]=res+1; dp[x][1]=dp[x][2]=inf; for(int y:g[x]) { if(y==ff) continue ; dp[x][1]=min(dp[x][1],min(dp[y][0],dp[y][1])+res-cal(y)); } int mi1=inf,mi2=inf; for(int y:g[x]) { if(y==ff) continue ; int qwq=min(dp[y][0],dp[y][1])-cal(y); if(qwq<mi1) mi2=mi1,mi1=qwq; else if(qwq<mi2) mi2=qwq; } if(mi2!=inf) { dp[x][2]=res+mi1+mi2-1; } } void sol() { cin>>n; for(int i=1;i<n;i++) { int x,y; cin>>x>>y; g[x].pb(y); g[y].pb(x); } dfs(1,0); int ans=cal(1); cout<<ans-1<<'\n'; clr(); } signed main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin>>T; while(T--) sol(); return 0; }

Elevators

时间显然有可二分性。

一个人假如在中间换乘,会更优吗?考虑原先乘坐的是电梯 A,现在要换乘电梯 B,那么你考虑我二分相当于有 m 容量为 Lim 的盒子,但是你一个人你就塞了 4 个 H 来贡献,显然是不会优的,你要是换乘 B 更优,那为什么不在一开始的时候就乘坐 B 呢?因为你一开始就乘坐 B 与之后换乘 BB 的运行时间都是一样的,而 A 的结束时刻提前了。因此,不存在换乘的情况。

那么一个人一定是只坐一个电梯,从 AiBi

考虑电梯的运行结束由所载的人的出去的最高楼层,记为 M,总共载的人数 k,那么结束时刻为 M1+2Hk

考虑 B 最大的人,显然他会贡献到答案的 M,这时候你显然按 B 从大到小填人,能减小这一段 B 对后面电梯的 M 的贡献。

因此,贪心判定是否合法就很显然了。从大到小排 B,然后能塞就塞。

#include <bits/stdc++.h> #define int long long #define pb push_back using namespace std; const int N=(int)(3e5+5),inf=(int)(1e18); struct node { int a,b; }v[N]; int n,m,H; bool cmp(const node &x,const node &y) { return x.b==y.b?x.a<y.a:x.b>y.b; } bool chk(int Lim) { int re=m; for(int i=1;i<=n;) { if(v[i].b-1+2*H>Lim) return 0; int pos=i,res=1; while(pos<n&&v[i].b-1+2*(res+1)*H<=Lim) ++res,++pos; --re; if(re<0) return 0; i=pos+1; } return 1; } void sol() { cin>>n>>m>>H; for(int i=1;i<=n;i++) cin>>v[i].a>>v[i].b; sort(v+1,v+1+n,cmp); int l=0,r=inf,res=0; while(l<=r) { int mid=(l+r)>>1; if(chk(mid)) res=mid,r=mid-1; else l=mid+1; } cout<<res<<'\n'; } signed main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin>>T; while(T--) sol(); return 0; }

First To Last

答案上界即为 n+m2

考虑特殊点的走法,一种贪心的是,我能走 (i+1,j+1) 就走,绝对不走存在 1 的。

考虑证明:要是走了 1 的更优,理应是能走更多的特殊点。
image

图中设在第一段中途的点为 a,之后假如 a+1 到特殊点,然后再回退过去,步数是 a+2,然后接上第二段有特殊点的路径。那么不如你在 a 时,直接普通走法走过去,同样步数为 a+2。那么显然不优于我第一段的时候中途推出,然后走普通的过去,然后接上第二段,显然是不优于的,因为走了 1 的多了一个累赘步。

image

这是一种更优的情况。

综上,你只需要选出最长特殊点序列 P,后面的点的 x,y 均大于前面的点的 x,y,即能跳 (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; // cout<<tot<<'\n'; 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; // for(int j=1;j<i;j++) { // if(a[j].x<a[i].x&&a[j].y<a[i].y) f[i]=max(f[i],f[j]+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; }

Minimum OR Path

考虑二进制下从高位到低位钦定答案,显然我们在满足高位最小的情况下,能使当前位为 0,那么不管后面咋样,就一定当前位为 0

那么就变成维护一个集合,那么你到当前位的时候,显然能使用的数都是满足比当前位高的位的限制的了,然后你钦定(按当前位 0/1 钦定)完肯定是一些点不能用的,然后判断当前钦定下的点集能否扫到 n 点。如果能,删去其他未被钦定到的点。否则,你当前位只能为 1,集合不必任何修改。

#include <bits/stdc++.h> //#define int long long #define pb push_back using namespace std; const int N=(int)(5e5+5); int n,a[N],L[N],R[N],vec[N],tot; void clr() { tot=0; for(int i=0;i<=n+1;i++) L[i]=R[i]=0; } bool chk() { if(!tot) return 0; if(vec[1]!=1) return 0; if(vec[tot]!=n) return 0; int R=min(1+a[1],n); for(int i=1;i<=tot;i++) { bool qwq=(R>=vec[i]); if(!qwq) continue ; R=max(R,min(vec[i]+a[vec[i]],n)); } if(R>=n) return 1; return 0; } void sol() { cin>>n; tot=0; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) L[i]=i-1,R[i]=i+1; R[n]=0; R[0]=1; for(int i=1;i<=n;i++) vec[++tot]=i; if(!chk()) { cout<<"-1\n"; return ; } int ans=0; for(int i=19;i>=0;i--) { tot=0; for(int j=R[0];j;j=R[j]) { if(!((a[j]>>i)&1)) { vec[++tot]=j; // cout<<j<<' '; } } // cout<<'\n'; if(tot&&vec[1]==1) { if(chk()) { for(int j=1;j<=tot;j++) { L[vec[j]]=vec[j-1]; R[vec[j]]=vec[j+1]; } R[vec[tot]]=0; R[0]=vec[1]; continue ; } // cout<<"x\n"; } ans+=(1<<i); } cout<<ans<<'\n'; clr(); } signed main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin>>T; while(T--) sol(); return 0; }

__EOF__

本文作者F x o r G
本文链接https://www.cnblogs.com/xugangfan/p/17046992.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   FxorG  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示