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\) 的更优,理应是能走更多的特殊点。
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;
} 
posted @ 2023-01-12 16:12  FxorG  阅读(22)  评论(0编辑  收藏  举报