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\) 与之后换乘 \(B\),\(B\) 的运行时间都是一样的,而 \(A\) 的结束时刻提前了。因此,不存在换乘的情况。
那么一个人一定是只坐一个电梯,从 \(A_i\) 到 \(B_i\)。
考虑电梯的运行结束由所载的人的出去的最高楼层,记为 \(M\),总共载的人数 \(k\),那么结束时刻为 \(M-1+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+m-2\)。
考虑特殊点的走法,一种贪心的是,我能走 \((i+1,j+1)\) 就走,绝对不走存在 \(-1\) 的。
考虑证明:要是走了 \(-1\) 的更优,理应是能走更多的特殊点。
图中设在第一段中途的点为 \(a\),之后假如 \(a+1\) 到特殊点,然后再回退过去,步数是 \(a+2\),然后接上第二段有特殊点的路径。那么不如你在 \(a\) 时,直接普通走法走过去,同样步数为 \(a+2\)。那么显然不优于我第一段的时候中途推出,然后走普通的过去,然后接上第二段,显然是不优于的,因为走了 \(-1\) 的多了一个累赘步。
这是一种更优的情况。
综上,你只需要选出最长特殊点序列 \(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;
}