[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;
} 
posted @ 2018-10-08 21:00  风浔凌  阅读(331)  评论(0编辑  收藏  举报