2018_9_23 模拟赛

本文精选三道算法竞赛题目,包括“农夫约的假期”、“小x游世界树”和“观察”,深入分析每道题目的解题思路,并提供详细的代码实现。涉及曼哈顿距离、树的根节点选择及LCA深度查询等核心算法。

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

前言:

感觉自己越来越渺小了


JZOJ 5775 农夫约的假期

题目

在一个平面,有 n n n个点,找出一个点使 n n n个点到该点的曼哈顿距离 ( ∑ a b s ( x i − x j ) + a b s ( y i + y j ) ) (\sum abs(x_i-x_j)+abs(y_i+y_j)) (abs(xixj)+abs(yi+yj))和最短


分析

显然,这道题讲到这里,那么可以发现中位数就是最优的
。All in all,就讲完了


代码

    #include <cstdio>
    #include <algorithm>
    #define rr register
    using namespace std;
    int m,x[100001],y[100001];long long ans;
    inline int in(){
    	rr int ans=0; rr char c=getchar();
    	while (c<48||c>57) c=getchar();
    	while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
    	return ans;
    }
    int main(){
    	in(); m=in(); in(); rr int mid=m+1>>1;
    	for (rr int i=1;i<=m;i++) x[i]=in(),y[i]=in(),ans+=in();
    	nth_element(x+1,x+mid,x+1+m);//求第mid大
    	nth_element(y+1,y+mid,y+1+m);	
        for (rr int i=1;i<=m;i++) ans+=abs(x[i]-x[mid])+abs(y[i]-y[mid]);//计算曼哈顿距离
    	return !printf("%lld\n%d %d",ans,x[mid],y[mid]);
    }

JZOJ 5776 小x游世界树

题目

在一棵树上重新找一个根,使根到各点的距离最短


分析

其实这道题可以说是近乎暴力,但是关于找根可以用二次扫描换根法,感性理解,代码才是最有力的解释工具


代码

    #include <cstdio>
    #define rr register
    struct node{int y,w,next;}e[1400011];
    int n,son[700001],ls[700001],k=1,ans1=1;long long ans;
    inline int in(){
    	rr int ans=0; rr char c=getchar();
    	while (c<48||c>57) c=getchar();
    	while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
    	return ans;
    }
    inline void add(int x,int y){
    	rr int w=in();
    	e[++k]=(node){y,w-son[x],ls[x]}; ls[x]=k;
    	e[++k]=(node){x,w-son[y],ls[y]}; ls[y]=k;
    }
    inline void dp(int x,int fa,long long sum){
    	for (rr int i=ls[x];i;i=e[i].next)
    	if (e[i].y!=fa)	dp(e[i].y,x,sum+e[i].w),son[x]+=son[e[i].y];
    	ans+=sum; son[x]++;
    }
    inline void dfs(int x,int fa,long long sum){
    	for (rr int i=ls[x];i;i=e[i].next)
    	if (e[i].y!=fa) dfs(e[i].y,x,sum+(n-son[e[i].y])*e[i^1].w-son[e[i].y]*e[i].w);
    	if (sum<ans||sum==ans&&ans1>x) ans1=x,ans=sum;
    }
    int main(){
    	n=in();
    	for (rr int i=1;i<=n;i++) son[i]=in();
    	for (rr int i=1;i<n;i++) add(in(),in());
    	for (rr int i=1;i<=n;i++) son[i]=0;
    	dp(1,0,0); dfs(1,0,ans);
    	return !printf("%d\n%lld",ans1,ans);
    }

JZOJ 5786 观察

题目

求离当前点LCA最深的编号(可能会被修改)


分析

这道题也就是求欧拉序的前驱和该点,和后继,可以用线段树维护,然后求深度更大的点的编号


代码

    #include <cstdio>
    #include <cstring>
    #define rr register
    #define f(i,a,b) for (rr int i=a;i<=b;++i)
    #define min(a,b) ((a)<(b)?(a):(b))
    #define max(a,b) ((a)>(b)?(a):(b))
    #define N 800005
    bool v[N],op; int top,dep[N],ne[N],ls[N],sta[N],ola[N],rk[N],st[N],dfn[N],son[N];
    int n,q,bas=1,tot,father[N],minx[2100001],maxx[2100001]; char buf[1<<13],*p1,*p2;
    inline int in(bool &op){
    	rr int ans=0; rr char c=getchar();
    	while ((c<48||c>57)&&c!='-') c=getchar();
    	if (c=='-') op=1,c=getchar();
    	while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
    	return ans;
    }
    inline void print(int ans){
    	if (ans>9) print(ans/10);
    	putchar(ans%10+48);
    }
    inline int lca(int x,int y){
    	while (son[x]!=son[y]) if (dep[son[x]]>dep[son[y]]) x=father[son[x]]; else y=father[son[y]];
    	return dep[x]<dep[y]?x:y;
    }
    signed main(){
    	n=in(op); q=in(op); while ((bas<<=1)<n+2); bas--;
    	f(i,2,n) father[i]=in(op); dep[1]=1;
    	f(i,2,n) ne[i]=ls[father[i]],ls[father[i]]=i;
    	f(i,1,n) sta[i]=ls[i]; st[top=1]=1;
    	while (top){//第一次深搜求深度和子树大小以及通过子树大小求欧拉序
    		rr int x=st[top];
    		if (!dfn[x]) son[rk[dfn[x]=++tot]=x]=1;
    		if (sta[x]){
    			st[++top]=sta[x];
    			dep[sta[x]]=dep[x]+1;
    			sta[x]=ne[sta[x]];
    			continue;
    		}
    		son[st[--top]]+=son[x];
    		if (son[ola[st[top]]]<son[x]) ola[st[top]]=x,v[st[top]]=1;
    	}
    	f(i,1,n) sta[i]=ls[i],son[i]=0;
    	st[top=1]=1; rr int ST=1;
    	while (top){//现在的son表示的已经是倍增的祖先了
    		rr int x=st[top];
    		if (!son[x]) son[x]=ST;
    		if (v[x]){
    			v[x]=0,st[++top]=ola[x];
    			continue;
    		}
    		if (sta[x]==ola[x]) sta[x]=ne[sta[x]];
    		if (sta[x]){
    			ST=st[++top]=sta[x];
    			sta[x]=ne[sta[x]];
    			continue;
    		}
    		top--;
    	}
    	f(i,1,(bas<<1|1)) minx[i]=n+1,maxx[i]=0;//zkw线段树初始化
    	while (q--){
    		rr int x=in(op=0),y=dfn[x]+bas;//跳到叶子节点
    		if (op){
    			rr int ans;
    			if (minx[y]==y-bas) ans=x;//直接找到答案
    			else{
    				rr int l=0,r=n+1;
    				for (rr int i=y>>1;i;y=i,i>>=1) if (i<<1==y) r=min(r,minx[y^1]); else l=max(l,maxx[y^1]);//查找前驱和后继
    				if (l) l=lca(rk[l],x);
    				if (r<=n) r=lca(x,rk[r]);
    				ans=dep[l]<dep[r]?r:l;//深度更深
    			}
    			if (ans) print(ans); else putchar(48); putchar(10);
    		}
    		else{
    			if (minx[y]==y-bas) minx[y]=n+1,maxx[y]=0; else minx[y]=maxx[y]=y-bas;//修改该点的最小值和最大值
    			for (rr int i=y>>1;i;y=i,i>>=1) minx[i]=min(minx[y],minx[y^1]),maxx[i]=max(maxx[y],maxx[y^1]); //单点修改
    		}
    	}
    	return 0;
    }

后续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值