COCI CONTEST #3 29.11.2014 T6 KAMIONI

第六题:
题目描述
A国的城市可以看做是数轴上的整数点,有n辆卡车在公路上往返行驶,它们只能在到达某个城市才能掉头,掉头是不需要时间的,而且卡车在途中不能停下来。这n辆卡车初始时可能位于不同的城市,在某个时刻它们同时出发,每辆卡车每分钟可以到达下一个城市,即每分钟可以行使单位1的路程。当卡车到达终点时,它将会进入时空隧道,瞬间消失。给出每辆卡车的行驶的路径,即卡车的起点、中途每次掉头的点及终点,有m次查询,每次查询指定两辆卡车,问它们一共相遇多少次?
注意:卡车相遇点不一定是城市。另外,相遇时其中一辆卡车进入时光隧道不算相遇;起始瞬间两辆卡车位于同一地点不算相遇;其中一辆卡车正好掉头不算相遇。
n<=100000,m<100000,城市的编号在[1,10^9]之间。每辆卡车路径掉头的次数不超过3*10^5;所有卡车掉头的总次数之和不超过3*10^5.
输入:第一行两个整数n,m,表示卡车的数量和查询的次数。
接下来n行,每行的第一个数为Ki(2<=Ki<=3*10^5),表示卡车的路径包含Ki个点,接下来有Ki个数,每个数在区间[1,10^9]之间,按顺序表示卡车行驶时经过的城市,包括起点和终点。
所有Ki之和不超过3*10^5
接下来m行,每行两个整数,表示查询这两辆卡车一共相遇多少次。
输出:
对每次查询,输出一个整数,表示它们相遇次数。
50%的数据,n<100,Ki<1000,m<1000
50分,因为考试的时候奔着50分去的。50分代码还是很好想的,直接暴力。存储数据需要一些技巧,或者直接STL的vector,链表等等,不然内存开不下。
下面说一下我后来A这个题的过程。
官方给的解法:
1.预处理出所有的转折点,包含以下信息:
卡车编号,转折点编号,时间,方向
并根据时间排序。
2.预处理所有的查询,就是假设一辆车的编号是i,就把跟i有查询关系的其他车全部保存下来(可以用链表),保存的时候保证i的转折点个数小于保存的那些车的转折点个数,如果i的转折点比j的多,那么就不管了,反正在j那里会保存的。这个是基于一个推论,考虑两辆车是否相遇,只用考虑其中一辆车的转折点,另外一辆可以不管。这个比较显然,略去不证。同时利用这个推论可以简化那个50分代码。
3.取出来一个转折点,然后处理和这个转折点的卡车编号有查询关系的卡车的位置。
下面计算时间复杂度。
转折点肯定有300000个。
如果某个卡车的转折点小于sqrt(300000)个,虽然和它有查询关系的卡车多于sqrt(300000)个,但是它在转折点数组中一定小于sqrt(300000)次出现。
如果某个卡车的转折点多于sqrt(300000)个,虽然它在转折点数组中一定多于sqrt(300000)次出现,但是和它有查询关系的卡车一定小于sqrt(300000)个。
因此总的时间复杂度也就是O(n*sqrt(n))级别的。
然后我想看看标程代码,却发现这代码写得实在太飘逸太难以阅读,而且还编译不过,没法调试,于是我放弃了。。
看到个做这次考试AK的人的代码,思路不一样,代码看上去比较短,结果我又跳进了另外一个深坑。
1.先记录每个车的转折点,包含两个信息。first是总路程*2(就是总时间*2),second是向左走的路程*2+最初的坐标。存储数据还是vector,或者链表等等。
2.对于每一次查询:选择一个转折点少的作为标准a。维护prvx=v[a][0].first,nowx=v[a][i].first.表示a车前时刻和后时刻的总路程。
然后维护aprvy=v[a][0].second,anowy=v[a][i].second,bprvy=v[b][0].second.(i从1开始枚举)
然后计算bnowy,bnowy就是一个二分查找,找一个pos,使得v[b][pos].first < nowx < v[b][pos+1].first
说白了就是找一个b的总路程要小于a的总路程的b的转折点
那么b在pos这个点时向左走的路程就是v[b][pos].second
然后看b在pos这个点是向左走的还是向右走的。
如果是向右走的,对向左走的总路程没有影响。
如果是向左走的,那么在a的第i个转折点时,b向左走的总路程bnowy就可以求出来了。就是v[b][pos].second+now_x-v[b][pos].first。
然后再看:
aprvy,bprvy,anowy,bnowy的关系。
如果a在前一步,向左走的路程比b多,在后一步向左走的路程却比b少,那么就是一个相遇。
如果a在前一步,向左走的路程比b少,在后一步向左走的路程却比b多,那么也是一个相遇。
然后你可能会觉得这个数据处理得好奇葩
Q:为什么向左走的路程要加坐标?
A:这问题很简单,自己多想一想。
Q:为什么路程要*2,为什么不*3?
A:考虑方向:如果两个车方向一样,那么*2也没有关系;如果两个车方向相反,那么一个车相对于另外一个车就是路程两倍了。因为只考虑向左走的路程,其实它并不能刻画是否相遇。所以乘以2。如果在某一个转折点,a车的second没有增加,其实a车这个时候是向右走了,坐标在减小,为了不让坐标减小(计算麻烦),因此b车的second增加了2倍(反正速度一样)。
Q:为什么这个处理这么复杂?
A:为了维护一个时间的单调性方便二分。其实second存放a车的实际位置,然后不*2,我觉得也是可行的。毕竟二分查找只在总路程那里。最后的坐标处理两者相比都是O(1)的。
最后3个注意的点:
1.如果nowx>=b车的最大路程,这里要特判
2.循环完了一次之后,nowx=prvx,anowy=aprvy,bnowy=bprvy
3.用map保存每次询问的答案,要是答案已经算出来了就不要算第二次
分析时间复杂度:
总共的转折点300000个,查询100000次
一次查询的时间复杂度是O(minv*logmaxv)
minv是较少的那个转折点
maxv是较多的那个
如果minv小于sqrt(300000),那么minv最多出现100000次,maxv小于300000,logmaxv也就不大了。
如果minv大于sqrt(300000),那么minv最多出现sqrt(300000)次,同样时间复杂度不高。
粗略算来要比上面的方法多一个logmaxv。
但是实际情况maxv取不到300000。
最终还是可以过。
但是没有map存答案就会超时= =

#include<vector>
#include<cstdio>
#include<iostream>
#include<map>
#define ll long long
#define pll pair<ll,ll>
using namespace std;
inline int abs(int x)
{
    return x>0?x:-x;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    vector<vector<pll > > v(n);
    map<pair<int,int>,int>ma;
    for(int i=0;i<n;++i)
    {
        int k,prv;
        ll x=0,y;
        bool flag;
        scanf("%d",&k);
        for(int j=0;j<k;++j)
        {
            int now;
            scanf("%d",&now);
            if(j==0)
            {
                y=prv=now;
                v[i].push_back(pll(x,y));
                continue;
            }
            if(j==1)now>prv?flag=0:flag=1;
            int len=abs(now-prv)*2;
            x+=len;
            if(flag^(j&1))y+=len;
            v[i].push_back(pll(x,y));
            prv=now;
        }
    }
    for(int i=0;i<m;++i)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        a--,b--;
        if(ma.find(pair<int, int>(a, b))!=ma.end())
        {
            printf("%d\n",ma[pair<int, int>(a, b)]);
            continue;
        }
        if(v[a].size()>v[b].size())swap(a,b);
        int a_sz=v[a].size(),b_sz=v[b].size(),pos=0,ans=0;
        ll prv_x=v[a][0].first,a_prv_y=v[a][0].second,b_prv_y=v[b][0].second;
        for(int j=1;j<a_sz;++j)
        {
            ll now_x=v[a][j].first,a_now_y=v[a][j].second;
            if(now_x>v[b][b_sz-1].first)
            {
                int flag=(a_now_y-a_prv_y)/(now_x-prv_x);
                ll num=v[b][b_sz-1].first-prv_x;
                now_x=v[b][b_sz-1].first;
                a_now_y=a_prv_y+flag*num;
            }
            int l=pos,r=pos+1;
            while(v[b][r].first<now_x)
            {
                int rr=r+(r-l)*2;
                l=r;
                r=min(b_sz-1,rr);
            }
            while(l+1<r)
            {
                int mid=(l+r)/2;
                if(v[b][mid].first<now_x)l=mid;
                else r=mid;
            }
            pos=l;
            int b_flag=(v[b][pos+1].second-v[b][pos].second)/(v[b][pos+1].first-v[b][pos].first);
            ll b_now_y=v[b][pos].second+b_flag*(now_x-v[b][pos].first);
            if(a_prv_y<b_prv_y&&a_now_y>b_now_y)ans++;
            if(a_prv_y>b_prv_y&&a_now_y<b_now_y)ans++;
            b_prv_y=b_now_y;
            a_prv_y=a_now_y;
            prv_x=now_x;
            if(now_x>=v[b][b_sz-1].first)break;
        }
        printf("%d\n",ans);
        ma[pair<int, int>(a, b)]=ma[pair<int, int>(b, a)]=ans;
    }
}

你可能感兴趣的:(COCI CONTEST #3 29.11.2014 T6 KAMIONI)