花了点时间做了一下CEOI2011,题目质量还是比较好的
1.Balloons
大意是很多有着固定的横坐标气球没有固定的半径,然后求从左到右依次吹这些气球,它们的半径最大是多少。(气球下端必须触地)因为气球的横坐标固定了,因此对于第i个气球,我们要使得它与前i-1个气球不相交,就可以推其最大半径的式子:
dist(i, j) = sqrt((x[i] – x[j])^2 + (r[i] – r[j])^2) = r[i] + r[j] (j为与i相切的那个气球)
整理可得r[i] = (x[i] – x[j])^2 / (4 * r[j])
因此 r[i] = min((x[i] – x[j])^2 / (4 * r[j])) j < i
这样的做法是O(n^2)的,可以发现能够维护一个r递减的栈,就能剔除绝对不会取到的解(x与r都相对较大),于是就O(n)了.
#include <stdio.h> const int nmax = 200000; int x[nmax + 18], r[nmax + 18], sta[nmax + 18], st; double ans[nmax + 18]; int n; int eps(double x){ return x < -1e-10 ? -1 : x > 1e-10;} double sqr(double x) {return x * x;} void downdate(double &a, double b){ return (void)((eps(a - b) > 0) ? (a = b) : 0);} int main() { // freopen("bal.in", "r", stdin); // freopen("bal.out", "w", stdout); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d%d", x + i, r + i); ans[sta[st = 1] = 1] = r[1]; for (int i = 2; i <= n; sta[++st] = i++) for (ans[i] = r[i]; st && (downdate(ans[i], sqr(x[i] - x[sta[st]]) / (4.0 * ans[sta[st]])), eps(ans[i] - ans[sta[st]]) >= 0); --st); for (int i = 1; i <= n; ++i) printf("%.3f\n", ans[i]); return 0; }
2.Matching
大意是给你一个排列a[i]和一个数字互不相同的母数串b[i],要你找出所有b的连续子串,是的这些子串中的第a[i]个元素在这个子串中的排名为i
其为kmp的变种,可以看到对于第i个元素,如果它可以匹配到模式串(a串指值互换之后的串)中的第j个的条件是,i的前面j-1个数中比它大的和比它小的数的数目分别与模式传中的前j-1个数中比第j个大的和比其小的的数相等(好绕),这样每次匹配就可以使用线段树来查询比较,复杂度为O(nlogn)
要想要有更为优秀的复杂度,需要用到一个性质就是,设l[i]和r[i]分别为模式串前i -1个数中第一个比第i个数小的数的位置和第一个比它大的数的位置,那么只要在匹配的过程中保证对于第i个数,第l[i]个数比它小,第r[i]个数比它大即可保证整个串的匹配,这样预处理出模式串的l[i]和r[i],kmp中O(1)判断即可。
#include <stdio.h> const int nmax = 1000000; int a[nmax + 18], b[nmax + 18], l[nmax + 18], r[nmax + 18], p[nmax + 18]; int n, m, ans, q[nmax + 18], pre[nmax + 18], nxt[nmax + 18], d[nmax + 18]; void prepare() { for (int i = 1; i <= n; ++i) d[b[i]] = i, pre[i] = i - 1, nxt[i] = i + 1; for (int i = n; i; --i) { if (pre[d[i]] > 0) l[i] = i - b[pre[d[i]]], nxt[pre[d[i]]] = nxt[d[i]]; if (nxt[d[i]] <= n) r[i] = i - b[nxt[d[i]]], pre[nxt[d[i]]] = pre[d[i]]; } for (int i = 2, j = 0; i <= n; ++i) { while (j && ((l[j + 1] && d[i - l[j + 1]] > d[i]) || (r[j + 1] && d[i - r[j + 1]] < d[i]))) j = p[j]; p[i] = (((!l[j + 1] || d[i - l[j + 1]] < d[i]) && (!r[j + 1] || d[i - r[j + 1]] > d[i])) ? ++j : j); } } void work() { for (int i = 1, j = 0; i <= m; ++i) { while (j && ((l[j + 1] && a[i - l[j + 1]] > a[i]) || (r[j + 1] && a[i - r[j + 1]] < a[i]))) j = p[j]; if ((((!l[j + 1] || a[i - l[j + 1]] < a[i]) && (!r[j + 1] || a[i - r[j + 1]] > a[i])) ? ++j : j) >= n) q[++ans] = i - n + 1, j = p[j]; } } int main() { //freopen("mat.in", "r", stdin); //freopen("mat.out", "w", stdout); scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) scanf("%d", b + i); for (int i = 1; i <= m; ++i) scanf("%d", a + i); prepare(); work(); printf("%d\n", ans); for (int i = 1; i <= ans; ++i) printf("%d ", q[i]); return 0; }
3.Treasure Hunt
鉴于我还想要再一次做一做平衡树的相关题目,这个动态树的就留待那个时候做。
1.Hotel
大意是一个宾馆有n个房间和m个预订,每个房间有住过之后的维护费用c和最 大入住人数p,每个预订有收入v和人数d,大房间的维护费用不会比小房间的低,问 你最多接受o个预订的情况下的最大盈利。
可以将房间以大小为第一关键字,维护费为第二关键字非增排序,将预订按费用非 降排列,然后对于每一笔预订,直接二分查找满足要求的最小最便宜的房间,然后将所 有这样的配对排个序取前面o个或者取到负数为止。
#include <stdio.h> #include <stdlib.h> const int nmax = 500000; int f[nmax + 18], pr[nmax + 18], po[nmax + 18]; int n, m, o, now, q[nmax + 18], qt; long long ans; int c[nmax + 18], cy[nmax + 18], v[nmax + 18], mc[nmax + 18]; int cmpor1(const void *i, const void *j){return cy[*(int *)i] != cy[*(int *)j] ? cy[*(int *)i] - cy[*(int *)j] : c[*(int *)i] - c[*(int *)j];} int cmpor2(const void *i, const void *j){return v[*(int *)j] - v[*(int *)i];} int cmpor3(const void *i, const void *j){return *(int *)j - *(int *)i;} int find(int x){return f[x] == x ? x : f[x] = find(f[x]);} int bi(int k) { int l = 1, r = n; for (int mid, tf; l < r; ) if ((tf = find(mid = (l + r) >> 1)) <= n && cy[pr[tf]] >= k) r = mid; else l = mid + 1; return find(l); } int main() { //freopen("hot.in", "r", stdin); //freopen("hot.out", "w", stdout); scanf("%d%d%d", &n, &m, &o); for (int i = 1; i <= n; ++i) scanf("%d%d", c + i, cy + i), pr[i] = f[i] = i;f[n + 1] = n + 1; for (int i = 1; i <= m; ++i) scanf("%d%d", v + i, mc + i), po[i] = i; qsort(pr + 1, n, sizeof(pr[0]), cmpor1); qsort(po + 1, m, sizeof(po[0]), cmpor2); for (int i = 1, k; i <= m; ++i) if ((k = bi(mc[po[i]])) && k <= n) if (cy[pr[k]] >= mc[po[i]] && c[pr[k]] < v[po[i]]) q[++qt] += v[po[i]] - c[pr[k]], f[k] = k + 1; qsort(q + 1, qt, sizeof(q[0]), cmpor3); if (qt > o) qt = o; for (int i = 1; i <= qt; ++i) if (q[i] > 0) ans += q[i]; else break; printf("%lld", ans); return 0; }
2.Teams
大意是有n个队员,每个队员对于自己所在的队伍的最小人数有要求a[i],需要你将他们分成尽量多的队,在此基础上使得人数最多的队伍的人数最少
有一种做法是将队员按要求从小到大排列,然后可以很简单地dp得到最大的队伍数,但是求最大队伍的最小人数却不好做,于是我们将这个变成一个判定性问题,二分最大队伍的最小人数,每次在这个限制下做,需要一个线段树,是O(nlog^2(n))的
正解是将队员从大到小排列,用f[i]记录1到i的队员能够组成的最大队伍数,容易得到只有满足a[j]+j =i+1的j才会有可能是转移到i的最优解,所以先这样dp一次计算出最大队伍数,然后第二次dp求最大队伍的最小人数,这里记h[i]为1到i分成f[i]份中最大队伍的最小人数,则如果h[i-1] * f[i – 1] = i -1 ,那么说明i-1的分组方案已经使得每一队都恰好有h[i – 1]个人,此时第i个人就得加到其中一个去,h[i] = h[i – 1] + 1,否则可以将第i个人加到任何一个没有满h[i – 1]个人的队伍里去,h[i] = h[i – 1]。
#include <stdio.h> const int nmax = 1000000; int n, a[nmax + 18], f[nmax + 18], pre[nmax + 18]; int b[nmax + 18], p[nmax + 18], g[nmax + 18], ans, h[nmax + 18]; int max(int a, int b){return a > b ? a : b;} void update(int &a, int &b, int c, int i){if (a > c || !b) a = c, b = i;} void print(int i) { if (!i) return; if (i - pre[i] > h[n]) pre[i - h[n]] = pre[pre[i]], pre[i] = i - h[n]; printf("%d", i - pre[i]); for (int j = pre[i] + 1; j <= i; ++j) printf(" %d", p[j]); printf("\n"); print(pre[i]); } int main() { //freopen("tea.in", "r", stdin); //freopen("tea.out", "w", stdout); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i), ++b[a[i]]; for (int i = n; i; --i) b[i] += b[i + 1]; for (int i = 1; i <= n; ++i) p[b[a[i]]--] = i; for (int i = 1; i <= n; ++i) f[i] = (i < a[p[1]] ? 0 : f[g[i]] + 1), g[a[p[i + 1]] + i] = i, h[i] = n; h[a[p[1]]] = a[p[1]]; for (int i = a[p[1]] + 1; i <= n; ++i) { if (f[i] == f[i - 1]) if (i - 1 == f[i - 1] * h[i - 1]) update(h[i], pre[i], h[i - 1] + 1, i - h[i - 1] - 1); else update(h[i], pre[i], h[i - 1], pre[i - 1]); if (f[i - 1] + 1 == f[i + a[p[i]] - 1]) update(h[i + a[p[i]] - 1], pre[i + a[p[i]] - 1], max(a[p[i]], h[i - 1]), i - 1); } printf("%d\n", f[n]); print(n); return 0; }
3.Traffic
基本就是noip的引水入城,去掉不连通的点之后直接根据左边的点到达右边的点一定是连续的一片的这个性质两边单调扫一遍就行了。
#include <stdio.h> #include <stdlib.h> #include <memory.h> const int nmax = 300000, mmax = 900000 * 2; int x[nmax + 18], y[nmax + 18], p[nmax + 18]; int fst[2][nmax + 18], nxt[2][mmax + 18], pnt[2][mmax + 18], tot; int ansl[nmax + 18], ansr[nmax + 18], at, nr, nl; int real[nmax + 18], rt; bool inl[nmax + 18], inr[nmax + 18], ed[nmax + 18]; int n, m, A, B; int q[nmax + 18], qt, qh; int cmpor(const void *i, const void *j){return y[*(int *)j] - y[*(int *)i];} void add(int s, int t){pnt[0][++tot] = t, nxt[0][tot] = fst[0][s], fst[0][s] = tot;pnt[1][tot] = s, nxt[1][tot] = fst[1][t], fst[1][t] = tot;} void update(int k){if (nl > k) nl = k;if (nr < k) nr = k;} void bfs(bool *ed, int k, int l) { q[qt = 0];qh = 1; for (int i = 1; i <= n; ++i) if (x[i] == k) q[++qt] = i, ed[i] = 1; for (int now; qh <= qt; ++qh) { now = q[qh]; for (int i = fst[l][now]; i; i = nxt[l][i]) if (!ed[pnt[l][i]]) ed[pnt[l][i]] = 1, q[++qt] = pnt[l][i]; } } void rdfs(int k) { if (x[k] == A && inr[k]) update(real[k]); ed[k] = 1; for (int i = fst[0][k]; i; i = nxt[0][i]) if (!ed[pnt[0][i]]) rdfs(pnt[0][i]); } int main() { //freopen("tra.in", "r", stdin); //freopen("tra.out", "w", stdout); scanf("%d%d%d%d", &n, &m, &A, &B); for (int i = 1; i <= n; ++i) scanf("%d%d", x + i, y + i), p[i] = i; for (int i = 1, l, r, mode; i <= m; ++i) scanf("%d%d%d", &l, &r, &mode), (mode == 1 ? add(l, r) : (add(l, r), add(r, l))); qsort(p + 1, n, sizeof(p[0]), cmpor); bfs(inr, 0, 0), bfs(inl, A, 1); for (int i = 1; i <= n; ++i) if (inr[p[i]] && x[p[i]] == A) real[p[i]] = ++rt; nr = 1; for (int i = 1; i <= n; ++i) if (inl[p[i]] && !x[p[i]]) rdfs(p[i]), ansr[i] = nr; memset(ed, 0, sizeof(ed)); nl = rt; for (int i = n; i; --i) if (inl[p[i]] && !x[p[i]]) rdfs(p[i]), ansl[i] = nl; for (int i = 1; i <= n; ++i) if (!x[p[i]]) printf("%d\n", inl[p[i]] ? (ansr[i] - ansl[i] + 1) : 0); return 0; }