来源:NOIP2012提高组Day1
题意:
有n个城市,第i个城市可以通向所有j城市(j>i),每个城市有一个海拔h[i],两个城市之间的距离定义为d(x,y)=abs(h[x]-h[y])。
有两个人,小A和小B轮流开车,小A第一个开。
小A的开车习惯是开到与当前城市距离次小的城市。
小B的开车习惯是开到与当前城市距离最小的城市。
如果轮到某个人的时候他们开不下去了,或者开去的代价会使得总代价大于x,那么就停止驾驶。
首先给出一个x[0],求出从哪个城市出发使得cost[a]/cost[b]最小。注意如若cost[b]=0,那么这个值视为无穷大,且两个无穷大是一样大的。如果存在比值相等的情况,那么输出海拔最高的那个城市编号。
然后给出m个询问。
求对于s[i],x[i],从s[i]出发,总代价不超过x[i],a,b各走了多少距离。
注意:
1.如果对于某个城市有两个城市与它的距离一样大,那么认为海拔较低的离它近。
2.城市的海拔各不相同。
n<=100000,m<=10000,x[i],h[i]在int范围内,1<=s[i]<=n
分析
题面好长
首先显而易见的可以发现对于同一个点同一个人,走到的结果肯定是一样的。x对走向的下一个点是没有影响的。也就是说从某个点开始走的路径是唯一的,只是x决定了这条路能走多长。
第一问我没有想到什么特别好的做法,只能选择暴力枚举每一个点来找最优的。
对于70%的数据的做法。
n<=1000,m<=10000
可以用O(
n2
)的方法求出对于每个点向后走向的最小点和次小点是哪个,然后对于第一问直接枚举每个点,然后对于每个(s,x)都用O(n)的跑一次。事实上时间会比n要小一些不过还是肯定会T的根本不用想…第二问是同理的。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 100005
#define ll long long
#define pll pair<long long,long long>
#define A first
#define B second
#define mp make_pair
using namespace std;
void read(int &x){
x=0; char c=getchar(); int f=1;
for (; c<'0'; c=getchar())if (c=='-')f=-1;
for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
x*=f;
}
int h[M],n,m,X[M],S[M];
struct AAA{
ll mx[M],mx1[M];
pll get(ll s,ll x){
// printf("---%d %d---\n",s,x);
// printf("%d",s);
// printf("%lld %lld\n",s,x);
ll coa=0,cob=0,nex,co;
bool f=0;
for (; x&&s;){
if (f){
nex=mx[s]; co=abs(h[nex]-h[s]);
if (!nex){x=0; continue;}
if (nex&&co<=x)x-=co,cob+=co;
else x=0;
s=nex;
}
else{
nex=mx1[s]; co=abs(h[nex]-h[s]);
if (!nex){x=0; continue;}
if (nex&&co<=x)x-=co,coa+=co;
else x=0;
s=nex;
}
f=!f;
}
return mp(coa,cob);
}
void solve(){
int i,j;
for (i=1; i<=n; i++){
mx[i]=mx1[i]=0;
for (j=i+1; j<=n; j++){
int d=abs(h[i]-h[j]);
if (mx[i]==0||d<abs(h[i]-h[mx[i]])||(d==abs(h[i]-h[mx[i]])&&h[j]<h[mx[i]])){mx1[i]=mx[i]; mx[i]=j;}
else if(mx1[i]==0||d<abs(h[i]-h[mx1[i]])||(d==abs(h[i]-h[mx1[i]])&&h[j]<h[mx1[i]]))mx1[i]=j;
}
// printf("%lld %lld\n",mx[i],mx1[i]);
}
ll res=-1;
ll ca=0,cb=0;
for (i=1; i<=n; i++){
pll now=get(i,X[0]);
if (now.B){if (res==-1||now.A*cb<now.B*ca||(now.A*cb==now.B*ca&&h[i]>h[res])){res=i;ca=now.A;cb=now.B;}}
else {if (res==-1||(h[i]>h[res]&&cb==0))res=i;}
}
printf("%lld\n",res);
for (i=1; i<=m; i++){
pll now=get(S[i],X[i]);
printf("%lld %lld\n",now.A,now.B);
}
}
}p70;
int main(){
int i;
// freopen("drive.in","r",stdin);
// freopen("drive.out","w",stdout);
read(n);
for (i=1; i<=n; i++)read(h[i]);
read(X[0]);
read(m);
for (i=1; i<=m; i++)read(S[i]),read(X[i]);
p70.solve();
return 0;
}
对于100%的数据的做法
首先来看一下上面的做法,复杂度主要是在两个方面,一个是对于每一个点求之后的最小值和次小值,一个是对于每个(s,x)都要用O(n)的复杂度来模拟,太费时间了。
对于第一个求最小值和次小值的优化。似乎有两种方法。
1.用数据结构维护,快速的找比它小的最大的点和比它大的最小的点,还有次大和次小。不过我并没有写这个。
2.链表,排序依次然后将城市依次按海拔顺序连起来,每次求完一个点就把它给删了。且每个点的答案只会在它的左节点,左节点的左节点,右节点和右节点的右节点中出现,判断一下即可。
对于第二个的优化。可以采用倍增的方法。
设to[x][k]表示从x出发,走了
2k
轮后到达的位置。
co[x][k].A表示从x出发,走了
2k
轮后小A的路程。
co[x][k].B表示从x出发,走了
2k
轮后小B的路程。
然后模拟的复杂度就降为O(log n)了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 100005
#define ll long long
#define pll pair<long long,long long>
#define A first
#define B second
#define inf (1e18)
#define mp make_pair
using namespace std;
void read(ll &x){
x=0; char c=getchar(); int f=1;
for (; c<'0'; c=getchar())if (c=='-')f=-1;
for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
x*=f;
}
ll h[M],to[M][18],n,m,X[M],S[M],Mp[M];
pll co[M][18];
struct AC{ll x,ne,be;}c[M];
pll b[M],a[M];
void Max(ll &x,ll y){if (x<y)x=y;}
void Min(ll &x,ll y){if (x>y)x=y;}
int ch(int x){
//二分查找 用于离散(似乎手写比lb要快...)
int l=1,r=n,mid;
for (; ;){
mid=(l+r)>>1;
if (b[mid].A==x)return mid;
if (b[mid].A<x)l=mid+1;
else r=mid-1;
}
}
void del(int x){
//删除链表中的节点
c[c[x].be].ne=c[x].ne;
c[c[x].ne].be=c[x].be;
c[x].ne=c[x].be=0;
c[0].be=c[0].ne=c[n+1].be=c[n+1].ne=0;
}
bool ch(int y,int z,int x){
if (z==0)return 0;
if (y==0||y==n+1)return 1;
x=h[x]; y=h[y]; z=h[z];
int l1=abs(b[y].A-b[x].A),l2=abs(b[z].A-b[x].A);
if (l1<l2)return 0;
if (l1>l2)return 1;
//对于距离相等的情况,海拔低的比较近
return y>z;
}
pll get(ll s,ll x){
ll ca=0,cb=0,k;
//倍增模拟
for (k=16; k>=0&&x&&s; k--)if (to[s][k]&&co[s][k].A+co[s][k].B<=x){
x-=co[s][k].A+co[s][k].B;
ca+=co[s][k].A;
cb+=co[s][k].B;
s=to[s][k];
}
//小A可能可以再走一次
if (s&&co[s][0].A<=x){x-=co[s][0].A; ca+=co[s][0].A;}
return mp(ca,cb);
}
int main(){
int i;
// freopen("drive.in","r",stdin);
// freopen("drive.out","w",stdout);
read(n);
for (i=1; i<=n; i++)read(h[i]),b[i].A=h[i],b[i].B=i;
sort(b+1,b+n+1);
for (i=1; i<=n; i++)h[i]=ch(h[i]),Mp[h[i]]=i;
c[0].x=inf; c[n+1].x=inf;
for (i=1; i<=n; i++){
c[b[i].B].x=i;
c[b[i].B].be=b[i-1].B;
c[b[i].B].ne=b[i+1].B;
}
int be1,be2,ne1,ne2;
for (i=1; i<=n; i++){
be1=c[i].be;
be2=c[c[i].be].be;
ne1=c[i].ne;
ne2=c[c[i].ne].ne;
int res1=0,res2=0;
if (ch(res1,be1,i)){res2=res1; res1=be1;}else if (ch(res2,be1,i))res2=be1;
if (ch(res1,be2,i)){res2=res1; res1=be2;}else if (ch(res2,be2,i))res2=be2;
if (ch(res1,ne1,i)){res2=res1; res1=ne1;}else if (ch(res2,ne1,i))res2=ne1;
if (ch(res1,ne2,i)){res2=res1; res1=ne2;}else if (ch(res2,ne2,i))res2=ne2;
if (res1>n)res1=0; if (res2>n)res2=0;
a[i].A=res1; a[i].B=res2;
del(i);
}
b[0].A=inf; b[n+1].A=inf;
for (i=1; i<=n;i++){
to[i][0]=a[a[i].B].A;
co[i][0].A=abs(b[h[i]].A-b[h[a[i].B]].A);
co[i][0].B=abs(b[h[a[i].B]].A-b[h[a[a[i].B].A]].A);
}
int k;
for (k=1; k<17; k++){
for (i=1; i<=n; i++){
to[i][k]=to[to[i][k-1]][k-1];
co[i][k].A=co[i][k-1].A+co[to[i][k-1]][k-1].A;
co[i][k].B=co[i][k-1].B+co[to[i][k-1]][k-1].B;
}
}
ll x,s;
read(x);
int res=-1;
ll ca=0,cb=0;
for (i=1; i<=n; i++){
pll now=get(i,x);
if (now.B){if (res==-1||now.A*cb<now.B*ca||(now.A*cb==now.B*ca&&h[i]>h[res])){res=i;ca=now.A;cb=now.B;}}
else {if (res==-1||(h[i]>h[res]&&cb==0))res=i;}
}
printf("%d\n",res);
read(m);
for (i=1; i<=m; i++){
read(s); read(x);
pll now=get(s,x);
printf("%lld %lld\n",now.A,now.B);
}
return 0;
}
吐槽
码量好像有点大啊…而且再加上前一题有个高精度除法…我估计比赛就算知道正解也未必敲的出来
而且因为嫌麻烦好多不需要long long的也用long long了…不过实在是懒得修改了。