题目链接

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;
}
04-28 21:45