不要以为普及组的题就很和谐。。
勇者斗恶龙
一、题目
现在有一条 n 头龙,生命值为 h,勇士想要打败这条作恶多端的龙。
勇士攻击第 i 个头会造成 min(h,atki) 点伤害,即龙的生命值减少 min(h,atki) 点;但龙的第 i 个头受到伤害后会恢复 di 点生命值,即生命值增加 di 。 勇士无法重复攻击同一个头,即他对于每个头最多只能攻击一次。当龙的生命值为 o 时则视为被勇士打败,此时它不能再恢复生命值了。 勇士想要知道他是否能打败这头龙,如果能打败最少需要攻击多少次。
二、解法
0x01 错解及思考
首先先讲一个错解,虽然它是错的,但是可以启发我们。
发现每次攻击完后伤害都是
a
i
−
b
i
ai-bi
ai−bi(除了最后一击),我们就可以把
a
i
−
b
i
ai-bi
ai−bi排序,然后贪心取答案。
这个解法有一个局限,就是最后一击是与众不同的,看下面一组反例:
2 1050
50 0
1000 900
如果按上述的贪心算法,那么是得不到答案的,但是我们先取第一击,就可以得到答案。
0x02 贪心的改进
上面的贪心还是比较优秀,现在考虑改进它,我们可以将最后一击拿来枚举,记
v
i
=
a
i
−
b
i
vi=ai-bi
vi=ai−bi,将
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);
}