[NOIP2012] 开车旅行
戳我
做了快一天了。。。qwq
这个题写着真的难受。。。。上午打了快两个小时的暴力(天啊。。我竟然写了这么久)拿到了70分。。。
这个是我的暴力代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 1010
#define MAXM 10010
using namespace std;
int n,x0,cnt,m;
int s[MAXM],x[MAXM],ansa[MAXM],ansb[MAXM],ans[MAXM],dist[MAXN][MAXN],aa[MAXM],bb[MAXM];
struct Node{int id,h,pos1=0,pos2=0;}node[MAXN];
bool cmp(struct Node x,struct Node y)
{
if(x.h<y.h) return 1;
else return 0;
}
inline void solve(int now,int check,int disa,int disb,int limit)
{
//printf("now=%d check=%d disa=%d disb=%d limit=%d\n",now,check,disa,disb,limit);
if(check==1) //it's time for pos2
{
int to=node[now].pos2;
// printf("A:to=%d dist=%d\n",to,dist[now][to]);
if(to==0||dist[now][to]+disa+disb>limit)
{
ansa[++cnt]=disa;
ansb[cnt]=disb;
return;
}
solve(to,check^1,disa+dist[now][to],disb,limit);
}
else//it's time for pos1
{
int to=node[now].pos1;
// printf("B:to=%d dist=%d\n",to,dist[now][to]);
if(to==0||dist[now][to]+disa+disb>limit)
{
ansa[++cnt]=disa;
ansb[cnt]=disb;
return;
}
solve(to,check^1,disa,disb+dist[now][to],limit);
}
}
int main()
{
//freopen("ce.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&node[i].h);
scanf("%d%d",&x0,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&s[i],&x[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dist[i][j]=dist[j][i]=abs(node[i].h-node[j].h);
//check out the distance betweeen two points
for(int i=1;i<=n;i++)
{
int minn1=2147483647,minn2=2147483647;
//cout<<i<<endl;
for(int j=i+1;j<=n;j++)
{
if(dist[i][j]<minn1||(dist[i][j]==minn1&&node[j].h<node[node[i].pos1].h))
{
minn2=minn1,node[i].pos2=node[i].pos1;
minn1=dist[i][j],node[i].pos1=j;
}
else if(dist[i][j]<minn2||(dist[i][j]==minn2&&node[j].h<node[node[i].pos2].h))
minn2=dist[i][j],node[i].pos2=j;
//printf("j=%d minn1=%d pos1=%d minn2=%d pos2=%d\n",j,minn1,node[i].pos1,minn2,node[i].pos2);
}
}//find out the position of the minn1 and minn2
//cout<<endl;
//for(int i=1;i<=n;i++)
// printf("i=%d pos1=%d pos2=%d\n",i,node[i].pos1,node[i].pos2);
for(int i=1;i<=m;i++)
solve(s[i],1,0,0,x[i]);
memcpy(aa,ansa,sizeof(ansa));
memcpy(bb,ansb,sizeof(ansb));
memset(ansa,0,sizeof(ansa));
memset(ansb,0,sizeof(ansb));
cnt=0;
for(int i=1;i<=n;i++)
solve(i,1,0,0,x0);
int pos_ans,cur_h;
double min_ans=1e10;
//cout<<"cnt="<<cnt<<endl;
for(int i=1;i<=cnt;i++)
{
if(ansb[i]==0) continue;
//printf("from=%d %.6lf\n",i,(double)ansa[i]/ansb[i]);
if((double)ansa[i]/ansb[i]<min_ans)
min_ans=(double)ansa[i]/ansb[i],pos_ans=i,cur_h=node[i].h;
else if((double)ansa[i]/ansb[i]==min_ans&&node[i].h>cur_h)
pos_ans=i,cur_h=node[i].h;
//printf("pos_ans=%d\n\n",pos_ans);
}
printf("%d\n",pos_ans);
for(int i=1;i<=m;i++)
printf("%d %d\n",aa[i],bb[i]);
return 0;
}
其他的点又MLE又TLE的,反正是GG了。
所以我们要考虑优化。。。。。什么优化?一看数据范围1e5。。。那么考虑O(nlogn)的做法——自然是倍增了。
在经过仔细思考之后在看了题解之后——
我们可以注意到A和B交替开车,就可以将他们两个各开一次的看作一次,状态显然可以推移合并。
这个题难在预处理。。。。我们需要找每个点的最近城市和次近城市。这个找前驱后继的自然是可以排序(+离散化)之后用双向链表做。。但是蒟蒻我不太会,所以我们可以选择插入,查询复杂度为O(logn)set+lower_bound来寻找。。。。
看一个daolao用了multiset,开始重复插入四个极值来避免寻找的时候出界导致RE,感觉是个不错的做法·,就学习了一下。
我们设\(f[i][j][0(A)/1(B)]\)为A/B从第i个城市出发,走\(2^j\)天到达的城市(注意A,B交替开车)
然后\(sum_a[i][j][0(A)/1(B)]\)为A/B从第i个城市出发,走\(2^j\)天,其中A走的路程。
\(sum_b[i][j][0(A)/1(B)]\)为A/B从第i个城市出发,走\(2^j\)天,其中B走的路程。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#define MAXN 100010
using namespace std;
int n,x0,m;
int si[MAXN],xi[MAXN],height[MAXN],f[MAXN][22][2],sum_a[MAXN][22][2],sum_b[MAXN][22][2];
struct Node{
int id,h;
friend bool operator<(Node x,Node y){
return x.h<y.h;
}
};
multiset<Node>s;
inline void solve(int S,int &dist_a,int &dist_b,int limit)
{
int now=S;
for(int i=20;i>=0;i--)
{
if(f[now][i][0]&&sum_a[now][i][0]+sum_b[now][i][0]+dist_a+dist_b<=limit)
{
dist_a+=sum_a[now][i][0];
dist_b+=sum_b[now][i][0];
now=f[now][i][0];
}
}
}
inline void init()
{
for(int i=n;i>=1;i--)
{
int to_a,to_b,nxt_pos,nxt_height,pre_pos,pre_height;
Node cur;
cur.id=i; cur.h=height[i];
s.insert(cur);
multiset<Node>::iterator it=s.lower_bound(cur);
it++;
nxt_pos=(*it).id; nxt_height=(*it).h;
it--,it--;
pre_pos=(*it).id; pre_height=(*it).h;
it++;
//因为我们已经排序过了(set自带从小到大排序功能,我们也在结构体里面定义过了)
//易知当前节点的最近和次近城市就在他的-2,-1,+1,+2的地方qwq
//所以我们只需要处理判断这四个点就可以了qwq
if(abs(nxt_height-height[i])<abs(pre_height-height[i]))
{
it++,it++;
to_b=nxt_pos;
if(abs(pre_height-height[i])>abs((*it).h-height[i])) to_a=(*it).id;
else to_a=pre_pos;
}
else
{
it--,it--;
to_b=pre_pos;
if(abs(nxt_height-height[i])>=abs((*it).h-height[i])) to_a=(*it).id;
else to_a=nxt_pos;
}
//这里需要注意等于号的使用,考虑到我们已经按照高度排序过了,所以有的等于号需要加,有的不能加
f[i][0][0]=to_a;
f[i][0][1]=to_b;
sum_a[i][0][0]=abs(height[to_a]-height[i]);
sum_b[i][0][0]=0;
sum_b[i][0][1]=abs(height[to_b]-height[i]);
sum_a[i][0][1]=0;
}
for(int i=1;i<=n;i++)
{
f[i][1][0]=f[f[i][0][0]][0][1];
f[i][1][1]=f[f[i][0][1]][0][0];
sum_a[i][1][0]=sum_a[i][0][0];
sum_b[i][1][1]=sum_b[i][0][1];
sum_a[i][1][1]=abs(height[f[i][1][1]]-height[f[i][0][1]]);
sum_b[i][1][0]=abs(height[f[i][1][0]]-height[f[i][0][0]]);
}
//我们前面处理的是只走了一天的情况,但是因为我们要按照A,B两人交替开车一天视为一次来进行倍增
//所以我们也需要把走了2^1天的情况处理出来
for(int k=2;k<=20;k++)
{
for(int i=1;i<=n;i++)
{
f[i][k][0]=f[f[i][k-1][0]][k-1][0];
f[i][k][1]=f[f[i][k-1][1]][k-1][1];
sum_a[i][k][0]=sum_a[i][k-1][0]+sum_a[f[i][k-1][0]][k-1][0];
sum_b[i][k][0]=sum_b[i][k-1][0]+sum_b[f[i][k-1][0]][k-1][0];
sum_a[i][k][1]=sum_a[i][k-1][1]+sum_a[f[i][k-1][1]][k-1][1];
sum_b[i][k][1]=sum_b[i][k-1][1]+sum_b[f[i][k-1][1]][k-1][1];
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&height[i]);
height[0]=2147483627; height[n+1]=-2147483627;
Node cur1;
cur1.h=2147483627;
cur1.id=0;
Node cur2;
cur2.h=-2147483627;
cur2.id=n+1;
s.insert(cur1),s.insert(cur1);
s.insert(cur2),s.insert(cur2);
//这个就是上面提到的小技巧
init();
scanf("%d%d",&x0,&m);
int cur_ans=0;
double ans=1e15;
for(int i=1;i<=n;i++)
{
int dist_a=0,dist_b=0;
solve(i,dist_a,dist_b,x0);
if(dist_b==0)
{
if(ans>1e14) cur_ans=i,ans=1e14;
else if(ans==1e14&&height[i]>height[cur_ans]) cur_ans=i;
}
else
{
double kkk=(double)dist_a/dist_b;
if(kkk<ans) ans=kkk,cur_ans=i;
else if(kkk==ans&&height[i]>height[cur_ans]) cur_ans=i;
}
}
printf("%d\n",cur_ans);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&si[i],&xi[i]);
int dist_a=0,dist_b=0;
solve(si[i],dist_a,dist_b,xi[i]);
printf("%d %d\n",dist_a,dist_b);
}
return 0;
}