2013 Multi-University Training Contest 3
HDU-4622 Reincarnation
题意:给定一个字符串,有Q次询问,每次询问得出区间[L, R]内有多少个不同的子串。
分析:后缀数组搞,不过hash+dp也能够搞定这题,详解见http://www.cnblogs.com/Lyush/p/3233573.html。
#include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> using namespace std; const int N = 2005; char str[N]; int len, seq[N]; int sa[N], rank[N], height[N]; int wa[N], wb[N], ws[N], wv[N]; int f[N][30]; int lg[N]; bool cmp(int r[], int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int r[], int sa[], int n, int m) { int i, j, p, *x = wa, *y = wb; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[x[i]=r[i]]++; for (i = 1; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[x[i]]] = i; for (j = 1, p = 1; p < n; j *= 2, m = p) { for (p = 0, i = n - j; i < n; ++i) y[p++] = i; for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j; for (i = 0; i < n; ++i) wv[i] = x[y[i]]; for (i = 0; i < m; ++i) ws[i] = 0; for (i = 0; i < n; ++i) ws[wv[i]]++; for (i = 1; i < m; ++i) ws[i] += ws[i-1]; for (i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; for (swap(x, y), p = 1, x[sa[0]] = 0, i = 1; i < n; ++i) x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++; } } void calheight(int r[], int sa[], int n) { int i, j, k = 0; for (i = 1; i <= n; ++i) rank[sa[i]] = i; for (i = 0; i < n; height[rank[i++]] = k) for (k?k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; ++k) ; } void initrmq() { int LIM = (int)log2(1.0*N); for (int i = 1; i <= len; ++i) { f[i][0] = height[i]; } for (int j = 1; j <= LIM; ++j) { for (int i = 1; i+(1<<j)-1 <= len; ++i) { f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]); } } } int query(int l, int r) { int k = lg[r-l+1]; return min(f[l][k], f[r-(1<<k)+1][k]); } int cal(int l, int r) { int ret = (r-l+2)*(r-l+1)/2, last = -1; int lcp, a, b, alen, blen, k = r-l+1; for (int i = 1; i <= len && k; ++i) { if (sa[i] >= l && sa[i] <= r) { // 说明该后缀位于所选的区间内,且全局rank最靠前 k--; if (last != -1) { a = last, b = i; if (a > b) swap(a, b); lcp = query(a+1, b); alen = r-sa[last]+1, blen = r-sa[i]+1; /* if (lcp < alen && lcp < blen) {last = i;} else if (lcp >= alen && lcp >= blen) { if (blen > alen) last = i; } else if (lcp >= alen) {last = i;} */ if (alen > blen && lcp >= blen) {} else last = i; ret -= min(lcp, min(alen, blen)); } else last = i; } } return ret; } void solve() { int Q, l, r; scanf("%d", &Q); while (Q--) { scanf("%d %d", &l, &r); printf("%d\n", cal(l-1, r-1)); // 字符串编号从0开始 } } int main() { lg[0] = -1; for (int i = 1; i < N; ++i) { lg[i] = lg[i>>1] + 1; } int T; scanf("%d", &T); while (T--) { scanf("%s", str); len = strlen(str); for (int i = 0; i < len; ++i) seq[i] = str[i]-'a'+1; seq[len] = 0; da(seq, sa, len+1, 27); calheight(seq, sa, len); initrmq(); solve(); } return 0; }
HDU-4630 No Pain No Game
题意:给出1-N的全排列,后面跟随Q次询问,每次询问给出L, R,求给出的排列中[L, R]中最大的一对最大公约数时多少?
分析:比赛的时候觉得这题怎么搞都没有办法把时间复杂度降下来,知道是离线处理的方式但是不知道怎么搞。其实以后遇到这种要离线的题目先设想一种能够从一端求解到另一端的算法,即算法维护一个前 i 个元素的一个性质,即这个性质只适合从前往后计算过去。。。。。还是要多题目这类离线的题目。针对该题而言,首先把1-50000每个数的所有因子全部保存起来,将所有询问排序,然后从数组的1号元素开始往后遍历,每次扫描该数的所有因子,查看是否有因子x在前面出现过,如果出现过,假设最靠右的位置为last[x]那么就更新1-last[x]的最大值为x(只是试探性的覆盖,只覆盖比它小的,不覆盖本来就比其大的值),表示如果后面的询问如果左端点如果在1-last[x]内的话,那么最大公约数至少为x。注意如果一个数被处理,那么它的所有因子的last[]就赋值成当前位置,因此该数也能够提供这个因子。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <vector> #include <cmath> #define lch p<<1 #define rch p<<1|1 using namespace std; const int N = 50005; struct SegmentTree { struct Node { int lazy; int l, r, Max; }; Node e[N<<2]; void push_up(int p) { e[p].Max = max(e[lch].Max, e[rch].Max); } void push_down(int p) { if (e[p].lazy != -1) { e[lch].lazy = max(e[lch].lazy, e[p].lazy); e[rch].lazy = max(e[rch].lazy, e[p].lazy); e[p].Max = max(e[p].Max, e[p].lazy); } } void build(int p, int l, int r) { e[p].l = l, e[p].r = r, e[p].lazy = -1; if (l != r) { int mid = (l + r) >> 1; build(lch, l, mid); build(rch, mid+1, r); push_up(p); } else { e[p].Max = 1; } } void modify(int p, int l, int r, int val) { if (e[p].l == l && e[p].r == r) { e[p].lazy = max(e[p].lazy, val); if (l == r) { e[p].Max = max(e[p].Max, e[p].lazy); e[p].lazy = -1; } return; } push_down(p); int mid = (e[p].l + e[p].r) >> 1; if (r <= mid) modify(lch, l, r, val); else if (l > mid) modify(rch, l, r, val); else modify(lch, l, mid, val), modify(rch, mid+1, r, val); push_up(p); // 更新下去的才需要push_up } int query(int p, int l, int r) { if (e[p].l == l && e[p].r == r) { if (l == r) { e[p].Max = max(e[p].Max, e[p].lazy); e[p].lazy = -1; } return e[p].Max; } push_down(p); int mid = (e[p].l + e[p].r) >> 1; if (r <= mid) return query(lch, l, r); else if (l > mid) return query(rch, l, r); else return max(query(lch, l, mid), query(rch, mid+1, r)); } }; struct Query { int l, r, No; bool operator < (const Query &t) const { return r < t.r; } }q[N]; int n, Q, seq[N]; int last[N], ans[N]; vector<int>v[N]; SegmentTree st; void prepare() { for (int i = 1; i <= 50000; ++i) { for (int j = i; j <= 50000; j+=i) { v[j].push_back(i); } } } void solve() { int p = 1; memset(last, 0xff, sizeof (last)); st.build(1, 1, n); for (int i = 1; i <= n; ++i) { vector<int>&vt = v[seq[i]]; for (int j = 0; j < (int)vt.size(); ++j) { // 读取该数字的所有因子进行更新 if (last[vt[j]] != -1) { // 说明前面出现过 st.modify(1, 1, last[vt[j]], vt[j]); } last[vt[j]] = i; } while (p <= Q && q[p].r == i) { if (q[p].r == q[p].l) ans[q[p].No] = 0; else ans[q[p].No] = st.query(1, q[p].l, q[p].l); ++p; } if (p > Q) break; } for (int i = 1; i <= Q; ++i) { printf("%d\n", ans[i]); } } int main() { prepare(); int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &seq[i]); } scanf("%d", &Q); for (int i = 1; i <= Q; ++i) { scanf("%d %d", &q[i].l, &q[i].r); q[i].No = i; } sort(q+1, q+1+Q); solve(); } return 9; }
HDU-4631 Sad Love Story
题意:题目中给出了六个参数:Ax,Bx,Cx,Ay,By,Cy,使用三个参数能够生成一个序列,这个序列:ai = (A * ai-1 + B) % C。现在初始化平面二维坐标点为(0, 0),x坐标和y坐标都按照这个规律进行生成。第一个点对就是(Bx%Cx, By%Cy)。现在问在t=2时刻起,每一时刻平面上的最近点对的距离的平方总和为多少?
分析:由于题目中反复强调了题目所给定的数据都是随机的,六个参数也是随机的,因此一种解法是使用set逐个进行加点(按照x轴进行排序),每次加点都只需要访问当前这个点周围的点即可。具体的范围是指周围的点与当前点的横坐标的距离平方不超过最近点对距离平方。
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> #include <set> #include <ctime> using namespace std; typedef long long LL; struct Point { int x, y; Point() {} Point(int _x, int _y) : x(_x), y(_y) {} bool operator < (const Point &other) const { if (x != other.x) return x < other.x; else return y < other.y; } }; int n; int fac[10]; set<Point>st; LL dist(const Point &a, const Point &b) { return 1LL*(a.x-b.x)*(a.x-b.x)+1LL*(a.y-b.y)*(a.y-b.y); } int gen_xy(int f[], int xy) { return (1LL*xy*f[0]+f[1])%f[2]; } void cal(const Point &obj, LL &close) { LL d; set<Point>::iterator it, tmp; it = st.find(obj); tmp = it, ++tmp; while (tmp != st.end() && 1LL*(tmp->x - it->x)*(tmp->x - it->x) < close) { d = dist(*it, *tmp); if (d < close) { close = d; } ++tmp; } tmp = it, --tmp; if (it == st.begin()) return; while (1LL*(tmp->x - it->x)*(tmp->x - it->x) < close) { d = dist(*it, *tmp); if (d < close) { close = d; } if (tmp == st.begin()) break; --tmp; } } int main() { int T; scanf("%d", &T); while (T--) { LL ret = 0, close = (1LLU<<63)-1; scanf("%d", &n); st.clear(); for (int i = 0; i < 6; ++i) { scanf("%d", &fac[i]); } int x = fac[1]%fac[2], y = fac[4]%fac[5]; st.insert(Point(x, y)); for (int i = 1; i < n; ++i) { x = gen_xy(fac, x); y = gen_xy(fac+3, y); if (st.count(Point(x, y))) { break; } st.insert(Point(x, y)); cal(Point(x, y), close); ret += close; } printf("%I64d\n", ret); } return 0; }
另外一种想法就是使用求解平面最近点对的方法。想法是这样的:首先多所有的点集求解一次最近点对,然后得到两个点的编号(i, j),其中 i < j,那么对于编号较大的点 j,从 j+1 开始到最后加进来的点的最近点对都将会是(i, j),于是使用当前距离乘上区间长度然后将问题转化为求解(1, j-1)的最近点对问题。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> using namespace std; struct Point { int x, y, No; Point() {} Point(int _x, int _y, int _No) : x(_x), y(_y), No(_No) {} }; bool operator < (const Point &a, const Point &b) { if (a.x != b.x) return a.x < b.x; return a.y < b.y; } typedef long long LL; const int N = 500005; int n; int fac[6]; int rk[N]; Point pt[N], ptx[N]; inline bool cmpy(const int &a, const int &b) { return ptx[a].y < ptx[b].y; } int getxy(int z, int f[]) { return (1LL*f[0]*z+f[1])%f[2]; } LL dist(const Point &a, const Point &b) { return 1LL*(a.x-b.x)*(a.x-b.x) + 1LL*(a.y-b.y)*(a.y-b.y); } LL getpair(int l, int r, Point pnts[], int &p, int &q) { // p、q分别用来最近点对的编号 LL tmp, ret = ((unsigned long long)(1)<<63)-1; if (l == r) return ret; if (l + 1 == r) { // 只剩下两个点时求最近点对 if (ret > (tmp=dist(pnts[l], pnts[l+1]))) { ret = tmp; p = l, q = l + 1; } return ret; } if (l + 2 == r) { // 还剩下三个点 if (ret > (tmp=dist(pnts[l], pnts[l+1]))) { ret = tmp; p = l, q = l + 1; } if (ret > (tmp=dist(pnts[l], pnts[l+2]))) { ret = tmp; p = l, q = l + 2; } if (ret > (tmp=dist(pnts[l+1], pnts[l+2]))) { ret = tmp; p = l + 1, q = l + 2; } return ret; } int mid = (l + r) >> 1; int pp, qq, idx = 0; if (ret > (tmp=getpair(l, mid, pnts, pp, qq))) { ret = tmp; p = pp, q = qq; } if (ret > (tmp=getpair(mid+1, r, pnts, pp, qq))) { ret = tmp; p = pp, q = qq; } for (int i = l; i <= r; ++i) { if (1LL*(pnts[i].x-pnts[mid].x)*(pnts[i].x-pnts[mid].x) < ret) { rk[idx++] = i; } } sort(rk, rk+idx, cmpy); for (int c = 0; c < idx; ++c) { for (int d=c+1; d < idx && 1LL*(pnts[rk[c]].y-pnts[rk[d]].y)*(pnts[rk[c]].y-pnts[rk[d]].y) < ret; ++d) { if (ret > (tmp=dist(pnts[rk[c]], pnts[rk[d]]))) { ret = tmp; p = rk[c], q = rk[d]; } } } return ret; } void solve() { int L = 1, R = n; // 初始化的左右计算区间 LL ret = 0; while (L < R) { // 最终会收敛与[1, 1] int p, q; memcpy(ptx+1, pt+1, sizeof(Point)*(R-L+1)); sort(ptx+1, ptx+1+(R-L+1)); // 换成一个rank数组进行排序后,时长竟然还变成了,所以保留了这份 LL d = getpair(L, R, ptx, p, q); if (ptx[p].No > ptx[q].No) swap(p, q); ret += d*(R-ptx[q].No+1); R = ptx[q].No-1; } printf("%I64d\n", ret); } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 0; i < 6; ++i) { scanf("%d", &fac[i]); } pt[1] = Point(fac[1]%fac[2], fac[4]%fac[5], 1); // 初始化第一个节点 for (int i = 2; i <= n; ++i) { pt[i] = Point(getxy(pt[i-1].x, fac), getxy(pt[i-1].y, fac+3), i); } solve(); } return 0; }
解题报告还说了KD树可做,但是我写的估计每个几十年跑不出来,每次插入一个点需要重建一棵树...... 贴个KD树的模板算了。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> #include <cmath> using namespace std; typedef long long LL; inline int sign(double); struct Point { double x, y; Point () {} Point (double _x, double _y) : x(_x), y(_y) {} void read() { scanf("%lf %lf", &x, &y); } bool operator < (const Point &t) const { if (sign(x-t.x) != 0) return sign(x-t.x) < 0; return sign(y-t.y) < 0; } }; const int N = 100005; const double eps = 1e-6; int n, m, Div[N]; Point pt[N]; double ret; inline int sign(double x) { return x < -eps ? -1 : x > eps; } double dist(const Point &a, const Point &b) { return sqrt(1.0*(a.x-b.x)*(a.x-b.x) + 1.0*(a.y-b.y)*(a.y-b.y)); } bool cmpX(const Point &a, const Point &b) { return a.x < b.x; } bool cmpY(const Point &a, const Point &b) { return a.y < b.y; } void BuildKD(int l, int r, Point p[]) { if (l >= r) return; // 如果已经划分到叶子节点 int mid = l + r >> 1; double MinX, MaxX, MinY, MaxY; MinX = min_element(p+l, p+r+1, cmpX)->x; MaxX = max_element(p+l, p+r+1, cmpX)->x; MinY = min_element(p+l, p+r+1, cmpY)->y; MaxY = max_element(p+l, p+r+1, cmpY)->y; Div[mid] = (MaxX - MinX) > (MaxY - MinY); // Div[i]为0表示按照x轴划分,为1表示按照y轴划分,划分的依据仅仅是那个方向的取值跨度大 nth_element(p+l, p+mid, p+r+1, Div[mid] ? cmpX : cmpY); BuildKD(l, mid-1, p); BuildKD(mid+1, r, p); } void Find(int l, int r, const Point &a, Point p[]) { if (l > r) return; int mid = l + r >> 1; double dis = dist(a, p[mid]); ret = min(ret, dis); double d = Div[mid] ? (a.x - p[mid].x) : (a.y - p[mid].y); int l1 = l, l2 = mid+1; int r1 = mid-1, r2 = r; if (sign(d) > 0) swap(l1, l2), swap(r1, r2); Find(l1, r1, a, p); if (sign(ret - fabs(d)) > 0) Find(l2, r2, a, p); } int main() { // n个节点,m次询问,返回离询问点最近的节点距离 while (scanf("%d %d", &n, &m) != EOF) { for (int i = 0; i < n; ++i) { pt[i].read(); } BuildKD(0, n-1, pt); while (m--) { Point p; p.read(); ret = 1e30; Find(0, n-1, p, pt); printf("%f\n", ret); } } return 0; }