- 样例大意
输入a个人,b组数据
每行输入x,y,z三个数,表示x-y中z是获胜者(即打败了其他的人)
输出每个人被谁打败,最终获胜者输出0
- 题目特点
区间更新问题,但是一个点只会被更新一次,线段树可以解,但是太过麻烦。问题的关键是如何快算的更新区间,即跳过更新过的区间
- 思路(解决更新的问题)
- 既然每次都是成段更新,而更新过的区间不用再更新,那么每次都二分一下当前区间,如果区间内都是已更新过的点,那么直接跳过;如果区间内只有一个需要更新的点,更新之。使用树状数组,1表示待更新,0表示更新过
代码如下(Time:514 ms Memory:2352 KB)
const int MAXN = 300010; int n[MAXN], ans[MAXN]; inline int lowbit(int x) { return x & -x; } inline int sum(int p) { int ret = 0; while (p > 0) { ret += n[p]; p -= lowbit(p); } return ret; } inline void add(int p, int v) { while (p < MAXN) { n[p] += v; p += lowbit(p); } } inline void update(int l, int r, int v) { int ls = sum(l - 1), rs = sum(r); if (rs == ls) return; if (l == r) { add(l, -1); ans[l] = v; return; } int m = (l + r) >> 1; update(l, m, v); update(m + 1, r, v); } int main() { // freopen("input.txt", "r", stdin); int a, b, x, y, z; RII(a, b); FE(i, 1, a) add(i, 1); REP(i, b) { RIII(x, y, z); update(x, y, z); add(z, 1); ans[z] = 0; } FE(i, 1, a) { if (i != 1) cout << ' '; cout << ans[i]; } cout << endl; return 0; }
- 利用每个点只更新一次的特点,使用set数据结构,初始的时候将每个点都插入集合,之后更新过的点就从set中删除
代码如下(Time:296 ms Memory:10776 KB)
注意:st.erase(it)最好不要使用,会出错导致RE。应该使用st.erase(it++)
const int MAXN = 300010; set<int> st; set<int>::iterator it; int ans[MAXN]; int main() { // freopen("input.txt", "r", stdin); int a, b, x, y, z; RII(a, b); FE(i, 1, a) st.insert(i); st.insert(a + 10); REP(i, b) { RIII(x, y, z); it = st.lower_bound(x); while ((*it) <= y) { ans[*it] = z; st.erase(it++); } st.insert(z); ans[z] = 0; } FE(i, 1, a) { if (i != 1) cout << ' '; cout << ans[i]; } cout << endl; return 0; }
- 使用跳过策略。每个点都记录一下它下一个没有更新的点是谁,使用的数据结构是并查集
代码如下(Time:171 ms Memory:4208 KB)
const int MAXN = 300010; int father[MAXN], ans[MAXN]; inline int findFather(int n) { if (father[n] == n) return n; return father[n] = findFather(father[n]); } inline void merge(int a, int b) { int fa = findFather(a), fb = findFather(b); father[fa] = fb; } void solve(int x, int y, int v) { if (x > y) return; int t = findFather(x); while (t <= y) { int next = findFather(t); if (t == next) { ans[t] = v; merge(t, t + 1); t++; } else { t = next; } } } int main() { // freopen("input.txt", "r", stdin); int a, b, x, y, z; RII(a, b); FE(i, 1, a + 1) father[i] = i; REP(i, b) { RIII(x, y, z); solve(x, z - 1, z); solve(z + 1, y, z); } FE(i, 1, a) { if (i != 1) cout << ' '; cout << ans[i]; } cout << endl; return 0; }
- 总结:
这道题目的另一个特点是,需要更新的区间可能已经更新过了。如果没有这个特点,使用带Hash的双向链表也是可以的
总的来看,如果不是特意去卡时间,这类问题使用set来解决是上策,代码复杂度十分的低。