首师大附中集训第十一天综合测试

本文精选三道算法竞赛题目,包括选址开店问题、路径可达性判断及积木塔稳定性分析,详细解析了每题的算法思路与代码实现,是算法学习者不可多得的参考资料。

正题

      第一题:Door Street 是一条繁华的街道,沿街一共有n栋大楼,编号依次为 1...n,相邻两栋楼编号相邻。 现在你想在 Door Street 开一家咖啡厅,你可选址在任意一栋楼内。每栋楼都有一个消费指数 c_i。若你 选址在第x号楼,则第i号楼的人在你的咖啡厅消费max(0,c_i-(x-i)^2) ,你的收入是 n 栋大楼的 人的消费总和\sum_{i=1}^{n}max(0,c_i-(x-i)^2),即 。 对于每栋大楼,你想评估出你若选址在该大楼,你的收入分别是多少?

      发现一个点只对周围的一些点产生贡献,这个范围很容易算。

      把贡献展开,就可以发现

      c_i-i^2+2xi-x^2 \\=(c_i-i^2)+x(2i-x)

      那么前面这个东西差分维护一下,后面的i差分维护一下,个数差分维护一下就做完了。

      不用学习博主的做法,因为这个树状数组是废的。

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define lowbit(x) (x&(-x))
using namespace std;

int n;
long long c[100010];
long long sum[100010],s[100010],num[100010];

void add(long long*a,int x,long long t){
	while(x<=n){
		a[x]+=t;
		x+=lowbit(x);
	}
}

long long get_sum(long long*a,int x){
	long long tot=0;
	while(x){
		tot+=a[x];
		x-=lowbit(x);
	}
	return tot;
}

int main(){
	freopen("addr.in","r",stdin);
	freopen("addr.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
	int tot,l,r;
	for(int i=1;i<=n;i++){
		tot=sqrt(c[i]);
		if(i-tot>=1) l=i-tot;else l=1;
		if(i+tot<=n) r=i+tot;else r=n;
		add(sum,l,c[i]-1ll*i*i);add(sum,r+1,1ll*i*i-c[i]);
		add(s,l,2*i);add(s,r+1,-2*i);
		add(num,l,1);add(num,r+1,-1);
	}
	for(int i=1;i<=n;i++)
		printf("%lld ",get_sum(sum,i)+i*(get_sum(s,i)-get_sum(num,i)*i));
}

      第二题:某地区的地图可以看成是一个 n 个点 m 条带权边的无向图,其中有 k 个点是加油站,而汽车在加油站可以把油箱加满。已知 x 单位的油箱 在不加油的情况下最多能走 x 单位的路程。 现在给定 q 组询问,每次询问能否开一辆油箱容量为 v 单位的汽车从 点 x 走到点 y。 k, n, q ≤ 200000,保证所有点 x、y 均为加油站。

      题目就叫随堂测试,因为这题前几天的课上讲过,题解可以直接看我之前的blog。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;

int n,m,k,q;
struct edge{
	int x,y,next,c;
	bool operator<(const edge q){
		return c<q.c;
	}
}s[800010],now[400010],e[800010];
int first[200010],fir[200010],len;
int p[200010],dis[200010],from[200010],dep[200010];
int f[200010][20],g[200010][20];
int fa[200010];
bool tf[200010];
queue<int> list;

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

void ins(edge*s,int*first,int x,int y,int c){
	s[++len]=(edge){x,y,first[x],c};first[x]=len;
	s[++len]=(edge){y,x,first[y],c};first[y]=len;
}

void SPFA(){
	memset(dis,63,sizeof(dis));
	for(int i=1;i<=k;i++) list.push(p[i]),tf[p[i]]=true,dis[p[i]]=0,from[p[i]]=p[i];
	while(!list.empty()){
		int x=list.front();list.pop();tf[x]=false;
		for(int i=first[x];i!=0;i=s[i].next){
			int y=s[i].y;
			if(dis[y]>dis[x]+s[i].c){
				dis[y]=dis[x]+s[i].c;
				from[y]=from[x];
				if(!tf[y]) tf[y]=true,list.push(y);
			}
		}
	}
}

void dfs(edge*s,int*first,int x,int fa){
	for(int i=first[x];i!=0;i=s[i].next){
		int y=s[i].y;
		if(y==fa) continue;
		f[y][0]=x;g[y][0]=s[i].c;dep[y]=dep[x]+1;
		for(int k=1;k<=19;k++) 
			f[y][k]=f[f[y][k-1]][k-1],g[y][k]=max(g[y][k-1],g[f[y][k-1]][k-1]);
		dfs(s,first,y,x);
	}
}

int get_max(int x,int y){
	int ans=0;
	if(dep[y]<dep[x]) swap(x,y);
	for(int k=19;k>=0;k--) if(dep[f[y][k]]>=dep[x]) 
		ans=max(ans,g[y][k]),y=f[y][k];
	if(x==y) return ans;
	for(int k=19;k>=0;k--) if(f[y][k]!=f[x][k]){
		ans=max(ans,max(g[x][k],g[y][k]));
		x=f[x][k];y=f[y][k];
	}
	ans=max(ans,max(g[x][0],g[y][0]));
	return ans;
}

int main(){
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	scanf("%d %d",&n,&m);
	int x,y,c,t=0;
	for(int i=1;i<=m;i++) scanf("%d %d %d",&x,&y,&c),ins(s,first,x,y,c);
	scanf("%d",&k);for(int i=1;i<=k;i++) scanf("%d",&p[i]);
	SPFA();
	for(int i=1;i<=len;i+=2) if(from[s[i].x]!=from[s[i].y])
		now[++t]=(edge){from[s[i].x],from[s[i].y],0,dis[s[i].x]+dis[s[i].y]+s[i].c};
	sort(now+1,now+1+t);
	for(int i=1;i<=n;i++) fa[i]=i;len=0;
	for(int i=1;i<=t;i++){
		int fx=findpa(now[i].x),fy=findpa(now[i].y);
		if(fx!=fy) ins(e,fir,now[i].x,now[i].y,now[i].c),fa[fx]=fy;
	}
	dep[p[1]]=1;dfs(e,fir,p[1],0);scanf("%d",&q);
	while(q--){
		scanf("%d %d",&x,&y);
		printf("%d\n",get_max(x,y));
	}
}

      第三题:建筑需要整体结构来维持,不能只靠地基…… —— LazyJazz [万圣节Party进行时……] LazyJazz号召大家一起来搭积木,搭一座巨高巨高的积木塔。大家一起先构思了一个最终目标,即最终 塔的形状,然后打算分工搭出若干小积木塔,每个小积木塔对应最终塔的某一段结构,最后一个一个摞 起来。 积木塔的最终形态由 n块密度均匀且相等,宽、高相等,长度不定的积木上下摞在一起,宽对齐,从上 至下数第 i块积木覆盖了横坐标对应 L_iR_i的范围(包含 L_i, R_i两点),长度为R_i-L_i 单位长 度。根据前面的描述,可以得出结论,每块积木的质量正比于其长度。 由于是积木塔,所以塔有可能不稳定,稳定的判定条件是:若一个积木塔有 m层,当且仅当对于任意 ,从上至下数,前 i\in [1,m)块积木的重心落在第 i+1块积木的覆盖范围内,则稳定,否则不稳 定。前i块积木的重心为前 i块积木的几何中心,以质量为权重的带权平均位置。

      因为对于一个i满足条件,那么以i为结尾的所有子段都满足条件,所以我们要求的就是i到底端是否满足条件。

      可以直接过一遍获得80分的好成绩,正解:考虑取消自顶向下前i个积木个贡献,对下面的每一个积木是否满足都会有影响,这个影响是可以算出来的,最后上面的改变对于当前积木的判定是一个二元一次不等式的形式,可以直接将这条直线画在李超线段树上,维护即可。

      具体可以看http://lazyjazz.blog.zijian-lv.com/blog/13

      一开始没想到上面对下面的影响,是因为我的物理不好,不能简洁的算出i个积木的中心。所以就化简不了。

      代码就先不写了,贴一个考场80分的。先去学半平面交。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
double l[100010],r[100010],pos[100010],len[100010];
int f[100010];
const double eps=1e-7;

int main(){
	//freopen("bricks.in","r",stdin);
//	freopen("bricks.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) f[i]=1e9;
	for(int i=1;i<=n;i++){
		scanf("%lf %lf",&l[i],&r[i]);
		pos[i]=(l[i]+r[i])/2,len[i]=r[i]-l[i];
	}
	double tpos=0,tlen=0;
	for(int i=n;i>=1;i--){
		tpos=pos[i],tlen=len[i];
		bool we=true;
		for(int j=i+1;j<=n;j++){
			if(tpos+eps<l[j] || r[j]+eps<tpos) {we=false;break;}
			if(f[j]!=1e9) f[i]=min(f[i],max(f[j],j-i));
			tpos+=(pos[j]-tpos)*len[j]/(tlen+len[j]);
			tlen+=len[j];
		}
		if(we==false) f[i]=1e9;
		else f[i]=min(f[i],n+1-i);
	}
	printf("%d\n",f[1]);
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值