ZOJ Monthly, March 2018 题解
【题目链接】
A. ZOJ 4004 - Easy Number Game
首先肯定是选择值最小的 $2*m$ 进行操作,这些数在操作的时候每次取一个最大的和最小的相乘是最优的。
#include <bits/stdc++.h> using namespace std; const int maxn = 100010; int T, n, m; long long a[maxn]; int main() { scanf("%d", &T); while(T--) { scanf("%d%d", &n, &m); long long sum = 0; for(int i = 0; i < n; i ++) { scanf("%lld", &a[i]); } sort(a, a+n); int first = 0, last = m * 2 - 1; while(first < m) { sum = sum + a[first] * a[last]; first++; last--; } printf("%lld\n", sum); } return 0; }
B. ZOJ 4005 - Lucky Man
找规律会发现就是求 $\left\lfloor \sqrt { n } \right\rfloor $ 的奇偶性。从这里学大数开根号。
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int l;char ans[1000];int num=0; int work(int o,char *O,int I) { char c,*D=O; if(o>0) { for(l=0;D[l];D[l++]-=10) { D[l++]-=120; D[l]-=110; while(!work(0,O,l)) D[l]+=20; //putchar((D[l]+1032)/20); ans[num++]=(D[l]+1032)/20; } //putchar(10); //ans[num++]='1';ans[num++]='0'; } else { c=o+(D[I]+82)%10-(I>l/2)*(D[I-l+I]+72)/10-9; D[I]+=I<0 ? 0 : !(o=work(c/10,O,I-1))*((c+999)%10-(D[I]+92)%10); } return o; } int main() { char s[1200]; int t;scanf("%d",&t); while(t--){ num=0; s[0]='0'; scanf("%s",s+1); if(strlen(s)%2 == 1) work(2,s+1,0); else work(2,s,0); ans[num]='\0'; int tmp=ans[num-1]-'0'; if(tmp&1) printf("1\n"); else printf("0\n"); } return 0; }
C. ZOJ 4006 - Travel along the Line
假设往左走了 $x$ 步,停留了 $y$ 步,往右走了 $z$ 步,这种情况下的概率为 ${ C }_{ n }^{ x }{ C }_{ n-x }^{ y }{ \left( \frac { 1 }{ 4 } \right) }^{ x+z }{ \left( \frac { 1 }{ 2 } \right) }^{ y }$。枚举 $x$ 之后,$y$ 和 $z$ 都能确定。
#include <bits/stdc++.h> using namespace std; const int maxn = 6e5 + 10; const int INF = 0x7FFFFFFF; const long long mod = 1e9 + 7; int T, n, m; long long f[maxn], po[maxn]; long long extend_gcd(long long a,long long b,long long &x,long long &y) { if(a==0&&b==0) return -1;//无最大公约数 if(b==0){x=1;y=0;return a;} long long d=extend_gcd(b,a%b,y,x); y-=a/b*x; return d; } long long mod_reverse(long long a,long long n) { long long x,y; long long d=extend_gcd(a,n,x,y); if(d==1) return (x%n+n)%n; else return -1; } long long C(int a, int b) { long long fz = f[a]; long long fm = f[a - b] * f[b] % mod; return fz * mod_reverse(fm, mod) % mod; } void init() { po[0] = 1LL; f[0] = 1LL; for(int i = 1; i < maxn; i ++) { po[i] = (po[i - 1] * 2LL) % mod; f[i] = (f[i - 1] * i) % mod; } } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif init(); scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); long long ans = 0; for(int x = 0; x <= n; x ++) { int y = n - x - m - x; int z = m + x; if(y < 0 || y > n) continue; if(z < 0 || z > n) continue; long long fz = C(n, x) * C(n - x, y) % mod; long long fm = po[2 * (x + z) + y]; long long tmp = fz * mod_reverse(fm ,mod) % mod; ans = (ans + tmp) % mod; } printf("%lld\n", ans); } return 0; }
D. ZOJ 4007 - Machine Learning on a Tree
由于每一个节点的 $x_i$ 都相同,因此 $w_{ij}$ 均为 1。总的来说就是让你安排每个节点的权值,如果某条边两端点权值不同,$value$ 就加 1,问 $value$ 的最小值是多少。
将树有根化后进行 dp,$f[i][j]$ 表示 $i$ 节点的权值设置为 $j$,以 $i$ 为根的子树的最小 $value$ 值。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 10; const int INF = 0x7FFFFFFF; int h[maxn], to[maxn], nx[maxn], sz; int y[maxn]; int T, n; int dp[maxn][2], f[maxn]; void add(int x, int y) { to[sz] = y; nx[sz] = h[x]; h[x] = sz ++; } void dfs(int x) { f[x] = 1; if(y[x] == -1) dp[x][0] = dp[x][1] = 0; else dp[x][y[x]] = 0; for(int i = h[x]; i != -1; i = nx[i]) { if(f[to[i]]) continue; dfs(to[i]); if(y[x] != -1) { dp[x][y[x]] += min(dp[to[i]][y[x]], dp[to[i]][y[x] ^ 1] + 1); } else { dp[x][0] += min(dp[to[i]][0], dp[to[i]][1] + 1); dp[x][1] += min(dp[to[i]][0] + 1, dp[to[i]][1]); } } } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif int T; scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%d", &y[i]); h[i] = -1; dp[i][0] = dp[i][1] = 5000000; f[i] = 0; } sz = 0; for(int i = 1; i < n; i ++) { int x, y; scanf("%d%d", &x, &y); add(x, y); add(y, x); } dfs(1); for(int i = 1; i <= n; i ++) { // cout << i << " " << dp[i][0] << " " << dp[i][1]<< endl; } printf("%d\n", min(dp[1][0], dp[1][1])); } return 0; }
E. ZOJ 4008 - Yet Another Tree Query Problem
树上询问编号在 $[L,R]$ 范围的节点和边构成了几个连通块。首先 $[L, R]$ 有 $R-L+1$ 个节点,如果我们能够计算出来 $[L, R]$ 有 $x$ 条边,那么答案就是 $R-L+1-x$。由于是树,多一条边就会少一个连通块。
计算 $[L, R]$ 中的边的条数就成了一个区间问题。题目中给出的边相当于 $n-1$ 个区间,问这些区间中有多少区间完全在 $[L,R]$ 内。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; const int INF = 0x7FFFFFFF; int T, n, q; struct X { int L, R, tp, id; }s[maxn * 4]; int ans[maxn * 4]; int c[maxn * 4]; int lowbit(int x) { return x & (-x); } int sum(int p) { int res = 0; while(p > 0) { res += c[p]; p -= lowbit(p); } return res; } void update(int p) { while(p < 4 * maxn) { c[p] += 1; p += lowbit(p); } } bool cmp(const X& a, const X& b) { if(a.R != b.R) { return a.R < b.R; } else { return a.tp < b.tp; } } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &T); while(T--) { scanf("%d%d", &n, &q); for(int i = 1; i < n + q; i ++) { scanf("%d%d", &s[i].L, &s[i].R); if(s[i].L > s[i].R) { swap(s[i].L, s[i].R); } if(i < n) s[i].tp = 0; else s[i].tp = 1; s[i].id = i; } int u = 0; sort(s + 1, s + n + q, cmp); for(int i = 1; i < n + q; i ++) { // cout << s[i].L << " " << s[i].R << " " << s[i].tp << " " << s[i].id << endl;; } memset(c, 0, sizeof c); for(int i = 1; i < n + q; i ++) { if(s[i].tp == 0) { u ++; update(s[i].L); } else { ans[s[i].id] = (s[i].R - s[i].L + 1) - (u - sum(s[i].L - 1)); } } for(int i = n; i < n + q; i ++) { printf("%d\n", ans[i]); } } return 0; }
F. ZOJ 4009 - And Another Data Structure Problem
所有数据在变 48 次之后,就会变回一开始的数,因此循环节为 48,线段树每个节点存 48 种状态即可。
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int mod = 99971; const int maxn = 1e5 + 10; int T, n, q; int s[maxn * 4][50]; int f[maxn * 4], p[maxn * 4]; int nx[maxn]; void init() { for(int i = 0; i < 99971; i ++) { nx[i] = 1; for(int j = 0; j < 3; j ++) { long long tmp = (1LL * nx[i] * 1LL * i) % (long long)mod; nx[i] = (int)tmp; } } } void pushUp(int rt) { for(int i = 0; i < 48; i ++) { s[rt][i] = (s[2 * rt][(p[2 * rt] + i) % 48] + s[2 * rt + 1][(p[2 * rt + 1] + i) % 48]) % mod; } p[rt] = 0; } void pushDown(int rt) { if(f[rt] == 0) return; f[2 * rt] += f[rt]; f[2 * rt + 1] += f[rt]; p[2 * rt] = (p[2 * rt] + f[rt]) % 48; p[2 * rt + 1] = (p[2 * rt + 1] + f[rt]) % 48; f[rt] = 0; } void build(int l, int r, int rt) { p[rt] = f[rt] = 0; if(l == r) { scanf("%d", &s[rt][0]); s[rt][0] = s[rt][0] % mod; for(int i = 1; i < 48; i ++) { s[rt][i] = nx[s[rt][i - 1]]; } return; } int mid = (l + r) / 2; build(l, mid, 2 * rt); build(mid + 1, r, 2 * rt + 1); pushUp(rt); } void update(int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { p[rt] = (p[rt] + 1) % 48; f[rt] = f[rt] + 1; return; } pushDown(rt); int mid = (l + r) / 2; if(L <= mid) update(L, R, l, mid, 2 * rt); if(R > mid) update(L, R, mid + 1, r, 2 * rt + 1); pushUp(rt); } int sum(int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { return s[rt][p[rt]]; } int left = 0; int right = 0; pushDown(rt); int mid = (l + r) / 2; if(L <= mid) left = sum(L, R, l, mid, 2 * rt); if(R > mid) right = sum(L, R, mid + 1, r, 2 * rt + 1); pushUp(rt); return (left + right) % mod; } int main() { init(); scanf("%d", &T); while(T --) { scanf("%d%d", &n, &q); build(1, n, 1); while(q --) { int op, L, R; scanf("%d%d%d", &op, &L, &R); if(op == 1) { update(L, R, 1, n, 1); } else { printf("%d\n", sum(L, R, 1, n, 1)); } } } return 0; }
G. ZOJ 4010 - Neighboring Characters
看了 cxhscst2 的题解恍然大悟。
删除连续 $k$ 个字符,相当于留下连续的 $p=len-k$ 个字符,我们可以从留下来的角度来考虑问题。
先把环拆了,也就是字符串扩大一倍,这样环上一段连续的区间就对应于字符串上一段连续的区间;同样的地,字符串上一段连续的区间也对应于环上一段连续的区间。
然后要去看是否存在长度为 $p(1\le p\le len)$ 的子串,相邻和首尾的字符均不同。
接下来感觉是本题精髓:
假设位置 1 最右能扩展到位置 $a$,保证 $[1,a]$ 相邻字符都不相同(首尾先不管)
假设位置 $a+1$ 最右能扩展到位置 $b$,保证 $[a+1,b]$ 相邻字符都不相同(首尾先不管)
假设位置 $b+1$ 最右能扩展到位置 $c$,保证 $[b+1,c]$ 相邻字符都不相同(首尾先不管)
....
假设位置 $x+1$ 最右能扩展到位置 $len$,保证 $[x+1,len]$ 相邻字符都不相同(首尾先不管)
我们把 "长度为 $p(1\le p\le len)$ 的子串,相邻和首尾的字符均不同" 的子串称为我们需要寻找的串。
那么,我们需要寻找的串要么没有,要么必然出现在 $[1,a]$,$[a+1,b]$,....,$[x+1,len]$ 其中的一个或多个中。
为什么不用考虑 $[2, ?]$,因为 ?最大也只有 $a$,所以考虑 $[1,a]$ 足够了。
下面以 $[1,a]$ 为例,之后的都是一样的操作。
要在 $[1,a]$ 中寻找是否存在长度为 $p$ 的我们需要寻找的串。首先,$[1,a]$ 中任意一个长度为 $p$ 的子串,相邻都是不同的,我们只需判断首尾。
也就是如果满足以下其中的一项,我们就找到了长度为 $p$ 的我们需要找的串。
$s[1]\neq s[p]\\ s[2]\neq s[p+1]\\ s[3]\neq s[p+2]\\ ...\\ s[a-p+1]\neq s[a]$
观察一下可以发现,不等式左边形成了一个前缀,右边是一个后缀,也就是说如果前缀不等于后缀,就有满足条件的,因此字符串 hash 判断一下前后缀是否相同即可。
复杂度的话是 $O(n)$;因为 $[1,a]$,$[a+1,b]$,....,$[x+1,len]$ 这些区间无相交部分,每个区间内枚举一遍,所以复杂度是线性的。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e6 + 10; const int INF = 0x7FFFFFFF; char s[maxn]; int ans[maxn]; long long base = 131; long long mod = 1e9 + 7; long long h[maxn], po[maxn]; long long H(int L, int R) { L ++; R ++; long long res = 0; long long tmp = h[L - 1] * po[R - L + 1] % mod; res = (h[R] - tmp + mod) % mod; return res; } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif po[0] = 1LL; for(int i = 1; i < maxn; i ++) { po[i] = (po[i - 1] * base) % mod; } int T; scanf("%d", &T); while(T --) { scanf("%s", s); int len = strlen(s); for(int i = len; i < 2 * len; i ++) { s[i] = s[i - len]; s[i + 1] = 0; } len = 2 * len; for(int i = 0; i < len; i ++) { h[i + 1] = h[i] * base % mod; h[i + 1] = (h[i + 1] + s[i]) % mod; } //printf("%s\n", s); for(int i = 0; i <= len; i ++) { ans[i] = 0; } for(int i = 0, j = 0; i < len; i = j + 1) { for(j = i; j < len && j + 1 < len && s[j] != s[j + 1]; j ++) {} //cout << i << " " << j << endl; int t = j - i + 1; t = min(t, len / 2); for(int x = 1; x <= t; x ++) { if(x == 1) { ans[x] = 1; } else if(H(i, j - x + 1) != H(i + x - 1, j)) { ans[x] = 1; } } } for(int i = len / 2; i >= 1; i --) { printf("%d", ans[i]); } printf("\n"); } return 0; }
H. ZOJ 4011 - Happy Sequence
$f[i][j]$ 表示放了 $i$ 个数,最后一个数是 $j$ 的方案数。转移的时候枚举一下约数就可以了。
#include <bits/stdc++.h> using namespace std; const long long mod = 1e9 + 7; int T, n, m; long long dp[2010][2010]; vector<int> vec[2010]; int main() { for(int i = 1; i <= 2000; i++) { for(int j = 1; j <= i; j ++) { if(i % j == 0) { vec[i].push_back(j); } } } scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) { dp[1][i] = 1; } for(int i = 2; i <= m; i ++) { for(int j = 1; j <= n; j ++) { dp[i][j] = 0; for(int k = 0; k < vec[j].size(); k ++) { dp[i][j] = (dp[i][j] + dp[i - 1][vec[j][k]]) % mod; } } } long long ans = 0; for(int j = 1; j <= n; j ++) { ans = (ans + dp[m][j]) % mod; } printf("%lld\n", ans); } return 0; }
I. ZOJ 4012 - Your Bridge is under Attack
用 KD-Tree 切割一下平面,每个节点算一下包围住这个平面的最小矩形坐标,每次询问的时候如果发现这条线和这个矩形无相交部分,直接 return。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; const int INF = 0x7FFFFFFF; const int demension = 2;//二维 struct node { int pos[demension]; int l, r; int mi[2], ma[2]; }a[maxn]; int cmpDem; int root; int n, q, ans, A, B; bool cmp(const node &a, const node&b) { return a.pos[cmpDem] < b.pos[cmpDem]; } void Merge(int k) { for (int i = 0; i < demension; i++) { a[k].ma[i] = a[k].pos[i]; a[k].mi[i] = a[k].pos[i]; if (a[k].l) { a[k].ma[i] = max(a[k].ma[i], a[a[k].l].ma[i]); a[k].mi[i] = min(a[k].mi[i], a[a[k].l].mi[i]); } if (a[k].r) { a[k].ma[i] = max(a[k].ma[i], a[a[k].r].ma[i]); a[k].mi[i] = min(a[k].mi[i], a[a[k].r].mi[i]); } } } int build(int l, int r, int k) { if (l > r) return 0; int mid = (l + r) / 2; //以第mid个元素为中心排序 cmpDem = k; nth_element(a + l, a + mid, a + r + 1, cmp); //左右子树 a[mid].l = build(l, mid - 1, (k + 1) % demension); a[mid].r = build(mid + 1, r, (k + 1) % demension); Merge(mid); return mid; } int G(int x1, int y1, int x2, int y2, int x3, int y3) { if(1LL * (x2 - x1) * (y3 - y2) == 1LL * (x3 - x2) * (y2 - y1)) return 1; return 0; } int ok(int id) { int x1 = A, x2 = 0, x3 = a[id].pos[0]; int y1 = 0, y2 = B, y3 = a[id].pos[1]; return G(x1, y1, x2, y2, x3, y3); } struct Point { long long x; long long y; Point(int xx, int yy) { x = 1LL * xx; y = 1LL * yy; } }; bool lineIntersectSide(Point A, Point B, Point C, Point D) { // A(x1, y1), B(x2, y2)的直线方程为: // f(x, y) = (y - y1) * (x1 - x2) - (x - x1) * (y1 - y2) = 0 long long fC = (C.y - A.y) * (A.x - B.x) - (C.x - A.x) * (A.y - B.y); long long fD = (D.y - A.y) * (A.x - B.x) - (D.x - A.x) * (A.y - B.y); if(fC > 0 && fD > 0) return false; if(fC < 0 && fD < 0) return false; return true; } bool sideIntersectSide(Point A, Point B, Point C, Point D) { if(!lineIntersectSide(A, B, C, D)) return false; if(!lineIntersectSide(C, D, A, B)) return false; return true; } int check(int id) { // [mi[0], mi[1]], [ma[0], ma[1]] // [A, 0], [0, B] if(G(A, 0, 0, B, a[id].mi[0], a[id].mi[1])) return 1; if(G(A, 0, 0, B, a[id].ma[0], a[id].ma[1])) return 1; if(sideIntersectSide(Point(A, 0), Point(0, B), Point(a[id].mi[0], a[id].mi[1]), Point(a[id].ma[0], a[id].ma[1]))) { return 1; } return 0; } void ask(int l, int r, int id) { if(id == 0) return; if(check(id) == 0) return; ans = ans + ok(id); int mid = (l + r) / 2; ask(l, mid - 1, a[id].l); ask(mid + 1, r, a[id].r); } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif int T; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &q); for(int i = 1; i <= n; i ++) { scanf("%d%d", &a[i].pos[0], &a[i].pos[1]); } root = build(1, n, 0); /* for(int i = 1; i <= n; i ++) { printf("%d : %d %d mi[0]:%d mi[1]:%d ma[0]:%d ma[1]:%d\n", i, a[i].pos[0], a[i].pos[1], a[i].mi[0], a[i].mi[1], a[i].ma[0], a[i].ma[1]); } */ while(q --) { scanf("%d%d", &A, &B); ans = 0; ask(1, n, root); printf("%d\n", ans); } } return 0; }
J. ZOJ 4013 - Super Brain
这个题比较简单,有很多方法判吧。
#include <bits/stdc++.h> using namespace std; int T, n; int main() { scanf("%d", &T); while(T--) { scanf("%d", &n); set<int> st; for(int i = 1; i <= n; i ++) { int x; scanf("%d", &x); st.insert(x); } int ans; for(int i = 1; i <= n; i ++) { int x; scanf("%d", &x); if(st.count(x) > 0) ans = x; } printf("%d\n", ans); } return 0; }