今天考了一场COCI的比赛,感觉题目没考什么特别难的算法,但是有些题就实现很麻烦。
第一题键盘打字,就是个打表题,打了我好大半天,还好没打错。
第二题一群固执的老人看电视,模拟一下谁去换台,换了什么台就可以过了,注意有循环就输出-1
第三题building,一道卡格式的题,给出每个矩形的左下角右上角坐标,让你勾勒整个透视图的轮廓。我想的是从外围跑一次flood fill,能够接触到的矩形的边就是要画出来的了。然后输出的时候注意下标迭代的顺序。这么一道无聊的题花了我好大半天时间,写了100行才写完。
第四题ranking,就是绑定矩形前缀和,然后利用矩形割补法求一下这个人能“完虐”多少人,多少人能“完虐“他,其他的人总是存在合理的安排顺序的,然后注意一下细节就行了。
第五题堆栈,比较有意思,所有的操作要么是复制一个之前出现过的堆栈,然后把当前操作id放在栈顶,要么复制一个之前的堆栈,然后pop,要么比较两个堆栈有几个公共元素。首先明确模拟肯定没法做,因为每一次复制都会增加O(n)的内存,内存很容易就崩了。模拟一下样例就会发现栈里的元素都是递增的,那么建一棵trie树,把每一操作后新产生的堆栈对应到里面的节点即可,公共元素的操作就显然了,在上面做LCA即可。
第五题。在数轴上有许多城市,相邻两城市距离为1,N辆卡车,每辆途经Ki个城市并给出这Ki个城市。M次询问,每次给出两个城市,问这两辆车迎面相遇多少次,相遇时其中一个车正在掉头或刚好结束旅途不算。N<=100000, M<=300000, Ki之和<300000.
先记录所有掉头的时间并按时间先后排序,对于两辆卡车,若他们在相邻两个事件发生时的左右关系不同,则相遇次数+1,每次询问如果扫描一遍这个数组,可以得50分。
显然可以将询问绑定在其中一个车子的事件上,具体做法是选择Ki较小的绑定。这样做的时间复杂度为Msqrt(N)。证明如下:
若该车的Ki >= sqrtN,只有Kj大于Ki的卡车才会放入,则最多有N / Ki <= sqrtN个询问放在这个事件对应的链表里。
若该车的Ki < sqrtN,则这个车出现在事件数组中的次数=Ki<sqrtN,即使该车会牵扯很多的询问,重复次数不超过M*Ki<sqrtN.
从这个分析看出最坏的复杂度为(M+N)sqrtN,但是显然这题不可能所有车都处于最坏状态,所以3s应该还是比较宽松的。
具体实现过程中需要扫描事件数组,并实时更新每个车的状态(城市,方向),我自己写得很不科学,最后是按着标程的方法实现的,标程中用map来标记重复的询问,我省略了,加上那个判重大概快300ms。
正常题的解决模式应该是:理解题意,确定方向,对比数据范围,确定大致复杂度,设计算法,实现。
这类题比较特殊,解决模式是:理解题意,可行化,优化,检验复杂度,实现。以后应注意这类问题的操作模式。
#include<cstdio> #include<cstring> #include<algorithm> #include<utility> #include<map> #include<vector> using namespace std; #define LL long long #define MP make_pair #define pii pair<int,int> int N, M; inline void get(int&r) { char c; r = 0; do c=getchar(); while (c<'0'||c>'9'); do r=r*10+c-'0', c=getchar(); while (c>='0'&&c<='9'); } const int MAXN = 100005, MAXM = 300005; int ans[MAXM]; int evn; struct event { int id, x, dir; LL t; event() {} event(int a, int b, int c, LL d) { id = a; x = b; dir = c; t = d; } bool operator < (const event&b) const { if (t != b.t) return t < b.t; return dir > b.dir; } } ev[MAXM]; struct Query { int id, left, loc; Query () {} Query (int a, int b, int c) { id=a; left=b; loc=c; } }; vector<Query> as[MAXN]; int Ki[MAXN], xi[MAXN], dir[MAXN]; LL tm[MAXN]; void apply(const event &e) { xi[e.id] = e.x; dir[e.id] = e.dir; tm[e.id] = e.t; } int checkleft(int a, int b, LL pret, int pred) { if (dir[b] == 0 && pret >= tm[b]) return 0; int bpos, apos; if (dir[b] == 0) { bpos = xi[b]; apos = (tm[a] - tm[b]) * (-pred) + xi[a]; } else { apos = xi[a]; bpos = (tm[a] - tm[b]) * dir[b] + xi[b]; } if (bpos < apos) return -1; return 1; } void solve(int id, Query&q, LL pret, int pred) { int isleft = checkleft(id, q.id, pret, pred); ans[q.loc] += (q.left * isleft == -1); q.left = isleft; } int main() { get(N), get(M); LL cur; int i, j, x, x1, a, b; for (i = 1; i<=N; ++i) { cur = 0; get(Ki[i]); get(x); for (j = 1; j < Ki[i]; ++j) { get(x1); event e = event(i, x, x<x1 ? 1 : -1, cur); if (j == 1) apply(e); else ev[++evn] = e; cur += abs(x - x1); x = x1; } ev[++evn] = event(i, x, 0, cur); } for (i = 1; i<=M; ++i) { get(a); get(b); if (Ki[a] > Ki[b]) swap(a, b); as[a].push_back( Query(b, xi[b] < xi[a] ? -1 : 1, i) ); } sort(ev+1, ev+evn+1); for (i = 1; i<=evn; ++i) { LL pret = tm[ev[i].id]; int pred = dir[ev[i].id]; apply(ev[i]); for (j = 0; j < (int)as[ev[i].id].size(); ++j) solve(ev[i].id, as[ev[i].id][j], pret, pred); } for (i = 1; i<=M; ++i) printf("%d\n", ans[i]); return 0; }