2019.07.11【NOIP提高组】模拟 A 组(求上、下凸壳)

博客分享了三道题的解题思路。T1是简单的线段树维护;T2原本以为是网络流,实际是最小生成树,通过新建节点连边求解;T3利用经过x的个数分层SPFA,转化为求上凸壳问题,最后用等差数列求和得出答案。

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

T1:简单的线段树维护。

 

T2:这题正解很简单,几乎所有人都想出来了,但是我想了很久都没思路。

一开始我以为是一个网络流,想了一会发现不会建图。

比赛结束后发现其实就是一个最小生成树。因为我们要把图连成许多个联通块,每一个联通块都要有一个被选中的点,那么我们就新建一个节点,这个节点向其他点连边的边权就是选中那个点的代价。建好新点之后,直接做最小生成树就可以了。

 

总结:以后遇到这种花最小代价使图联通的题目就要往最小生成树方向想。因为最小生成树的定义就是这个。

 

T3:首先利用经过x的个数进行分层SPFA。完了之后我们发现所有可能的最短路都可以表示成kx+b的形式。

那么这题就转化成了有许多条直线,我们要求x在一个范围内的最小值的个数和最小值的和。

再分析一下发现题目要求的就是一个上凸壳。上凸壳的求法:先按k从小到大(从大到小也行,只要是有序的就可以了)排序,然后如果之前两条直线的交点在当前直线的左边,那么就弹栈。

(拓展:求下凸壳。先按k排序,然后加边,不过这次是交点在右边才弹栈。)

最后求答案时要用等差数列求和,不能枚举x(因为x会很大)。

细节较多,贴一下代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define db long double
#define MAXN 510
#define MAXM 10010
using namespace std;

struct map
{
	ll x;
	ll y;
	ll l;
	ll k;
};
map way[MAXM];
struct dot
{
	db px;
	db py;
	db vx;
	db vy;
};
dot c[MAXN];
ll first[MAXN],next[MAXM],dis[MAXN][MAXN],bz[MAXN][MAXN],d[MAXN*MAXN*5][2],n,m,T,sd,td,ans1,ans2;
ll inf,t,g[MAXN],s,lim;
db x,y;
int SPFA()
{
	ll i,j,h,t;
	memset(dis,0x7f,sizeof(dis));inf=dis[0][0];
	memset(bz,0,sizeof(bz));
	dis[sd][0]=0;bz[sd][0]=1;
	d[1][0]=sd;d[1][1]=0;
	h=0;t=1;
	while(h<t)
	{
		h++;
		i=first[d[h][0]];
		while(i>=1&&i<=m)
		{
			if(way[i].k==0)
			{
				if(dis[d[h][0]][d[h][1]]+way[i].l<dis[way[i].y][d[h][1]])
				{
					dis[way[i].y][d[h][1]]=dis[d[h][0]][d[h][1]]+way[i].l;
					if(bz[way[i].y][d[h][1]]==0)
					{
						t++;d[t][0]=way[i].y;d[t][1]=d[h][1];
						bz[d[t][0]][d[t][1]]=1;
					}
				}
			}
			else
				if(d[h][1]<lim)
					if(dis[d[h][0]][d[h][1]]<dis[way[i].y][d[h][1]+1])
					{
						dis[way[i].y][d[h][1]+1]=dis[d[h][0]][d[h][1]];
						if(bz[way[i].y][d[h][1]+1]==0)
						{
							t++;d[t][0]=way[i].y;d[t][1]=d[h][1]+1;
							bz[d[t][0]][d[t][1]]=1;
						}
					}
			i=next[i];
		}
		bz[d[h][0]][d[h][1]]=0;
	}
}
ll read()
{
	ll s=0;
	char x=getchar();
	while(x<'0'||x>'9')
	{
		if(x=='x')return -1;
		x=getchar();
	}
	while('0'<=x&&x<='9')s=s*10+x-'0',x=getchar();
	return s;
}
db cro(db x1,db y1,db x2,db y2){return x1*y2-x2*y1;}
db inter(ll i,ll j)
{
	db c1,c2,g;
	c1=cro(c[i].vx,c[i].vy,c[i].px-c[j].px,c[i].py-c[j].py);
	c2=cro(c[i].vx,c[i].vy,c[j].vx,c[j].vy);
	g=c1/c2;
	x=c[j].px+c[j].vx*g;
	y=c[j].py+c[j].vy*g;
}
db val(db x,ll i){return c[i].py+(x-c[i].px)*c[i].vy;}
ll up(db x)
{
	if(x==(ll)x)return x;
	else return (ll)x+1;
}
ll down(db x){return (ll)x;}
int main()
{
ll i,j,tim,tf;
db lx,ly;
n=read();m=read();
for(i=1;i<=m;i++)
{
	way[i].x=read();way[i].y=read();way[i].l=read();
}
for(i=1;i<=m;i++)
	if(way[i].l==-1)way[i].k=1;
for(i=m;i>=1;i--)next[i]=first[way[i].x],first[way[i].x]=i;
if(n>m)lim=m;
else lim=n;
scanf("%lld",&T);
while(T>=1)
{
	scanf("%lld %lld\n",&sd,&td);
	SPFA();
	tf=0;
	for(i=0;i<=lim;i++)
		if(dis[td][i]!=inf){tf=1;break;}
	if(tf==0){printf("0 0\n");T--;continue;}
	if(dis[td][0]==inf){printf("inf\n");T--;continue;}
	s=0;
	for(i=0;i<=lim;i++)
	{
		if(dis[td][i]==inf)continue;
		s++;
		c[s].px=0;c[s].py=dis[td][i];
		c[s].vx=1;c[s].vy=i;
	}
	t=0;
	for(i=1;i<=s;i++)
	{
		while(t>1)
		{
			inter(g[t],g[t-1]);
			if(cro(c[i].vx,c[i].vy,x-c[i].px,y-c[i].py)>0)t--;
			else break;
		}
		t++;g[t]=i;
	}
	ans1=0;ans2=0;
	lx=1;tim=0;
	for(i=t-1;i>=1;i--)
	{
		inter(g[i+1],g[i]);
		if(up(lx)>down(x))continue;
		tim++;
		ans1=ans1+down(x)-up(lx)+1;
		ans2=ans2+(val(up(lx),g[i+1])+val(down(x),g[i+1]))*(down(x)-up(lx)+1)/2;
		if(tim!=1&&up(lx)==lx){ans1--;ans2-=val(up(lx),g[i+1]);}
		lx=x;
	}
	if(val(down(x),g[2])!=c[g[1]].py){ans1++;ans2=ans2+c[g[1]].py;}
	printf("%lld %lld\n",ans1,ans2);
	T--;
}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值