2019.09.21 多校联合训练(普及组)

不要以为普及组的题就很和谐。。

勇者斗恶龙

一、题目

现在有一条 n 头龙,生命值为 h,勇士想要打败这条作恶多端的龙。

勇士攻击第 i 个头会造成 min(h,atki) 点伤害,即龙的生命值减少 min(h,atki) 点;但龙的第 i 个头受到伤害后会恢复 di 点生命值,即生命值增加 di 。 勇士无法重复攻击同一个头,即他对于每个头最多只能攻击一次。当龙的生命值为 o 时则视为被勇士打败,此时它不能再恢复生命值了。 勇士想要知道他是否能打败这头龙,如果能打败最少需要攻击多少次。

二、解法

0x01 错解及思考
首先先讲一个错解,虽然它是错的,但是可以启发我们。
发现每次攻击完后伤害都是 a i − b i ai-bi aibi(除了最后一击),我们就可以把 a i − b i ai-bi aibi排序,然后贪心取答案。
这个解法有一个局限,就是最后一击是与众不同的,看下面一组反例:
2 1050
50 0
1000 900
如果按上述的贪心算法,那么是得不到答案的,但是我们先取第一击,就可以得到答案。

0x02 贪心的改进
上面的贪心还是比较优秀,现在考虑改进它,我们可以将最后一击拿来枚举,记 v i = a i − b i vi=ai-bi vi=aibi,将 v i vi vi从大到小排序处理出一个前缀和,这就给我们提供了二分的条件,注意特别考虑枚举的最后一击在前缀和中的情况,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

0x3f 代码
记得要特判 h = 0 h=0 h=0的情况。

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int MAXN = 300005; 
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,h,ans=0x3f3f3f3f,rank[MAXN],sum[MAXN],a[MAXN],b[MAXN];
struct node
{
	int v,id;
	bool operator < (node x) const
	{
		return v>x.v;
	}
}s[MAXN];
signed main()
{
	n=read();h=read();
	if(h==0)
	{
		printf("Yes\n0\n");
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		a[i]=read(),b[i]=read();
		s[i]=node{a[i]-b[i],i};
	}
	sort(s+1,s+1+n);
	for(int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+s[i].v;
		rank[s[i].id]=i;
	}
	for(int i=1;i<=n;i++)
	{
		int loc=lower_bound(sum+1,sum+1+n,h-a[i])-sum;
		if(loc>=rank[i])
			loc=lower_bound(sum+1,sum+1+n,h-b[i])-sum;
		if(loc==n+1) continue;
		ans=min(ans,loc+(rank[i]>loc));
	}
	if(ans==0x3f3f3f3f)
		return !puts("No");
	puts("Yes");
	printf("%d\n",ans);
}

徒步旅行

一、题目

小 A 决定开始一场奇妙的徒步旅行,旅行地图可以看成是一个平面直角坐标系,小 A 从家 O(0,0) 出发,每一步移动只能由他此时所在的位置 (x,y) 走到以下四个坐标之一:(x-1,y),(x,y-1),(x+1,y),(x,y+1)。现在有 n 个旅游景点,第 i 个旅游景点位置为(xi,yi)。 由于世界如此之大,整个旅行地图被分成了多个不同的气候区,某个景点(xi,yi)的气候区 Ci=max(xi,yi)。小 A 想要更好的了解这个世界使得他这次徒步旅行更有意义,所以他想要去气候区 i+1 旅行当且仅当访问完气候区 i 的所有旅游景点。当他访问完所有的景点时,他会回到家里。 小 A 想让你帮他设计出一条旅游路线使得他移动的步数最少,因为徒步旅行还是比较累的……

二、解法

对于每一个气候,可以发现它的起点和终点最有的取法一定是两个端点,明白这点后直接 d p dp dp即可。
注意要考虑两个端点间不经过拐点的情况。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int MAXN = 300005;
const int INF = 0x3f3f3f3f;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,dp[MAXN][2];
int abs_(int x)
{
	return x>0?x:-x;
}
struct point
{
	int x,y;
	int  operator * (point oth) const
	{
		return abs_(x-oth.x)+abs_(y-oth.y);
	}
	bool operator < (point oth) const
	{
		return max(x,y)<max(oth.x,oth.y);
	}
}p[MAXN],l[MAXN],r[MAXN];
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		p[i]={read(),read()};
	sort(p+1,p+1+n);
	for(int i=1;i<=n;i++)
	{
		if(p[i-1]<p[i]) 
		{
			l[++m]=p[i];
			r[m]=p[i];
			continue ;
		}
		if(p[i].x==max(p[i].x,p[i].y))
		{
			if(p[i].y>l[m].y) l[m]=p[i];
			if(p[i].y<r[m].y) r[m]=p[i];
		}
		else
		{
			if(p[i].x>r[m].x) r[m]=p[i];
			if(p[i].x<l[m].x) l[m]=p[i];
		}
	}
	for(int i=1;i<=n;i++)
	{
		int dis=l[i-1]*r[i-1];
		dp[i][0]=min(dp[i-1][0]+dis+r[i-1]*l[i],dp[i-1][1]+dis+l[i-1]*l[i]);
		dp[i][1]=min(dp[i-1][0]+dis+r[i-1]*r[i],dp[i-1][1]+dis+l[i-1]*r[i]);
	}
	printf("%lld\n",min(dp[n][0]+point{0,0}*r[n],dp[n][1]+point{0,0}*l[n]));
}

情报机器人

一、题目

点此看题

二、解法

看到这道题,就想到了二分答案,再看能不能生成树,结果只得了80分。
再看题目,发现机器人可以斜着走,所以它并不是一个单调的图像。
再考虑有没有单峰性质呢(其实是有的),下面给出证明:
??????????????????
然后我们可以把它转化成最小生成树问题,用三分解决边权,由于边的个数是 O ( n 2 ) O(n^2) O(n2)的,选用 p r i m prim prim算法,时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define int long long
const int MAXN = 1005;
const int inf = (1ll<<60);
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,a,b,fa[MAXN],dis[MAXN],g[MAXN][MAXN],cnt,ans,res;
int dx[9]={0,1,-1,0,0,1,1,-1,-1};
int dy[9]={0,0,0,1,-1,1,-1,1,-1};
int abs_(int x)
{
	return x>0?x:-x;
}
struct point
{
	int x,y,d;
	int operator * (point oth) const
	{
		return abs_(x-oth.x)+abs_(y-oth.y);
	}
	point make(int t)
	{
		return point{x+t*dx[d],y+t*dy[d],0};
	}
}p[MAXN],s[MAXN];
int find(int x)
{
	if(x^fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
int check(int x)
{
    int t=p[a].make(x)*p[b].make(x)-2*x;
    return t<=0?0:t;
}
void conquer(int l,int r)
{
	while(l<=r)
	{
		int m=(l+r)/2;
		int m1=(l+m)/2,m2=(r+m)/2;
	    int r1=check(m1),r2=check(m2);
	    if(r1==0) res=min(res,m1);
	    if(r2==0) res=min(res,m2);
	    if(r1<=r2)
	        r=m2-1;
	    else
	        l=m1+1;
	}
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
    {
        fa[i]=i;
		p[i]=point{read(),read(),read()};
    }
    for(int i=1;i<=n;i++)
	{
    	for(int j=i+1;j<=n;j++)
    	{
    		res=inf;
    		a=i;b=j;
			conquer(0,2e9);
    		g[i][j]=g[j][i]=res;
		}
	}
	memset(dis,0x3f,sizeof dis);
	dis[1]=0;
	for(int i=1;i<=n;i++)
	{
		int id=0;
		for(int j=1;j<=n;j++)
			if(dis[id]>dis[j] && dis[j]!=-1)
				id=j;
		ans=max(ans,dis[id]);
		dis[id]=-1;
		for(int j=1;j<=n;j++)
			dis[j]=min(dis[j],g[id][j]);
	}
    if(ans==inf) puts("No solutions");
    else printf("%lld\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值