2019.8.26 noip模拟赛#5

本文深入解析三道算法竞赛题目,包括细节丰富的T1序列题、优化上班路径的T2工作题及涉及树链剖分技巧的T3最小生成树题。通过代码示例,详述解题思路与关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1 序列

一道超级细节的模拟题,需要注意很多细节。我分了三类,写了100+行,而hzy大佬才写了50+行。其实很简单。
直接贴代码吧:

#include<cstdio>
#include<algorithm>
#define maxn 100005
using namespace std;

int n;
int a[maxn],b[maxn],ans[maxn];
int tonga[maxn],tongb[maxn];
int pos[maxn];
int cnta,cntb,vala,valb,lesa,lesb,tot;

int max(int x,int y)
{
	return x>y?x:y;
}

int min(int x,int y)
{
	return x<y?x:y;
}

int main()
{
	//freopen("seq.in","r",stdin);
	//freopen("seq.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		if(!tonga[a[i]])
		cnta++;
		tonga[a[i]]++;//打标记
	}
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&b[i]);
		if(!tongb[b[i]])
		cntb++;
		tongb[b[i]]++;
	}
	if(cnta!=n-1||cntb!=n-1)
	{
		printf("Impossible");
		return 0;
	}
	for(int i=1;i<=n;++i)
	{
		if(a[i]!=b[i])
		pos[++tot]=i;//记录不同数的坐标
		else
		ans[i]=a[i];
		if(tonga[i]==2)vala=i;
		if(tongb[i]==2)valb=i;//记录a,b重复的数
		if(tonga[i]==0)lesa=i;
		if(tongb[i]==0)lesb=i;//标记a,b缺少的数
	}
	if(tot>=3)//不同的数的个数不能超过3
	{
		printf("Impossible");
		return 0;
	}
	if(tot==2)
	{
		if(lesa!=lesb)
		{
			ans[pos[1]]=min(lesa,lesb);
			ans[pos[2]]=max(lesa,lesb);
			if((ans[pos[1]]!=b[pos[1]]&&ans[pos[2]]!=b[pos[2]])||(ans[pos[1]]!=a[pos[1]]&&ans[pos[2]]!=a[pos[2]]))
			{
				swap(ans[pos[1]],ans[pos[2]]);
			}
		}
		else
		{
			printf("Impossible");
			return 0;
		}
	}
	if(tot==1)
	ans[pos[1]]=lesa;
	if(tot==0)
	{
		if(vala<lesa)
		{
			for(int i=n;i>=1;--i)
			{
				if(ans[i]==vala)
				{
					ans[i]=lesa;
					break;
				}
			}
		}
		else
		{
			for(int i=1;i<=n;++i)
			{
				if(ans[i]==vala)
				{
					ans[i]=lesa;
					break;
				}
			}
		}
	}
	for(int i=1;i<=n;++i)
	printf("%d\n",ans[i]);
	return 0;
}

T2 工作

由于打卡机到公司的距离是不变的,我们只需要考虑人到打卡机的距离。把人和打卡机单独拎出来,画在数轴上:
工作1
因为影响答案的是上班时间的最大值,所以将人和打卡机连接起来,若不出现交点一定是更优的,如图:
工作2
工作3
上图一定比下图更优。
因为答案满足二分性,所以我们可以二分答案。先把人和机器的坐标排序,再二分一个答案,在check时贪心的枚举人,从1开始枚举机器,计算人—>机器—>公司的距离,若大于枚举的答案,则枚举下一个机器;反之,枚举下一个人。当枚举的机器编号大于m时跳出循环。若此时枚举到的人刚好等于n+1(即所有人都有机器时),返回1,反之返回0.
注:求距离时一定要用公式。其他细节见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 100005
using namespace std;

ll n,m,pos;
ll peo[maxn],mac[maxn];

ll check(ll x)
{
	ll i,cnt=1;
	for(i=1;i<=n;++i)
	{
		ll ret=abs(peo[i]-mac[cnt])+abs(pos-mac[cnt]);
		while(ret>x&&cnt<=m)
		{
			cnt++;
			ret=abs(peo[i]-mac[cnt])+abs(pos-mac[cnt]);
		}
		if(cnt>m)break;
		cnt++;
	}
	return (i==n+1);
}

int main()
{
	//freopen("work.in","r",stdin);
	//freopen("work.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&pos);
	for(ll i=1;i<=n;++i)
	scanf("%lld",&peo[i]);
	for(ll i=1;i<=m;++i)
	scanf("%lld",&mac[i]);
	sort(peo+1,peo+1+n);
	sort(mac+1,mac+1+m);
	ll l=0,r=2e9+1e8;
	while(l<=r)
	{
		ll mid=(l+r)>>1;
		if(check(mid))r=mid-1;
		else l=mid+1;
	}//二分答案
	printf("%lld",l);
	return 0;
}
/*
2 2 10
9 11
15 7
*/

T3 最小生成树

名字叫最小生成树,但算法与最小生成树关系不大。先求出一颗最小生成树,给树边打上标记。再枚举边,对于非树边,若其连接的节点是(u,v)那么该边的答案就是链(u,v)上的最大边权-1,用树链剖分处理即可;对于树边,该边的答案是包含它的非树边的最小值-1,画个图理解下(黑色是树边,绿色是非树边,黑数字是边的权值,红数字是答案):
tree
不信请自行验证。
所以我们可以只枚举非树边,先用树链剖分更新非树边的答案,再找到非树边连接的两点(u,v)的lca点p,从u到p更新树边的答案,再从v到p更新树边的答案。注意,因为我们以事先对边权排过序了,所以更新答案的时候不用取最小值。
细节:因为给的是边权,所以要边权下放。更重要的是,求两点间最大值时不能考虑lca的值,向上更新树边答案时也不能更新lca以及其父节点边的值(在这里我查错了一个多小时)。
更多细节见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100005
#define lid (k<<1)
#define rid (k<<1|1)
using namespace std;

struct node
{
	int u,v,val,flag,num;
}po[maxn];
struct tree
{
	int v,val,next,num;
}tr[maxn<<1];
struct poo
{
	int l,r,mx;
}pool[maxn*4];
int n,m,cnt;
int tot,head[maxn];
int fa[maxn],val[maxn];
int idc,size[maxn],dep[maxn],f[maxn],son[maxn];
int top[maxn],in[maxn],out[maxn],seq[maxn];//树链剖分数组
int flag,ans[maxn];

bool cmp(node x,node y)
{
	return x.val<y.val;
}

int find(int x)
{
	if(fa[x]!=x)return fa[x]=find(fa[x]);
	return x;
}

void unionn(int x,int y)
{
	x=find(x);y=find(y);
	fa[x]=y;
}

int judge(int x,int y)
{
	x=find(x);y=find(y);
	return (x==y)?1:0;
}

void add(int x,int y,int z,int nu)
{
	tot++;
	tr[tot].v=y;
	tr[tot].val=z;
	tr[tot].next=head[x];
	tr[tot].num=nu;
	head[x]=tot;
}

void dfs1(int x)
{
	size[x]=1;
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v,z=tr[t].val;
		if(y==f[x])continue;
		dep[y]=dep[x]+1;
		f[y]=x;
		val[y]=z;
		dfs1(y);
		size[x]+=size[y];
		if(size[y]>size[son[x]])son[x]=y;
	}
}

void dfs2(int x,int tp)
{
	in[x]=++idc;
	seq[idc]=x;
	top[x]=tp;
	if(son[x])
	dfs2(son[x],tp);
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v;
		if(y==son[x]||y==f[x])continue;
		dfs2(y,y);
	}
	out[x]=idc;
}

void build(int k,int l,int r)
{
	pool[k].l=l;
	pool[k].r=r;
	if(l==r)
	{
		pool[k].mx=val[seq[l]];
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pool[k].mx=max(pool[lid].mx,pool[rid].mx);
}//树链剖分

int query(int k,int l,int r)
{
	if(pool[k].l==pool[k].r)
	return pool[k].mx;
	int ret=0,mid=(pool[k].l+pool[k].r)>>1;
	if(l<=mid)
	ret=max(ret,query(lid,l,r));
	if(r>mid)
	ret=max(ret,query(rid,l,r));
	return ret;
}

int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=f[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}//跳lca

int add(int x,int y)
{
	int ret=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		ret=max(ret,query(1,in[top[x]],in[x]));
		x=f[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	ret=max(ret,query(1,in[y]+1,in[x]));
	return ret;
}

void up(int x,int p,int val)
{
	if(x==p)
	{
		flag=0;
		return;
	}
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v,nu=tr[t].num;
		if(y!=f[x])continue;
		if(!ans[nu])
		ans[nu]=val-1;
		up(y,p,val);
		if(!flag)return;
	}
}//向上更新树边答案

int main()
{
	//freopen("tree.in","r",stdin);
	//freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	fa[i]=i;
	for(int i=1;i<=m;++i)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		po[i].u=x;
		po[i].v=y;
		po[i].val=z;
		po[i].num=i;
	}
	sort(po+1,po+1+m,cmp);
	for(int i=1;i<=m;++i)
	{
		int x=po[i].u,y=po[i].v,z=po[i].val,nw=po[i].num;
		if(judge(x,y))continue;
		unionn(x,y);
		po[i].flag=1;//标记树边
		cnt++;
		add(x,y,z,nw);
		add(y,x,z,nw);
		if(cnt==n-1)break;
	}//求最小生成树
	f[1]=0;
	dep[1]=1;
	dfs1(1);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		int x=po[i].u,y=po[i].v,z=po[i].val;
		if(!po[i].flag)
		{
			int ret=add(x,y);
			ans[po[i].num]=ret-1;
			int p=lca(x,y);//寻找lca
			flag=1;
			up(x,p,z);
			flag=1;
			up(y,p,z);
		}//树边
	}
	for(int i=1;i<=m;++i)
	printf("%d\n",ans[i]==0?-1:ans[i]);
	return 0;
}

题目在这里(但一般人不能进去):题面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值