BZOJ练习记
决定从头到尾干一波BZOJ!
可能会写没几题就停下吧,但还是想学学新姿势啦。
1001. [BeiJing2006]狼抓兔子
即求 $(1, 1)$ 到 $(n, m)$ 的最小割。跑 dinic 即可。
#include <bits/stdc++.h> using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); } return x * f; } const int N = 1e6 + 10; const int INF = 0x3f3f3f3f; struct E { int v, ne, f; } e[N * 7]; int head[N], cnt, n, m, iter[N], level[N]; inline void add(int u, int v, int f) { e[cnt].v = v; e[cnt].f = f; e[cnt].ne = head[u]; head[u] = cnt++; e[cnt].v = u; e[cnt].f = f; e[cnt].ne = head[v]; head[v] = cnt++; } bool bfs(int s, int t) { for (int i = 0; i <= t; i++) level[i] = -1, iter[i] = head[i]; queue<int> que; que.push(s); level[s] = 0; while (!que.empty()) { int u = que.front(); que.pop(); for (int i = head[u]; ~i; i = e[i].ne) { int v = e[i].v, f = e[i].f; if (level[v] < 0 && f) { level[v] = level[u] + 1; que.push(v); } } } return level[t] != -1; } int dfs(int u, int t, int f) { if (u == t || !f) return f; int flow = 0; for (int i = iter[u]; ~i; i = e[i].ne) { iter[u] = i; int v = e[i].v; if (level[v] == level[u] + 1 && e[i].f) { int w = dfs(v, t, min(f, e[i].f)); if (!w) continue; e[i].f -= w, e[i^1].f += w; flow += w, f -= w; if (f <= 0) break; } } return flow; } int main() { memset(head, -1, sizeof(head)); n = read(), m = read(); for (int i = 1; i <= n; i++) { for (int j = 1; j < m; j++) { int f = read(); add((i - 1) * m + j, (i - 1) * m + j + 1, f); } } for (int i = 1; i < n; i++) { for (int j = 1; j <= m; j++) { int f = read(); add((i - 1) * m + j, i * m + j, f); } } for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { int f = read(); add((i - 1) * m + j, i * m + j + 1, f); } } int ans = 0; int s = 1, t = n * m; for (; bfs(s, t); ans += dfs(s, t, INF)); printf("%d\n", ans); return 0; }
1002. [FJOI2007]轮状病毒
生成树计数。
基尔霍夫矩阵为度数矩阵减去邻接矩阵。
无向图生成树计数为基尔霍夫矩阵的行列式
可得递推方程
$ans = 3 \times f(n - 1) - 2 \times f(n - 2) - 2$
$f(n) = 3 \times f(n - 1) - f(n - 2)$
加上高精度即可。
注意算行列式时多写几行容易看。
#include <bits/stdc++.h> using namespace std; struct Bigi { int a[600], len; Bigi() { memset(a, 0, sizeof(a)); len = 1; } friend Bigi operator * (int x, Bigi b) { Bigi res; res.len = b.len + 10; for (int i = 1; i <= res.len; i++) { res.a[i] += b.a[i] * x; res.a[i + 1] += res.a[i] / 10; res.a[i] %= 10; } while (res.len > 1 && res.a[res.len] == 0) res.len--; return res; } friend Bigi operator - (Bigi x, Bigi y) { Bigi res; res.len = x.len; for (int i = 1; i <= res.len; i++) { res.a[i] = x.a[i] - y.a[i]; while (res.a[i] < 0) { res.a[i] += 10; x.a[i + 1]--; } } while (res.len > 1 && res.a[res.len] == 0) res.len--; return res; } friend Bigi operator + (Bigi x, int y) { Bigi res = x; res.a[1] += y; for (int i = 1; i <= res.len; i++) { if (res.a[i] >= 10) { res.a[i] -= 10; res.a[i + 1]++; } else { break; } } while (res.a[res.len + 1]) res.len++; return res; } void print() { for (int i = len; i; i--) printf("%d", a[i]); puts(""); } } big[150], ans; int main() { int n; scanf("%d", &n); big[1] = big[1] + 3; big[2] = big[2] + 8; if (n <= 2) { big[n].print(); return 0; } for (int i = 3; i <= n; i++) big[i] = 3 * big[i - 1] - big[i - 2]; ans = 3 * big[n - 1] - 2 * big[n - 2]; ans = ans + (-2); ans.print(); return 0; }
1003. [ZJOI2006]物流运输
$cost[i][j]$ 表示第 $i$ 天到第 $j$ 天都走同一条路线时每天的最小花费,即为 $1$ 到 $m$ 的最短路。dijkstra即可。
然后 $dp[i]$ 表示到第 $i$ 天的最小花费
$dp[i] = min(dp[j] + cost[j + 1][i] * (i - j) + k)$
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second using namespace std; const int INF = 10000000; const int N = 110; int cost[N][N], dp[N], n, m, k, e; vector<pii> G[N]; bool ban[N][N], unable[N], done[N]; int dis[N]; inline void checkmin(int &a, int b) { if (a > b) a = b; } int dijkstra(int l, int r) { for (int i = 1; i <= m; i++) { unable[i] = 0; done[i] = 0; dis[i] = INF; for (int j = l; j <= r; j++) unable[i] |= ban[j][i]; } if (unable[1] || unable[m]) return INF; priority_queue<pii, vector<pii>, greater<pii> > que; dis[1] = 0; que.push(pii(0, 1)); while (!que.empty()) { auto pp = que.top(); que.pop(); int u = pp.se; if (done[u]) continue; done[u] = 1; for (auto p: G[u]) { int v = p.se, c = p.fi; if (!unable[v] && dis[v] > dis[u] + c) { dis[v] = dis[u] + c; que.push(pii(dis[v], v)); } } } return dis[m]; } int main() { //freopen("in.txt", "r", stdin); scanf("%d%d%d%d", &n, &m, &k, &e); for (int u, v, c; e--; ) { scanf("%d%d%d", &u, &v, &c); G[u].push_back(pii(c, v)); G[v].push_back(pii(c, u)); } int q; scanf("%d", &q); for (int u, a, b; q--; ) { scanf("%d%d%d", &u, &a, &b); for (int i = a; i <= b; i++) ban[i][u] = 1; } for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) cost[i][j] = dijkstra(i, j); for (int i = 1; i <= n; i++) { dp[i] = cost[1][i] * i; for (int j = 1; j < i; j++) checkmin(dp[i], dp[j] + cost[j + 1][i] * (i - j) + k); } printf("%d\n", dp[n]); return 0; }
1005. [HNOI2008]明明的烦恼
prufer序列为无根树的一种数列。长度为 $n$ - $2$
prufer转无根树
将最小编号的叶子删去,prufer序列加入其父亲。重复至树只剩下两个节点。
无根树转prufer
取出prufer首元素,与待选点集中最小未出现在prufer序列中的点连边,并将该点在待选点集中删去,直至待选点集剩下两个节点,将这两个节点连边。待选点集初始为 $1$ ~ $n$。
一个节点在prufer序列中出现次数为该节点度数减一。
判断无解的情况:出现度数为 $0$ 的点,在prufer序列中出现次数超过 $n$ - $2$。
有解情况下,设 $cnt$ 为有度数要求的节点个数,$sum = \sum_{i = 1} ^{cnt}(d_i - 1)$。
那么答案为 $C_{n-2}^{sum} \times \dfrac{sum!}{\prod_{i=1}^{cnt}(d_i-1)!} \times (n-cnt)^{n-2-sum}$
化简得到$\dfrac{(n-2)!}{(n-sum-2)! \times \prod_{i=1}^{cnt}(d_i-1)!} \times (n-cnt)^{n-2-sum}$
#include <bits/stdc++.h> const int N = 1007; const int MOD = 10000; int num[N]; int prime[N], tol, d[N], c[N]; bool vis[N]; void init() { for (int i = 2; i < N; i++) { if (!vis[i]) prime[++tol] = i; for (int j = 1; j <= tol && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) break; } } } void add(int x, int o) { for (int i = 1; i <= tol; i++) { while (x % prime[i] == 0) c[i] += o, x /= prime[i]; } } int main() { init(); int n; scanf("%d", &n); bool flag = 0; int sum = 0, cnt = 0; for (int i = 1; i <= n; i++) { scanf("%d", d + i); if (!d[i] || d[i] > n - 1) flag = 1; if (d[i] != -1) sum += d[i] - 1, cnt++; } if (n == 1) { if (!d[1]) puts("1"); else puts("0"); return 0; } if (sum > n - 2 || flag) { puts("0"); return 0; } for (int i = n - 2 - sum + 1; i <= n - 2; i++) add(i, 1); for (int i = 1; i <= n; i++) { if (d[i] > -1) { for (int j = 2; j < d[i]; j++) add(j, -1); } } int len = 0; num[++len] = 1; for (int i = 1; i <= n - 2 - sum; i++) { for (int j = 1; j <= len; j++) num[j] *= n - cnt; for (int j = 1; j <= len; j++) { if (num[j] >= MOD) { num[j + 1] += num[j] / MOD; num[j] %= MOD; } } while (num[len + 1]) { num[len + 1] += num[len] / MOD; num[len] %= MOD; len++; } } for (int i = 1; i <= tol; i++) { while (c[i]) { for (int j = 1; j <= len; j++) num[j] *= prime[i]; for (int j = 1; j <= len; j++) { if (num[j] >= MOD) { num[j + 1] += num[j] / MOD; num[j] %= MOD; } } while (num[len + 1]) { num[len + 1] += num[len] / MOD; num[len] %= MOD; len++; } c[i]--; } } printf("%d", num[len]); for (int i = len - 1; i; i--) printf("%04d", num[i]); puts(""); return 0; }
1007. [HNOI2008]水平可见直线
可见的直线为一下凸壳。
先按斜率和截距从小到大排序,再用单调栈判断交点的相对位置即可。
#include <bits/stdc++.h> const int N = 5e4 + 7; const double eps = 1e-7; inline int dcmp(double x) { if (fabs(x) < eps) return 0; return x < 0 ? -1 : 1; } struct P { double x, y; int id; inline bool operator < (const P &rhs) const { if (dcmp(x - rhs.x) == 0) return y < rhs.y; return x < rhs.x; } } p[N]; int st[N], top; inline double crossx(const P &a, const P &b) { return (a.y - b.y) / (b.x - a.x); } void ins(int id) { const P &cur = p[id]; while (top) { if (dcmp(p[st[top]].x - cur.x) == 0) top--; else if (top > 1 && dcmp(crossx(p[st[top]], cur) - crossx(p[st[top]], p[st[top - 1]])) <= 0) top--; else break; } st[++top] = id; } int ans[N]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lf%lf", &p[i].x, &p[i].y); p[i].id = i; } std::sort(p + 1, p + 1 + n); for (int i = 1; i <= n; i++) ins(i); for (int i = 1; i <= top; i++) ans[p[st[i]].id] = 1; for (int i = 1; i <= n; i++) if (ans[i]) printf("%d ", i); return 0; }
1008. [HNOI2008]越狱
总方案数 - 相邻颜色均不同的方案数。
#include <bits/stdc++.h> #define ll long long const int MOD = 100003; int qp(int a, ll b) { a %= MOD; int ans = 1; while (b) { if (b & 1) ans = 1LL * ans * a % MOD; a = 1LL * a * a % MOD; b >>= 1; } return ans; } int main() { int m; ll n; scanf("%d%lld", &m, &n); int ans = qp(m, n); ans = (ans - 1LL * m * qp(m - 1, n - 1) % MOD) % MOD; ans = (ans + MOD) % MOD; printf("%d\n", ans); return 0; }
1009. [HNOI2008]GT考试
显然有一个DP方程 $dp[i][j]$ 表示到第 $i$ 位已经末尾匹配了 $j$ 位的方案数。
暴力的话就枚举下一位放啥,看放完之后又匹配了多少。
这里可以引入一个 $f[i][j]$ 数组表示从不吉利数字当前匹配了 $i$ 位,加上一个字符能匹配 $j$ 位的方案数。
这一部分可以用kmp得到。
然后dp的转移方程即为 $dp[i][j] = \sum dp[i - 1][k] \times f[k][j]$
答案为 $\sum_{i = 0}^{m - 1} dp[n][i]$
#include <bits/stdc++.h> const int N = 22; int MOD, n, m; char s[N]; int ne[N]; struct Mat { int mat[25][25]; Mat() { memset(mat, 0, sizeof mat); } Mat operator * (const Mat &rhs) const { Mat c; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) for (int k = 0; k < n; k++) (c.mat[i][j] += mat[i][k] * rhs.mat[k][j]) %= MOD; return c; } }; Mat qp(Mat ans, Mat a, int b) { while (b) { if (b & 1) ans = ans * a; a = a * a; b >>= 1; } return ans; } void kmp() { int i = 0, j = ne[0] = -1; while (i < n) { while (j != -1 && s[i] != s[j]) j = ne[j]; ne[++i] = ++j; } } int main() { scanf("%d%d%d", &m, &n, &MOD); scanf("%s", s); kmp(); Mat b; for (int i = 0; i < n; i++) { for (int j = 0; j < 10; j++) { int k = i; while (k != -1 && s[k] != j + '0') k = ne[k]; k++; if (k < n) b.mat[i][k]++; } } Mat a; a.mat[0][0] = 1; a = qp(a, b, m); int ans = 0; for (int i = 0; i < n; i++) ans += a.mat[0][i]; ans %= MOD; printf("%d\n", ans); return 0; }
1010. [HNOI2008]玩具装箱toy
$dp[i]$ 表示以 $i$ 物品为结尾的最小费用
$dp[i] = min(dp[j] + (j - i + \sum c_k - l)^2)$
斜率优化一下即可。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 5e4 + 7; ll sum[N], l, c[N], dp[N]; int que[N], n; template <class T> inline T sqr (T a) { return a * a; } inline ll X(int i) { return sum[i]; } inline ll Y(int i) { return dp[i] + sqr(sum[i]) + 2 * l * sum[i]; } inline double K(int i, int j) { return 1.0 * (Y(i) - Y(j)) / (X(i) - X(j)); } int main() { scanf("%d%lld", &n, &l); for (int i = 1; i <= n; i++) scanf("%lld", &c[i]), sum[i] = sum[i - 1] + c[i]; for (int i = 1; i <= n; i++) sum[i] += i; int head = 1, tail = 1; l++; for (int i = 1; i <= n; i++) { while (head < tail && K(que[head], que[head + 1]) < 2 * sum[i]) head++; int j = que[head]; dp[i] = dp[j] + sqr(sum[i] - sum[j] - l); while (head < tail && K(que[tail - 1], que[tail]) > K(que[tail], i)) tail--; que[++tail] = i; } printf("%lld\n", dp[n]); return 0; }
1011. [HNOI2008]遥远的行星
怀疑是没有spj。反正是个瞎搞题?
1012. [JSOI2008]最大数maxnumber
开足够位置就变成单点修改区间查询。
线段树维护即可。
#include <bits/stdc++.h> #define ll long long const int N = 2e5 + 7; struct Seg { #define lp p << 1 #define rp p << 1 | 1 ll tree[N << 2]; void pushup(int p) { tree[p] = std::max(tree[lp], tree[rp]); } void update(int p, int l, int r, int pos, ll v) { if (l == r) { tree[p] = v; return; } int mid = l + r >> 1; if (pos <= mid) update(lp, l, mid, pos, v); else update(rp, mid + 1, r, pos, v); pushup(p); } ll query(int p, int l, int r, int x, int y) { if (x <= l && y >= r) return tree[p]; int mid = l + r >> 1; ll ans = 0; if (x <= mid) ans = std::max(ans, query(lp, l, mid, x, y)); if (y > mid) ans = std::max(ans, query(rp, mid + 1, r, x, y)); return ans; } } seg; int main() { int n; ll D; scanf("%d%lld", &n, &D); ll ans = 0; int cnt = 0; for (int i = 1; i <= n; i++) { char s[5]; ll num; scanf("%s%lld", s, &num); if (s[0] == 'A') { num += ans; num %= D; seg.update(1, 1, n, ++cnt, num); } else { printf("%lld\n", ans = seg.query(1, 1, n, cnt - num + 1, cnt)); } } return 0; }
1013. [JSOI2008]球形空间产生器sphere
圆心坐标为 $(a_1, a_2, a_3, \cdots, a_n)$
那么可以列出 $n$ 条等式,两边的平方项都消掉了。那么高斯消元求解即可。
#include <bits/stdc++.h> const int N = 20; const double eps = 1e-10; int n; double a[N][N]; struct Node { double d[N]; } p[N]; void gauss() { for (int i = 1; i <= n; i++) { int r = i; for (int j = i + 1; j <= n; j++) { if (std::fabs(a[r][i]) < std::fabs(a[j][i])) r = j; } if (r != i) std::swap(a[i], a[r]); for (int j = 1; j <= n; j++) { if (j != i) { double t = a[j][i] / a[i][i]; for (int k = i; k <= n + 1; k++) a[j][k] -= a[i][k] * t; } } } for (int i = 1; i <= n; i++) a[i][n + 1] /= a[i][i]; } int main() { scanf("%d", &n); for (int i = 1; i <= n + 1; i++) for (int j = 1; j <= n; j++) scanf("%lf", &p[i].d[j]); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { a[i][j] = 2 * p[i].d[j] - 2 * p[i + 1].d[j]; a[i][n + 1] += p[i].d[j] * p[i].d[j] - p[i + 1].d[j] * p[i + 1].d[j]; } } gauss(); for (int i = 1; i <= n; i++) printf("%.3f%c", a[i][n + 1], " \n"[i == n]); return 0; }
1014. [JSOI2008]火星人prefix
如果没有插入操作,仅仅只有修改操作,那么可以用线段树维护区间哈希值,对于一个查询,二分长度,再判是否相等即可。
现在多了插入操作,那么就用 splay 来维护哈希值,与线段树不同的只是多了非叶子节点也代表一个字符。
#include <bits/stdc++.h> #define ull unsigned long long const int N = 2e5 + 7; ull base[N]; const ull BASE = 19260817; int tol, root, m; char s[N]; struct Splay { int ch[N][2], fa[N], sz[N]; ull val[N], ha[N]; inline bool chk(int x) { return ch[fa[x]][1] == x; } inline void pushup(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1; ha[x] = ha[ch[x][0]] + val[x] * base[sz[ch[x][0]]] + ha[ch[x][1]] * base[(sz[ch[x][0]] + 1)]; } void rotate(int x) { int y = fa[x], z = fa[y], k = chk(x), w = ch[x][k ^ 1]; ch[y][k] = w; fa[w] = y; ch[z][chk(y)] = x; fa[x] = z; ch[x][k ^ 1] = y; fa[y] = x; pushup(y); pushup(x); } void splay(int x, int goal = 0) { while (fa[x] != goal) { int y = fa[x], z = fa[y]; if (z != goal) rotate((chk(x) == chk(y)) ? y : x); rotate(x); } pushup(x); if (!goal) root = x; pushup(x); } int kth(int k) { int cur = root; while (1) { if (ch[cur][0] && k <= sz[ch[cur][0]]) cur = ch[cur][0]; else if (k > 1 + sz[ch[cur][0]]) k -= 1 + sz[ch[cur][0]], cur = ch[cur][1]; else break; } return cur; } ull gethash(int l, int len) { int x = kth(l), y = kth(l + len + 1); splay(x), splay(y, x); return ha[ch[y][0]]; } void insert(int x, int v) { int l = kth(x + 1), r = kth(x + 2); splay(l); splay(r, l); val[++tol] = v; fa[tol] = r; ch[r][0] = tol; splay(tol); } void update(int x, int v) { int l = kth(x), r = kth(x + 2); splay(l); splay(r, l); val[ch[r][0]] = v; splay(ch[r][0]); } int query(int x, int y) { int l = 0, r = tol - std::max(x, y) - 1; int ans = 0; while (l <= r) { int mid = l + r >> 1; if (gethash(x, mid) == gethash(y, mid)) ans = mid, l = mid + 1; else r = mid - 1; } return ans; } void init() { ch[1][1] = 2; root = 1, tol = 2; fa[2] = 1; pushup(2); pushup(1); } } splay; int main() { for (int i = base[0] = 1; i < N; i++) base[i] = base[i - 1] * BASE; scanf("%s%d", s + 1, &m); int n = strlen(s + 1); splay.init(); for (int i = 1; i <= n; i++) splay.insert(i - 1, s[i]); for (int x, y; m--; ) { char s[3]; scanf("%s", s); if (s[0] == 'Q') { scanf("%d%d", &x, &y); printf("%d\n", splay.query(x, y)); } else if (s[0] == 'R') { char ss[3]; scanf("%d%s", &x, s); splay.update(x, s[0]); } else { char ss[3]; scanf("%d%s", &x, s); splay.insert(x, s[0]); } } return 0; }
1015. [JSOI2008]星球大战starwar
并查集不好直接删除边,那么考虑时光倒流,反向加边即可。
#include <bits/stdc++.h> const int N = 4e5 + 7; struct Edge { int x, y; } e[N]; std::vector<int> vec[N]; int n, m, fa[N], a[N], res, ans[N]; bool vis[N]; int getfa(int x) { return x == fa[x] ? x : fa[x] = getfa(fa[x]); } void merge(int x, int y) { x = getfa(x), y = getfa(y); if (x != y) { fa[x] = y; res--; } } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < n; i++) fa[i] = i; for (int i = 0; i < m; i++) scanf("%d%d", &e[i].x, &e[i].y), vec[e[i].x].push_back(e[i].y), vec[e[i].y].push_back(e[i].x); int k; scanf("%d", &k); for (int i = 1; i <= k; i++) scanf("%d", a + i), vis[a[i]] = 1; res = n - k; for (int i = 0; i < m; i++) if (!vis[e[i].x] && !vis[e[i].y]) merge(e[i].x, e[i].y); for (int i = k; i; i--) { ans[i] = res; int u = a[i]; vis[u] = 0; res++; for (int v: vec[u]) if (!vis[v]) merge(u, v); } printf("%d\n", res); for (int i = 1; i <= k; i++) printf("%d\n", ans[i]); return 0; }
1016. [JSOI2008]最小生成树计数
最小生成树会出现多个是因为权值相同的边可替换。把处理同权值的边称为一个阶段,若处理一个阶段中的边的顺序会影响该阶段后连通块的连通性,那么这就与可替换相矛盾。
所以处理完一个阶段后这棵树的连通性应该是固定的。(感觉解释不太清楚,但是应该可以意会吧)。
用并查集 $fa$ 和 邻接矩阵 $G$ 表示每一时刻树的连通情况以及邻接情况。
用并查集 $pre$ 表示上一阶段树的连通情况。
处理一个阶段后,对每个连通块用矩阵树定理求一遍生成树个数,根据乘法原理相乘。
然后再把连通块缩成点再处理下一阶段。
#include <bits/stdc++.h> using namespace std; const int N = 110; const int MOD = 31011; struct E { int u, v, cost; inline bool operator < (const E &rhs) const { return cost < rhs.cost; } void read() { scanf("%d%d%d", &u, &v, &cost); } } edge[N * 10]; int getfa(int x, int *fa) { return x == fa[x] ? x : fa[x] = getfa(fa[x], fa); } int Gauss(int a[N][N], int n) { int ans = 1; for (int i = 1; i <= n; i++) { for (int k = i + 1; k <= n; ++k) { while (a[k][i]) { int d = a[i][i] / a[k][i]; for (int j = i; j <= n; j++) a[i][j] = (a[i][j] - d * a[k][j] + MOD) % MOD; swap(a[i], a[k]); ans = -ans; } } ans = 1LL * ans * a[i][i] % MOD; ans = (ans + MOD) % MOD; } return ans; } int A[N][N], G[N][N], fa[N], pre[N]; vector<int> vec[N]; bool vis[N]; int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) edge[i].read(); sort(edge, edge + m); for (int i = 1; i <= n; i++) pre[i] = i; int last = -1; int ans = 1; for (int k = 0; k <= m; k++) { if (last != edge[k].cost || k == m) { for (int i = 1; i <= n; i++) { if (vis[i]) { int u = getfa(i, fa); vec[u].push_back(i); vis[i] = 0; } } for (int i = 1; i <= n; i++) { if (vec[i].size() > 1) { memset(A, 0, sizeof(A)); int len = vec[i].size(); for (int a = 0; a < len; a++) for (int b = a + 1; b < len; b++) { int u = vec[i][a], v = vec[i][b]; A[a][b] = A[b][a] = -G[u][v]; A[a][a] += G[u][v]; A[b][b] += G[u][v]; } ans = ans * Gauss(A, len - 1) % MOD; for (int a = 0; a < len; a++) pre[vec[i][a]] = i; } } for (int i = 1; i <= n; i++) fa[i] = getfa(i, pre), vec[i].clear(); if (k == m) break; last = edge[k].cost; } int u = edge[k].u, v = edge[k].v; u = getfa(u, pre); v = getfa(v, pre); if (u == v) continue; vis[u] = vis[v] = 1; fa[getfa(u, fa)] = getfa(v, fa); G[u][v]++; G[v][u]++; } int flag = 0; for (int i = 2; i <= n; i++) { if (getfa(i, fa) != getfa(i - 1, fa)) { puts("0"); return 0; } } if (!m) { puts("0"); return 0; } printf("%d\n", ans); return 0; }
1017. [JSOI2008]魔兽地图DotR
一种很妙的树上背包。
$dp[i][j][k]$ 表示处理完 $i$ 的子树,$j$ 个 $i$ 物品贡献给父亲,花费为 $k$ 的最大价值。
再用一个 $f[i][j]$ 表示做完 $i$ 个儿子,花费为 $j$ 的最大价值
然后就可以xjb转移了。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 55; const int INF = 0x3f3f3f3f; const int M = 2e3 + 7; int dp[N][110][M], f[N][M], n, m, mx[N], cost[N], val[N]; int degree[N]; std::vector<std::pii> vec[N]; void dfs(int u) { if (vec[u].empty()) { mx[u] = std::min(mx[u], m / cost[u]); for (int i = 0; i <= mx[u]; i++) for (int j = i; j <= mx[u]; j++) dp[u][i][j * cost[u]] = (j - i) * val[u]; return; } mx[u] = INF; for (auto p: vec[u]) { int v = p.fi; dfs(v); mx[u] = std::min(mx[u], mx[v] / p.se); cost[u] += cost[v] * p.se; } mx[u] = std::min(mx[u], m / cost[u]); memset(f, 0xcf, sizeof f); f[0][0] = 0; for (int c = mx[u]; ~c; c--) { int cur = 0; for (auto p: vec[u]) { int v = p.fi; cur++; for (int j = 0; j <= m; j++) for (int k = 0; k <= j; k++) f[cur][j] = std::max(f[cur][j], f[cur - 1][j - k] + dp[v][c * p.se][k]); } for (int i = 0; i <= c; i++) for (int j = 0; j <= m; j++) dp[u][i][j] = std::max(dp[u][i][j], (c - i) * val[u] + f[cur][j]); } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", val + i); char s[3]; scanf("%s", s); if (s[0] == 'A') { int cnt; scanf("%d", &cnt); while (cnt--) { int who, need; scanf("%d%d", &who, &need); vec[i].push_back(std::pii(who, need)); degree[who]++; } } else { scanf("%d%d", cost + i, mx + i); } } bool flag = 0; for (int i = 1; i <= n; i++) if (degree[i]) flag = 1; int ans = 0; memset(dp, 0xcf, sizeof dp); if (flag) { for (int i = 1; i <= n; i++) if (!degree[i]) { dfs(i); for (int j = 0; j <= mx[i]; j++) for (int k = 0; k <= m; k++) ans = std::max(ans, dp[i][j][k]); } } else { f[0][0] = 0; for (int i = 1; i <= n; i++) for (int k = 0; k <= mx[i]; k++) for (int j = k * cost[i]; j <= m; j++) f[i][j] = std::max(f[i][j], f[i - 1][j - k * cost[i]] + k * val[i]); for (int i = 0; i <= m; i++) ans = std::max(ans, f[n][i]); } printf("%d\n", ans); return 0; }
1018. [SHOI2008]堵塞的交通traffic
线段树分治。具体就是能实现删除/撤销操作。把时间看成下标,那么加入和删除就是一个区间,就像线段树一样分成 $log$ 个区间去操作。
可撤回的并查集就得按秩合并实现了。复杂度为 $O(nlog^2 n)$。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 2e5 + 7; int fa[N], sz[N], top, n, q, cnt, id[3][N], tol; std::pii st[N], query[N]; std::map<int, int> mp[N]; struct Node { int u, v, st, ed; Node(int u = 0, int v = 0, int st = 0, int ed = 0): u(u), v(v), st(st), ed(ed) {} }; std::vector<Node> E; int getfa(int x) { while (fa[x] != x) x = fa[x]; return x; } inline void merge(int x, int y) { x = getfa(x), y = getfa(y); if (sz[x] > sz[y]) std::swap(x, y); fa[x] = y; sz[y] += sz[x]; st[++top] = std::pii(x, y); } void solve(int l, int r, const std::vector<Node> &E) { std::vector<Node> L, R; int mid = l + r >> 1; int temp = top; for (auto p: E) { if (p.st <= l && p.ed >= r) { merge(p.u, p.v); } else { if (p.st <= mid) L.push_back(p); if (p.ed > mid) R.push_back(p); } } if (l == r) { puts(getfa(query[l].fi) == getfa(query[l].se) ? "Y" : "N"); } else { solve(l, mid, L); solve(mid + 1, r, R); } while (top > temp) { int x = st[top].fi, y = st[top].se; fa[x] = x; sz[y] -= sz[x]; top--; } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) id[1][i] = ++tol, id[2][i] = ++tol; while (1) { char s[10]; scanf("%s", s); if (s[0] == 'E') break; int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2); if (s[0] == 'O') { mp[id[x1][y1]][id[x2][y2]] = mp[id[x2][y2]][id[x1][y1]] = E.size(); E.push_back(Node(id[x1][y1], id[x2][y2], q + 1, -1)); } else if (s[0] == 'C') { E[mp[id[x1][y1]][id[x2][y2]]].ed = q; } else { query[++q] = std::pii(id[x1][y1], id[x2][y2]); } } for (auto &p: E) { if (p.ed == -1) p.ed = q; } for (int i = 1; i <= tol; i++) fa[i] = i, sz[i] = 1; solve(1, q, E); return 0; }
1019. [SHOI2008]汉诺塔
%这篇题解
会想到这个解法大概就是汉诺塔问题都是基于递归解决的思想吧,我要移动 $j$ 个盘子,那么就得先移动 $j-1$ 个盘子到一个柱子上,递归解决。
用 $f[j][i]$ 表示 $i$ 上有 $j$ 个盘子,其他柱子上没有盘子,将这 $j$ 个盘子移动到 $g[j][i]$ 上的最小步数
边界 $f[1][i] = 1$,$g[1][i]$ 由输入的优先级决定。
当前 $i$ 柱子上有 $j$ 个盘子,那么就先将 $j - 1$ 个盘子移动到 $a$ 上,$a=g[j - 1][i]$,剩下的柱子 $b = 3 - i - a$。再把最后一个盘子移动到 $b$ 上。
若 $g[j - 1][a] = b$,那么直接把 $a$ 上 $j - 1$ 个盘子移动到 $b$ 上就完成了。
此时 $f[j][i] = f[j - 1][i] + 1 + f[j - 1][a]$,$g[j][i] = b$。
否则 $g[j -1][a] = i$,那么先把 $a$ 上 $j-1$ 个盘子移动到 $i$ 上,再把 $b$ 上的大盘子移动到 $a$ 上,再把 $i$ 上 $j-1$ 个盘子移动到 $a$ 上。
此时 $f[j][i] = f[j - 1][i] + 1 + f[j - 1][a] + 1 + f[j - 1][i]$,$g[j][i] = a$。
答案即为 $f[n][0]$。
因为第一步符合操作优先级,那么第二步是模仿第一步来的,肯定也符合优先级,这么推下去也是符合优先级的。
然后又有一个要求是不移动刚移动的那个盘子,我们在移动完 $f[j - 1][i]$ 或者 $f[j - 1][a]$ 之后,都是移动那个大盘子,而不会再移动这 $j-1$ 个盘子(其实也就是最顶部那个盘子),所以也是符合要求的。
#include <bits/stdc++.h> #define ll long long const int N = 33; ll f[N][3]; int g[N][3]; bool vis[3]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= 6; i++) { static char s[10]; scanf("%s", s); int a = s[0] - 'A', b = s[1] - 'A'; if (vis[a]) continue; vis[a] = 1; f[1][a] = 1; g[1][a] = b; } for (int j = 2; j <= n; j++) for (int i = 0; i < 3; i++) { int a = g[j - 1][i], b = 3 - a - i; if (g[j - 1][a] == b) f[j][i] = f[j - 1][i] + 1 + f[j - 1][a], g[j][i] = b; else f[j][i] = f[j - 1][i] + 1 + f[j - 1][a] + 1 + f[j - 1][i], g[j][i] = a; } printf("%lld\n", f[n][0]); return 0; }
1020. [SHOI2008]安全的航线flight
可以对一条线段每间隔一个 eps 的长度的点找一下答案,但是这样会TLE。
其实得到一个答案后,很多小于这个答案的点就可以不用搜了。
可以迭代地解决问题。
先把所有航线的线段加入一个队列。
1. 取出一条线段后,左端点为 $a$,右端点为 $b$,找到 $a$ 离得最近的陆地的点 $p_1$,并用两者的距离更新答案,找到 $b$ 离的最近的陆地的点 $p_2$,并用两者的距离更新答案。
2. 找到线段 $ab$ 上的点 $p$,使得 $p$ 和 $p_1$ 之间的距离与 $p$ 和 $p_2$ 之间的距离 $d$ 大致相等,这一步可以二分得到。
3. 当 $d$ 的距离比当前 ans 小的时候,就丢掉这条线段。
4. 否则将 $(a, p)$ 和 $(p, b)$ 重新加入队列。
5. 重复上述过程直到队列为空。
第 3 步就是一个最优性剪枝,因为这条线段上的所有点到最近陆地的距离都不超过 $d$。
#include <bits/stdc++.h> const double pi = acos(-1.0); const int INF = 0x3f3f3f3f; const double eps = 1e-9; const int N = 55; int dcmp(double x) { if (fabs(x) < eps) return 0; return x > 0 ? 1 : -1; } struct Point { double x, y; Point(double x = 0, double y = 0): x(x), y(y) {} Point operator + (const Point &p) const { return Point(x + p.x, y + p.y); } Point operator - (const Point &p) const { return Point(x - p.x, y - p.y); } Point operator * (const double &rhs) const { return Point(x * rhs, y * rhs); } Point operator / (const double &rhs) const { return Point(x / rhs, y / rhs); } bool operator == (const Point &p) const { return !dcmp(x - p.x) && !dcmp(y - p.y); } void read() { scanf("%lf%lf", &x, &y); } void print() { printf("%.6f %.6f\n", x, y); } } flight[N]; typedef Point Vector; double Dot(const Vector &a, const Vector &b) { return a.x * b.x + a.y * b.y; } double Len(const Vector &a) { return sqrt(Dot(a, a)); } double Cross(const Vector &a, const Vector &b) { return a.x * b.y - a.y * b.x; } bool On(const Point &p, const Point &a, const Point &b) { return !dcmp(Cross(a - p, b - p)) && dcmp(Dot(a - p, b - p)) <= 0; } Vector Normal(const Vector &a) { int l = Len(a); return Vector(-a.y / l, a.x / l); } struct Seg { Point a, b; Seg() {} Seg(const Point &a, const Point &b): a(a), b(b) {} } queue[1000000 + 5]; struct Polygon { std::vector<Point> poly; int n; bool In(const Point &p) { int wn = 0; for (int i = 1; i <= n; i++) { if (On(p, poly[i], poly[i % n + 1])) return 1; int k = dcmp(Cross(poly[i % n + 1] - poly[i], p - poly[i])); int d1 = dcmp(poly[i].y - p.y); int d2 = dcmp(poly[i % n + 1].y - p.y); if (k > 0 && d1 <= 0 && d2 > 0) wn++; if (k < 0 && d2 <= 0 && d1 > 0) wn--; } if (wn) return 1; return 0; } } island[N]; struct near { Point p; double dis; near() {} near(const Point &a, const double &b): p(a), dis(b) {} }; int n, m; void init() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) flight[i].read(); for (int i = 1; i <= n; i++) { scanf("%d", &island[i].n); island[i].poly.resize(island[i].n + 1); for (int j = 1; j <= island[i].n; j++) island[i].poly[j].read(); } } bool check(const Point &p) { for (int i = 1; i <= n; i++) if (island[i].In(p)) return 1; return 0; } Point GetLineIntersection(const Point &P, const Vector &v, const Point &Q, const Vector &w) { Vector u = P - Q; double t = Cross(w, u) / Cross(v, w); return P + v * t; } // 点 a 到线段 bc 的最近点 near DISPS(const Point &a, const Point &b, const Point &c) { if (b == c) return near(b, Len(b - a)); Vector v1 = c - b, v2 = a - b, v3 = a - c; if (dcmp(Dot(v1, v2)) <= 0) return near(b, Len(v2)); if (dcmp(Dot(v1, v3)) >= 0) return near(c, Len(v3)); Vector v = Normal(b - c); Point p = GetLineIntersection(a, v, b, v1); return near(p, Len(a - p)); } double ans; near Find(const Point &p) { if (check(p)) return near(p, 0); near ans1; ans1.dis = 1e10; for (int i = 1; i <= n; i++) for (int j = 1; j <= island[i].n; j++) { near cur = DISPS(p, island[i].poly[j], island[i].poly[j % island[i].n + 1]); if (dcmp(ans1.dis - cur.dis) >= 0) ans1 = cur; } ans = std::max(ans, ans1.dis); return ans1; } const int M = 1e5; void solve() { int head = 0, tail = 0; for (int i = 1; i < m; i++) queue[++tail] = Seg(flight[i], flight[i + 1]), Find(flight[i]); Find(flight[m]); while (head != tail) { Seg cur = queue[head = head % M + 1]; Point p1 = Find(cur.a).p, p2 = Find(cur.b).p, l = cur.a, r = cur.b; while (Len(r - l) > 1e-4) { Point mid = (l + r) / 2; if (Len(mid - p1) < Len(mid - p2)) l = mid; else r = mid; } double nowans = std::min(Len(l - p1), Len(l - p2)); Find(l); if (ans + 0.0005 < nowans) { queue[tail = tail % M + 1] = Seg(cur.a, l); queue[tail = tail % M + 1] = Seg(l, cur.b); } } } int main() { freopen("in.txt", "r", stdin); init(); solve(); printf("%.2f\n", ans); return 0; }
1021. [SHOI2008]Debt 循环的债务
自己的DP真是弱爆了...虽然其他也弱...看完别人的题解恍然大悟...
$dp[i][j][k]$ 表示前 $i$ 种钞票,第一个人有 $j$ 块钱,第二个人有 $k$ 块钱所需要交换的次数。
$dp[0][第一个人初始钱数][第二个人初始钱数] = 0$
然后就暴力枚举两个人要多少张 $i$ 钞票转移即可。
#include <bits/stdc++.h> const int N = 1100; const int INF = 0x3f3f3f3f; const int val[7] = {0, 100, 50, 20, 10, 5, 1}; int dp[7][N][N], sum, cnt[5][10], d[10], e[10]; inline bool chkmin(int &a, const int &b) { return a > b ? a = b, 1: 0; } int main() { int a, b, c; scanf("%d%d%d", &a, &b, &c); for (int i = 1; i <= 3; i++) for (int j = 1; j <= 6; j++) { scanf("%d", &cnt[i][j]); sum += cnt[i][j] * val[j]; d[i] += cnt[i][j] * val[j]; e[j] += cnt[i][j]; } int lasta = d[1] - a + c, lastb = d[2] - b + a, lastc = sum - lasta - lastb; if (lasta < 0 || lastb < 0 || lastc < 0) { puts("impossible"); return 0; } memset(dp, 0x3f, sizeof dp); dp[0][d[1]][d[2]] = 0; for (int i = 1; i <= 6; i++) for (int j = 0; j <= sum; j++) for (int k = 0; k + j <= sum; k++) if (dp[i - 1][j][k] != INF) { chkmin(dp[i][j][k], dp[i - 1][j][k]); for (int m = 0; m <= e[i]; m++) for (int n = 0; n + m <= e[i]; n++) { int difa = m - cnt[1][i], difb = n - cnt[2][i]; int jj = j + difa * val[i], kk = k + difb * val[i]; if (jj < 0 || kk < 0 || sum - jj - kk < 0) continue; chkmin(dp[i][jj][kk], dp[i - 1][j][k] + (std::abs(difa) + std::abs(difb) + std::abs(difa + difb)) / 2); } } if (dp[6][lasta][lastb] == INF) puts("impossible"); else printf("%d\n", dp[6][lasta][lastb]); return 0; }
1022. [SHOI2008]小约翰的游戏John
Anti-Nim模板题。
先手必胜当且仅当
1. 所有石堆个数小于 $2$ 且 SG 值为 $0$
2. 存在一个石堆个数不小于 $2$ 且 SG 值不为 $0$
#include <bits/stdc++.h> int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); bool flag = 0; int sg = 0; while (n--) { int x; scanf("%d", &x); sg ^= x; if (x > 1) flag = 1; } if (flag) puts(sg ? "John" : "Brother"); else puts(!sg ? "John" : "Brother"); } return 0; }
1023. [SHOI2008]cactus仙人掌图
考虑求树上的直径,$f[u]$ 表示 $u$ 到以 $u$ 为子树中的节点的距离最大值,$ans$ 表示树的直径。
$ans = max \{f[u] + f[v] + 1 \}, v \in son(u)$
$f[u] = max \{f[v] + 1 \}, v \in son(u)$
如果能将环缩成点,那么就直接做就行了。
先跑tarjan,求出 $low$、$dfn$、$dep$ 数组,如果这条边是一条树边,即满足 $dfn[u] < low[v]$,$v \in son(u)$,就直接按上面的方法更新。
否则不更新。
然后如果 $dfn[u] < dfn[v] \wedge fa[v] \neq u$,则说明 $u$ 到 $v$ 是一个环,并且 $u$ 是环的起点,$v$ 是环的终点。
就把这个环单独处理一下,并把最长链的信息整合到 $u$ 上即可。
对一个环上处理,即把环上所有顶点拿出来暴力更新答案。
把环上节点按深度依次放到数组里,$C$ 代表这个环,$L$ 代表环的节点个数。
$ans = max \{ f[u] + f[v] + dis(u, v) \}, u \in C, v \in C$
$dis(u, v) = min \{ dep[v] - dep[u], L - (dep[v] - dep[u]) \}$
在数组里按深度排好序之后就可以用下标之差来表示距离了。
$ans = max \{ f[v] + v + f[u] - u \},2 \times dis(u, v) \leq L$
可以用单调队列优化。注意得把数组复制一下,因为最优点有可能被 $u$ 和 $v$ 隔开了。
$f[u] = max \{ f[v] + dis(u, v) \}$ 把最长链合并到 $u$ 上。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } #define read IO::read #define print IO::print inline bool chkmin(int &x, int y) { return x > y ? x = y, 1 : 0; } inline bool chkmax(int &x, int y) { return x < y ? x = y, 1 : 0; } const int N = 1e5 + 7; std::vector<int> vec[N]; int fa[N], dfn[N], low[N], tol, dep[N], n, m, dp[N], ans; int que[N], a[N]; void solve(int u, int v) { int cnt = dep[v] - dep[u] + 1; for (int i = v; i != u; i = fa[i]) a[cnt--] = dp[i]; a[1] = dp[u]; cnt = dep[v] - dep[u] + 1; for (int i = 1; i <= cnt; i++) a[i + cnt] = a[i]; int l = 0, r = 0; que[l = r = 1] = 1; for (int i = 2; i <= cnt + (cnt >> 1); i++) { if (i - que[l] > (cnt >> 1)) l++; chkmax(ans, a[i] + i + a[que[l]] - que[l]); while (l <= r && a[i] - i >= a[que[r]] - que[r]) r--; que[++r] = i; } for (int i = 2; i <= cnt; i++) chkmax(dp[u], a[i] + std::min(i - 1, cnt - i + 1)); } void dfs(int u, int pre) { fa[u] = pre; dep[u] = dep[pre] + 1; dfn[u] = low[u] = ++tol; for (int v: vec[u]) { if (v == pre) continue; if (!dfn[v]) { dfs(v, u); chkmin(low[u], low[v]); } else { chkmin(low[u], dfn[v]); } if (dfn[u] < low[v]) chkmax(ans, dp[u] + dp[v] + 1), chkmax(dp[u], dp[v] + 1); } for (int v: vec[u]) { if (dfn[u] < dfn[v] && fa[v] != u) solve(u, v); } } int main() { read(n, m); for (int i = 1; i <= m; i++) { int k, u; read(k, u); for (int j = 2; j <= k; j++) { int v; read(v); vec[u].push_back(v); vec[v].push_back(u); u = v; } } dfs(1, 0); print(ans); IO::flush(); return 0; }
1024. [SCOI2009]生日快乐
由于 $n$ 很小,那么可以暴力搜一波。
因为所有面积必须相同,所以考虑每一刀的时候,左右两部分必须长度符合比例。
#include <bits/stdc++.h> double solve(double x, double y, int n) { if (x < y) std::swap(x, y); if (n == 1) return x / y; double ans = 1e9; for (int i = 1; i <= n / 2; i++) { ans = std::min(ans, std::max(solve(x / n * i, y, i), solve(x / n * (n - i), y, n - i))); ans = std::min(ans, std::max(solve(x, y / n * i, i), solve(x, y / n * (n - i), n - i))); } return ans; } int main() { double x, y; int n; scanf("%lf%lf%d", &x, &y, &n); printf("%.6f\n", solve(x, y, n)); return 0; }
1026. [SCOI2009]windy数
数位DP裸题啦。
#include <bits/stdc++.h> #define int long long using namespace std; int dp[20][20]; int a[20]; int DP(int pos, int pre, bool limit, bool lead) { if (pos < 0) return 1; if (!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre]; int ans = 0; int up = limit ? a[pos] : 9; if (lead) { ans += DP(pos - 1, 0, limit && 0 == up, 1); for (int i = 1; i <= up; i++) ans += DP(pos - 1, i, limit && i == up, 0); } else { for (int i = 0; i <= pre - 2 && i <= up; i++) ans += DP(pos - 1, i, limit && i == up, 0); for (int i = pre + 2; i <= up; i++) ans += DP(pos - 1, i, limit && i == up, 0); } if (!limit && !lead) dp[pos][pre] = ans; return ans; } int solve(int x) { //if (x == 0) return 1; int pos = 0; while (x) { a[pos++] = x % 10; x /= 10; } return DP(pos - 1, 0, 1, 1); } signed main() { int l, r; scanf("%lld%lld", &l, &r); memset(dp, -1, sizeof(dp)); printf("%lld\n", solve(r) - solve(l - 1)); return 0; }
1027. [JSOI2007]合金
计算几何的题怎么都这么妙啊!
首先第三个元素是没有用的,因为如果前两个符合了,第三个肯定也符合。
然后把第一个元素看成 $x$ 轴,第二个元素看成 $y$ 轴。
那么对于一些原材料能形成的合金肯定在这些原材料的凸包内。
枚举每一对原材料 $(i, j)$,若所有合金都在其逆时针方向,那么 $i$ 就向 $j$ 连一条边。
之后就是求这个图的最小环,floyd即可。
不过需要特判一种情况,当所有合金都是一个点时,且有原材料刚好就是这种合金,答案就是 $1$。
看到有的博客说,所有金属都在一条线段上要特判,其实这种情况能被后面处理掉。即 $i$ 会向 $j$ 连一条边,$j$ 会向 $i$ 连一条边,答案就是 $2$。
所以就不用特判啦。
#include <bits/stdc++.h> #define db double const db eps = 1e-9; inline int sign(db k) { return k < -eps ? -1 : k > eps; } inline int cmp(db k1, db k2) { return sign(k1 - k2); } struct P { db x, y; P() {} P(db x, db y): x(x), y(y) {} P operator + (const P &rhs) const { return P(x + rhs.x, y + rhs.y); } P operator - (const P &rhs) const { return P(x - rhs.x, y - rhs.y); } P operator * (const db &k) const { return P(x * k, y * k); } P operator / (const db &k) const { return P(x / k, y / k); } bool operator < (const P &rhs) const { int c = cmp(x, rhs.x); return c ? c == -1 : cmp(y, rhs.y) == -1; } bool operator == (const P &rhs) const { return !cmp(x, rhs.x) && !cmp(y, rhs.y); } db distTo(const P &rhs) const { return (*this - rhs).abs(); } db alpha() { return atan2(y, x); } void read() { scanf("%lf%lf", &x, &y); } void print() { printf("%.10f %.10f\n", x, y); } db abs() { return sqrt(abs2()); } db abs2() { return x * x + y * y; } P rotate(const db &k) { return P(x * cos(k) - y * sin(k), x * sin(k) + y * cos(k)); } P rotate90() { return P(-y, x); } P unit() { return *this/abs(); } P normal() { return rotate90() / abs(); } int quad() { return sign(y) == 1 || (sign(y) == 0 && sign(x) >= 0); } db dot(const P &p) const { return x * p.x + y * p.y; } db det(const P &p) const { return x * p.y - y * p.x; } }; #define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) #define crossOp(p1, p2, p3) sign(cross(p1, p2, p3)) // 判断 p1p2 和 q1q2 是否相交 bool chkLL(const P &p1, const P &p2, const P &q1, const P &q2) { db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2); return sign(a1 + a2) != 0; } P isLL(const P &p1, const P &p2, const P &q1, const P &q2) { assert(chkLL(p1, p2, q1, q2)); db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2); return (p1 * a2 + p2 * a1) / (a1 + a2); } bool intersect(db l1, db r1, db l2, db r2) { if (l1 > r1) std::swap(l1, r2); if (l2 > r2) std::swap(l2, r2); return !(cmp(r1, l2) == -1 || cmp(r2, l1) == -1); } bool isSS(const P &p1, const P &p2, const P &q1, const P &q2) { return intersect(p1.x, p2.x, q1.x, q2.x) && intersect(p1.y, p2.y, q1.y, q2.y) && crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <= 0 && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= 0; } bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) { return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) < 0 && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < 0; } bool isMiddle(db a, db m, db b) { return sign(a - m) == 0 || sign(b - m) == 0 || (a < m != b < m); } bool isMiddle(const P &a, const P &m, const P &b) { return isMiddle(a.x, m.x, b.x) && isMiddle(a.y, m.y, b.y); } bool onSeg(const P &p1, const P &p2, const P &q) { return crossOp(p1, p2, q) == 0 && isMiddle(p1, q, p2); } bool onSeg_strict(const P &p1, const P &p2, const P &q) { return crossOp(p1, p2, q) == 0 && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < 0; } P proj(const P &p1, const P &p2, const P &q) { P dir = p2 - p1; return p1 + dir * (dir.dot(q - p1) / dir.abs2()); } P reflect(const P &p1, const P &p2, const P &q) { return proj(p1, p2, q) * 2 - q; } db nearest(const P &p1, const P &p2, const P &q) { P h = proj(p1, p2, q); if (isMiddle(p1, h, p2)) return q.distTo(h); return std::min(p1.distTo(q), p2.distTo(q)); } db disSS(const P &p1, const P &p2, const P &q1, const P &q2) { if (isSS(p1, p2, q1, q2)) return 0; return std::min(std::min(nearest(p1, p2, q1), nearest(p1, p2, q2)), std::min(nearest(q1, q2, p1), nearest(q1, q2, p2))); } db rad(const P &p1, const P &p2) { return atan2l(p1.det(p2), p1.dot(p2)); } const int N = 550; int n, m; P material[N], alloy[N]; bool spj() { for (int i = 1; i < n; i++) if (!(alloy[i] == alloy[i - 1])) return 0; for (int i = 0; i < m; i++) if (alloy[0] == material[i]) return 1; return 0; } const int INF = 0x3f3f3f3f; int dis[N][N]; inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; } int floyd() { for (int k = 0; k < m; k++) for (int i = 0; i < m; i++) if (dis[i][k] < INF) for (int j = 0; j < m; j++) chkmin(dis[i][j], dis[i][k] + dis[k][j]); int ans = INF; for (int i = 0; i < m; i++) chkmin(ans, dis[i][i]); return ans; } void addedge(int p, int q) { for (int i = 0; i < n; i++) { int k = crossOp(material[p], material[q], alloy[i]); if (k > 0) continue; if (k < 0) return; if (!onSeg(material[p], material[q], alloy[i])) return; } dis[p][q] = 1; } int main() { scanf("%d%d", &m, &n); for (int i = 0; i < m; i++) { material[i].read(); db x; scanf("%lf", &x); } for (int i = 0; i < n; i++) { alloy[i].read(); db x; scanf("%lf", &x); } if (spj()) { puts("1"); return 0; } memset(dis, 0x3f, sizeof dis); for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) if (i != j) addedge(i, j); int ans = floyd(); printf("%d\n", ans == INF ? -1 : ans); return 0; }
1028. [JSOI2007]麻将
暴力枚举要补哪张牌,再枚举谁是对子,最后 check 一下剩下能否组成顺子或刻子,优先 check 刻子。
#include <cstdio> #include <vector> #include <algorithm> const int N = 407; int a[N], cnt[N], n, m, temp[N]; bool solve() { for (int i = 1; i <= n; i++) { if (cnt[i] < 2) continue; bool flag = 1; cnt[i] -= 2; for (int j = 1; j <= n + 2; j++) temp[j] = cnt[j]; for (int j = 1; j <= n + 2; j++) { if (temp[j] < 0) { flag = 0; break; } temp[j] %= 3; temp[j + 1] -= temp[j]; temp[j + 2] -= temp[j]; } cnt[i] += 2; if (flag) return 1; } return 0; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= 3 * m + 1; i++) { int x; scanf("%d", &x); cnt[x]++; } std::vector<int> ans; for (int i = 1; i <= n; i++) { cnt[i]++; if (solve()) ans.push_back(i); cnt[i]--; } if (ans.size() == 0) puts("NO"); else for (int x: ans) printf("%d ", x); puts(""); return 0; }
1029. [JSOI2007]建筑抢修
ddl 靠前的先做,如果做不了,看看之前有没有耗时比它多的,有的话换掉,用堆维护。
#include <bits/stdc++.h> #define ll long long const int N = 2e5 + 7; struct P { ll x, y; bool operator < (const P &pp) const { return y < pp.y; } } p[N]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%lld%lld", &p[i].x, &p[i].y); ll cur = 0; std::sort(p + 1, p + 1 + n); std::priority_queue<int> que; que.push(0); int ans = 0; for (int i = 1; i <= n; i++) { if (cur + p[i].x <= p[i].y) { cur += p[i].x; que.push(p[i].x); ans++; } else if (p[i].x < que.top()) { cur += p[i].x - que.top(); que.pop(); que.push(p[i].x); } } printf("%d\n", ans); return 0; }
1030. [JSOI2007]文本生成器
建出AC自动机后DP,注意第一维要枚举长度,因为第一维枚举节点的话一些节点会往回跳。
#include <bits/stdc++.h> const int N = 5000; const int sz = 26; const int MOD = 10007; void M(int &a) { if (a >= MOD) a -= MOD; } int n, m; struct Aho { int trie[N][sz], tol, fail[N], last[N]; bool flag[N]; int dp[N][110]; void init() { tol = 0; memset(dp, 0, sizeof(dp)); newnode(); } int newnode() { for (int i = 0; i < sz; i++) trie[tol][i] = 0; fail[tol] = flag[tol] = last[tol] = 0; return tol++; } void insert(char *s) { int n = strlen(s), cur = 0;; for (int i = 0; i < n; i++) { int id = s[i] - 'A'; if (!trie[cur][id]) trie[cur][id] = newnode(); cur = trie[cur][id]; } flag[cur] = 1; } int build() { std::queue<int> que; for (int i = 0; i < sz; i++) if (trie[0][i]) que.push(trie[0][i]); while (!que.empty()) { int u = que.front(); que.pop(); flag[u] |= flag[fail[u]]; for (int i = 0; i < sz; i++) { int &v = trie[u][i]; if (v) { fail[v] = trie[fail[u]][i]; que.push(v); last[v] = flag[fail[v]] ? fail[v] : last[fail[v]]; } else { v = trie[fail[u]][i]; } } } dp[0][0] = 1; for (int j = 0; j < m; j++) for (int i = 0; i < tol; i++) if (dp[i][j] && !flag[i]) for (int k = 0; k < sz; k++) { M(dp[trie[i][k]][j + 1] += dp[i][j]); } int ans = 0; for (int i = 0; i < tol; i++) if (!flag[i]) M(ans += dp[i][m]); return ans; } } ac; char s[N]; int qp(int a, int b) { int ans = 1; while (b) { if (b & 1) ans = ans * a % MOD; a = a * a % MOD; b >>= 1; } return ans; } int main() { while (~scanf("%d%d", &n, &m)) { ac.init(); for (int i = 0; i < n; i++) scanf("%s", s), ac.insert(s); printf("%d\n", (qp(26, m) - ac.build() + MOD) % MOD); } return 0; }
1031. [JSOI2007]字符加密Cipher
把字符串复制一下,求个后缀数组,再按sa枚举后缀,判断长度是否大于等于原字符串长度,注意坑就是第 $1$ 和 第 $len + 1$ 个后缀可能会重复。
#include <bits/stdc++.h> const int N = 2e5 + 7; char s[N]; namespace SA { int sa[N], rk[N], fir[N], sec[N], c[N], height[N]; void build(int len, int num = 130) { register int i, j, k; for (i = 1; i <= num; i++) c[i] = 0; for (i = 1; i <= len; i++) ++c[fir[i] = s[i]]; for (i = 1; i <= num; i++) c[i] += c[i - 1]; for (i = len; i >= 1; i--) sa[c[fir[i]]--] = i; for (k = 1; k <= len; k <<= 1) { int cnt = 0; for (i = len - k + 1; i <= len; i++) sec[++cnt] = i; for (i = 1; i <= len; i++) if (sa[i] > k) sec[++cnt] = sa[i] - k; for (i = 1; i <= num; i++) c[i] = 0; for (i = 1; i <= len; i++) ++c[fir[i]]; for (i = 1; i <= num; i++) c[i] += c[i - 1]; for (i = len; i >= 1; i--) sa[c[fir[sec[i]]]--] = sec[i], sec[i] = 0; std::swap(fir, sec); fir[sa[1]] = 1; cnt = 1; for (i = 2; i <= len; i++) fir[sa[i]] = (sec[sa[i]] == sec[sa[i - 1]] && sec[sa[i] + k] == sec[sa[i - 1] + k]) ? cnt : ++cnt; if (cnt == len) break; num = cnt; } k = 0; for (i = 1; i <= len; i++) rk[sa[i]] = i; for (i = 1; i <= len; i++) { if (rk[i] == 1) continue; if (k) k--; j = sa[rk[i] - 1]; while (j + k <= len && i + k <= len && s[i + k] == s[j + k]) k++; height[rk[i]] = k; } } } using namespace SA; char ans[N]; int cnt; bool vis[N]; int id(int x, int len) { if (x > len) x -= len; return x; } int main() { scanf("%s", s + 1); int len = strlen(s + 1); for (int i = 1; i <= len; i++) s[i + len] = s[i]; build(len * 2); for (int i = 1; i <= 2 * len; i++) { if (sa[i] <= len + 1 && !vis[id(sa[i], len)]) { ans[++cnt] = s[sa[i] + len - 1]; vis[id(sa[i], len)] = 1; if (cnt == len) { puts(ans + 1); return 0; } } } return 0; }
1032. [JSOI2007]祖码Zuma
区间DP,把同颜色的段缩成一个点,用 $num$ 数组表示个数。
$f[l][r]$ 表示消除 $l$ 到 $r$ 区间所有点的最小花费。
当 $num[i] > 1$,$f[i][i] = 1$,否则等于 $2$。
然后区间DP,若左右端点相同时 $f[l][r] = f[l + 1][r - 1] + c$,其中当 $num[l] + num[r] > 2$ 时,$c = 0$,否则 $c = 1$。
然后枚举中断点即可。
#include <bits/stdc++.h> const int INF = 0x3f3f3f3f; const int N = 550; int f[N][N], num[N], color[N], cnt, n; int a[N]; int main() { memset(f, 0x3f, sizeof f); scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); int cur = 1; for (int i = 2; i <= n; i++) { if (a[i] != a[i - 1]) { color[++cnt] = a[i - 1]; num[cnt] = cur; cur = 1; } else { cur++; } } color[++cnt] = a[n]; num[cnt] = cur; for (int i = 1; i <= cnt; i++) f[i][i] = num[i] > 1 ? 1 : 2; for (int len = 2; len <= cnt; len++) for (int l = 1; l <= cnt; l++) { int r = l + len - 1; if (r > cnt) break; if (color[l] == color[r]) f[l][r] = f[l + 1][r - 1] + ((num[l] + num[r]) > 2 ? 0 : 1); for (int k = l; k < r; k++) f[l][r] = std::min(f[l][r], f[l][k] + f[k + 1][r]); } printf("%d\n", f[1][cnt]); return 0; }
1034. [ZJOI2008]泡泡堂BNB
感觉我双指针不行啊...写了个multiset过的。看了别人代码改了发双指针。
写几个样例大概就知道跟田忌赛马一样了。
#include <bits/stdc++.h> const int N = 1e5 + 7; int a[N], b[N]; std::multiset<int> st1, st2; bool vis[N]; int solve(int A[], int B[], int n) { int ans = 0; int l1 = 1, l2 = 1, r1 = n, r2 = n; while (l1 <= r1 && l2 <= r2) { if (A[l1] > B[l2]) ans += 2, l1++, l2++; else if (A[r1] > B[r2]) ans += 2, r1--, r2--; else ans += (A[l1] == B[r2]) ? 1 : 0, l1++, r2--; } return ans; } int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) scanf("%d", b + i); std::sort(a + 1, a + 1 + n); std::sort(b + 1, b + 1 + n); printf("%d %d\n", solve(a, b, n), 2 * n - solve(b, a, n)); }
1036. [ZJOI2008]树的统计Count
树剖板子题。
#include <bits/stdc++.h> using namespace std; template<typename T> inline void read(T &x) { x = 0; T f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); } x *= f; } const int INF = 0x3f3f3f3f; const int N = 30010; int w[N], wt[N], n, q; int sz[N], son[N], fa[N], dep[N]; int dfn[N], tol, top[N]; struct E { int v, ne; } e[N * 2]; int head[N], cnt; struct Seg { #define lp p << 1 #define rp p << 1 | 1 int sum[N << 2], mx[N << 2]; inline void pushup(int p) { sum[p] = sum[lp] + sum[rp]; mx[p] = max(mx[lp], mx[rp]); } void build(int p, int l, int r) { if (l == r) { sum[p] = mx[p] = wt[l]; return; } int mid = l + r >> 1; build(lp, l, mid); build(rp, mid + 1, r); pushup(p); } void update(int p, int l, int r, int pos, int val) { if (l == r) { sum[p] = mx[p] = val; return; } int mid = l + r >> 1; if (pos <= mid) update(lp, l, mid, pos, val); else update(rp, mid + 1, r, pos, val); pushup(p); } int query1(int p, int l, int r, int x, int y) { if (x <= l && y >= r) return sum[p]; int ans = 0; int mid = l + r >> 1; if (x <= mid) ans += query1(lp, l, mid, x, y); if (y > mid) ans += query1(rp, mid + 1, r, x, y); return ans; } int query2(int p, int l, int r, int x, int y) { if (x <= l && y >= r) return mx[p]; int ans = -INF; int mid = l + r >> 1; if (x <= mid) ans = max(ans, query2(lp, l, mid, x, y)); if (y > mid) ans = max(ans, query2(rp, mid + 1, r, x, y)); return ans; } } seg; inline void add(int u, int v) { e[++cnt].v = v; e[cnt].ne = head[u]; head[u] = cnt; e[++cnt].v = u; e[cnt].ne = head[v]; head[v] = cnt; } void dfs1(int u, int pre, int d) { dep[u] = d; fa[u] = pre; sz[u] = 1; for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (v == pre) continue; dfs1(v, u, d + 1); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } void dfs2(int u, int tp) { top[u] = tp; dfn[u] = ++tol; wt[tol] = w[u]; if (!son[u]) return; dfs2(son[u], tp); for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (v != fa[u] && v != son[u]) dfs2(v, v); } } int solve1(int u, int v) { int ans = 0; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); ans += seg.query1(1, 1, n, dfn[top[u]], dfn[u]); u = fa[top[u]]; } if (dep[u] > dep[v]) swap(u, v); ans += seg.query1(1, 1, n, dfn[u], dfn[v]); return ans; } int solve2(int u, int v) { int ans = -INF; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); ans = max(ans, seg.query2(1, 1, n, dfn[top[u]], dfn[u])); u = fa[top[u]]; } if (dep[u] > dep[v]) swap(u, v); ans = max(ans, seg.query2(1, 1, n, dfn[u], dfn[v])); return ans; } char s[20]; int main() { read(n); for (int i = 1; i < n; i++) { int u, v; read(u), read(v); add(u, v); } for (int i = 1; i <= n; i++) read(w[i]); dfs1(1, 0, 1); dfs2(1, 1); seg.build(1, 1, n); read(q); while (q--) { scanf("%s", s); int u, v; read(u), read(v); if (s[1] == 'M') { printf("%d\n", solve2(u, v)); } else if (s[1] == 'S') { printf("%d\n", solve1(u, v)); } else { seg.update(1, 1, n, dfn[u], v); } } return 0; }
1037. [ZJOI2008]生日聚会Party
$dp[i][j][k][l]$ 表示 $i$ 个男生,$j$ 个女生,结尾段男生最多比女生多 $k$ 个,女生最多比男生多 $l$ 个。
$dp[i + 1][j][k + 1][\max\{l - 1, 0\}]$ $+=$ $dp[i][j][k][l]$
$dp[i][j+1][\max\{k - 1, 0\}][l+1]$ $+=$ $dp[i][j][k][l]$
#include <bits/stdc++.h> const int MOD = 12345678; const int N = 157; int dp[N][N][22][22]; void M(int &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } int main() { int n, m, k; scanf("%d%d%d", &n, &m, &k); dp[0][0][0][0] = 1; for (int i = 0; i <= n; i++) for (int j = 0; j <= m; j++) for (int o = 0; o <= k; o++) for (int p = 0; p <= k; p++) if (dp[i][j][o][p]) { if (i < n && o < k) M(dp[i + 1][j][o + 1][std::max(p - 1, 0)] += dp[i][j][o][p]); if (j < m && p < k) M(dp[i][j + 1][std::max(o - 1, 0)][p + 1] += dp[i][j][o][p]); } int ans = 0; for (int i = 0; i <= k; i++) for (int j = 0; j <= k; j++) M(ans += dp[n][m][i][j]); printf("%d\n", ans); return 0; }
1038. [ZJOI2008]瞭望塔
能看到其他所有点的区域就是轮廓线的半平面交。
然后最小高度就是半平面交与轮廓线这两个一次分段函数的差,极值肯定出现在分段点上,分别求一下即可。
#include <bits/stdc++.h> #define db double const db eps = 1e-9; inline int sign(db k) { return k < -eps ? -1 : k > eps; } inline int cmp(db k1, db k2) { return sign(k1 - k2); } struct P { db x, y; P() {} P(db x, db y): x(x), y(y) {} P operator + (const P &rhs) const { return P(x + rhs.x, y + rhs.y); } P operator - (const P &rhs) const { return P(x - rhs.x, y - rhs.y); } P operator * (const db &k) const { return P(x * k, y * k); } P operator / (const db &k) const { return P(x / k, y / k); } bool operator < (const P &rhs) const { int c = cmp(x, rhs.x); return c ? c == -1 : cmp(y, rhs.y) == -1; } bool operator == (const P &rhs) const { return !cmp(x, rhs.x) && !cmp(y, rhs.y); } db distTo(const P &rhs) const { return (*this - rhs).abs(); } db alpha() { return atan2(y, x); } void read() { scanf("%lf%lf", &x, &y); } void print() { printf("%.10f %.10f\n", x, y); } db abs() { return sqrt(abs2()); } db abs2() { return x * x + y * y; } P rot(const db &k) { return P(x * cos(k) - y * sin(k), x * sin(k) + y * cos(k)); } P rot90() { return P(-y, x); } P unit() { return *this / abs(); } P normal() { return rot90() / abs(); } int quad() { return sign(y) == 1 || (sign(y) == 0 && sign(x) >= 0); } db dot(const P &p) const { return x * p.x + y * p.y; } db det(const P &p) const { return x * p.y - y * p.x; } }; struct L { // ps[0] -> ps[1] P ps[2]; L() {} L(const P &p0, const P &p1) { ps[0] = p0; ps[1] = p1; } P &operator[](int i) { return ps[i]; } P dir() { return ps[1] - ps[0]; } bool include(const P &p) { return sign((ps[1] - ps[0]).det(p - ps[0])) > 0; } L push() { // push eps outawrd const db Eps = 1e-6; P delta = (ps[1] - ps[0]).normal() * Eps; return {ps[0] - delta, ps[1] - delta}; } }; #define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) #define crossOp(p1, p2, p3) sign(cross(p1, p2, p3)) // 判断 p1p2 和 q1q2 是否相交 bool chkLL(const P &p1, const P &p2, const P &q1, const P &q2) { db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2); return sign(a1 + a2) != 0; } // 直线交点 P isLL(const P &p1, const P &p2, const P &q1, const P &q2) { assert(chkLL(p1, p2, q1, q2)); db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2); return (p1 * a2 + p2 * a1) / (a1 + a2); } P isLL(L l1, L l2) { return isLL(l1[0], l1[1], l2[0], l2[1]); } /***** 线段相交 *****/ bool intersect(db l1, db r1, db l2, db r2) { if (l1 > r1) std::swap(l1, r2); if (l2 > r2) std::swap(l2, r2); return !(cmp(r1, l2) == -1 || cmp(r2, l1) == -1); } bool isSS(const P &p1, const P &p2, const P &q1, const P &q2) { return intersect(p1.x, p2.x, q1.x, q2.x) && intersect(p1.y, p2.y, q1.y, q2.y) && crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <= 0 && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= 0; } bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) { return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) < 0 && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < 0; } /***************/ /***** 点在线段上判定 *****/ bool isMiddle(db a, db m, db b) { return sign(a - m) == 0 || sign(b - m) == 0 || (a < m != b < m); } bool isMiddle(const P &a, const P &m, const P &b) { return isMiddle(a.x, m.x, b.x) && isMiddle(a.y, m.y, b.y); } bool onSeg(const P &p1, const P &p2, const P &q) { return crossOp(p1, p2, q) == 0 && isMiddle(p1, q, p2); } bool onSeg_strict(const P &p1, const P &p2, const P &q) { return crossOp(p1, p2, q) == 0 && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < 0; } /*******************/ // 投影 P proj(const P &p1, const P &p2, const P &q) { P dir = p2 - p1; return p1 + dir * (dir.dot(q - p1) / dir.abs2()); } // 反射 P reflect(const P &p1, const P &p2, const P &q) { return proj(p1, p2, q) * 2 - q; } // 最近点 db nearest(const P &p1, const P &p2, const P &q) { P h = proj(p1, p2, q); if (isMiddle(p1, h, p2)) return q.distTo(h); return std::min(p1.distTo(q), p2.distTo(q)); } // 线段距离 db disSS(const P &p1, const P &p2, const P &q1, const P &q2) { if (isSS(p1, p2, q1, q2)) return 0; return std::min(std::min(nearest(p1, p2, q1), nearest(p1, p2, q2)), std::min(nearest(q1, q2, p1), nearest(q1, q2, p2))); } // 夹角 db rad(const P &p1, const P &p2) { return atan2l(p1.det(p2), p1.dot(p2)); } // 多边形面积 db area(const std::vector<P> &ps) { db ans = 0; for (int i = 0, n = ps.size(); i < n; i++) ans += ps[i].det(ps[(i + 1) % n]); return ans; } // 点包含 2: inside 1: onSeg 0: outside int contain(const std::vector<P> &ps, const P &p) { int n = ps.size(), ret = 0; for (int i = 0; i < n; i++) { P u = ps[i], v = ps[(i + 1) % n]; if (onSeg(u, v, p)) return 1; if (cmp(u.y, v.y) <= 0) std::swap(u, v); if (cmp(p.y, u.y) > 0 || cmp(p.y, v.y) <= 0) continue; ret ^= crossOp(p, u, v) > 0; } return ret * 2; } // 凸包 std::vector<P> convexHull(std::vector<P> ps) { int n = ps.size(); if (n <= 1) return ps; std::sort(ps.begin(), ps.end()); std::vector<P> qs(n * 2); int k = 0; for (int i = 0; i < n; qs[k++] = ps[i++]) while (k > 1 && crossOp(qs[k - 2], qs[k - 1], ps[i]) <= 0) --k; for (int i = n - 2, t = k; i >= 0; qs[k++] = ps[i--]) while (k > t && crossOp(qs[k - 2], qs[k - 1], ps[i]) <= 0) --k; qs.resize(k - 1); return qs; } std::vector<P> convexHullNonStrict(std::vector<P> ps) { int n = ps.size(); if (n <= 1) return ps; std::sort(ps.begin(), ps.end()); std::vector<P> qs(n * 2); int k = 0; for (int i = 0; i < n; qs[k++] = ps[i++]) while (k > 1 && crossOp(qs[k - 2], qs[k - 1], ps[i]) < 0) --k; for (int i = n - 2, t = k; i >= 0; qs[k++] = ps[i--]) while (k > t && crossOp(qs[k - 2], qs[k - 1], ps[i]) < 0) --k; qs.resize(k - 1); return qs; } // 点集直径 db convexDiameter(const std::vector<P> &ps) { int n = ps.size(); if (n <= 1) return 0; int is = 0, js = 0; for (int k = 1; k < n; k++) is = ps[k] < ps[is] ? k : is, js = ps[js] < ps[k] ? k : js; int i = is, j = js; db ret = ps[i].distTo(ps[j]); do { if ((ps[(i + 1) % n] - ps[i]).det(ps[(j + 1) % n] - ps[j]) >= 0) (++j) %= n; else (++i) %= n; ret = std::max(ret, ps[i].distTo(ps[j])); } while (i != is || j != js); return ret; } // convecCut std::vector<P> convexCut(const std::vector<P> &ps, const P &q1, const P &q2) { std::vector<P> qs; int n = ps.size(); for (int i = 0; i < n; i++) { P p1 = ps[i], p2 = ps[(i + 1) % n]; int d1 = crossOp(q1, q2, p1), d2 = crossOp(q1, q2, p2); if (d1 >= 0) qs.push_back(p1); if (d1 * d2 < 0) qs.push_back(isLL(p1, p2, q1, q2)); } return qs; } // min_dis db min_dis(const std::vector<P> &ps, int l, int r) { if (r - l <= 5) { db ret = 1e100; for (int i = l; i < r; i++) for (int j = l; j < i; j++) ret = std::min(ret, ps[i].distTo(ps[j])); return ret; } int mid = l + r >> 1; db ret = std::min(min_dis(ps, l, mid), min_dis(ps, mid, r)); std::vector<P> qs; for (int i = l; i < r; i++) if (cmp(fabs(ps[i].x - ps[mid].x), ret) <= 0) qs.push_back(ps[i]); std::sort(qs.begin(), qs.end(), [](const P & a, const P & b) -> bool { return cmp(a.y, b.y) < 0; }); for (int i = 1; i < qs.size(); i++) for (int j = i - 1; j >= 0 && cmp(qs[j].y, qs[i].y - ret) >= 0; j--) ret = std::min(ret, qs[j].distTo(qs[i])); return ret; } // 圆的关系 int type(const P &o1, db r1, const P &o2, db r2) { db d = o1.distTo(o2); if (cmp(d, r1 + r2) == 1) return 4; // 相离 if (cmp(d, r1 + r2) == 0) return 3; // 外切 if (cmp(d, fabs(r1 - r2)) == 1) return 2; // 相交 if (cmp(d, fabs(r1 - r2)) == 0) return 1; // 内切 return 0; } bool parallel(L l0, L l1) { return sign(l0.dir().det(l1.dir())) == 0; } bool sameDir(L l0, L l1) { return parallel(l0, l1) && sign(l0.dir().dot(l1.dir())) == 1; } bool cmp(P a, P b) { if (a.quad() != b.quad()) { return a.quad() < b.quad(); } else { return sign(a.det(b)) > 0; } } bool operator < (L l0, L l1) { if (sameDir(l0, l1)) { return l1.include(l0[0]); } else { return cmp(l0.dir(), l1.dir()); } } bool check(L u, L v, L w) { return w.include(isLL(u, v)); } const int N = 1e3 + 7; L que[N]; std::vector<L> halfPlaneIS(std::vector<L> &l) { std::sort(l.begin(), l.end()); int head = 0, tail = 0; for (int i = 0; i < l.size(); i++) { if (i && sameDir(l[i], l[i - 1])) continue; while (tail - head > 1 && !check(que[tail - 2], que[tail - 1], l[i])) tail--; while (tail - head > 1 && !check(que[head + 1], que[head], l[i])) head++; que[tail++] = l[i]; } while (tail - head > 2 && !check(que[tail - 2], que[tail - 1], que[0])) tail--; while (tail - head > 2 && !check(que[1], que[0], que[tail - 1])) head++; std::vector<L> ans; for (int i = head; i < tail; i++) ans.push_back(que[i]); return ans; } db gety(P p, std::vector<P> point, std::vector<L> line) { int n = point.size(); if (sign(p.x - point[0].x) <= 0) return isLL(line[0], L(p, P(p.x, p.y + 10))).y; if (sign(p.x - point[n - 1].x) >= 0) return isLL(line[n], L(p, P(p.x, p.y + 10))).y; for (int i = 0; i < n - 1; i++) { if (isMiddle(point[i].x, p.x, point[i + 1].x)) return isLL(line[i + 1], L(p, P(p.x, p.y + 10))).y; } return 1e11; } db getyy(P p, std::vector<P> point) { for (int i = 0, sz = point.size(); i < sz - 1; i++) { if (isMiddle(point[i].x, p.x, point[i + 1].x)) return isLL(point[i], point[i + 1], p, P(p.x, p.y + 10)).y; } return 1e11; } int main() { int n; scanf("%d", &n); if (n <= 2) { puts("0"); return 0; } std::vector<P> p(n); for (int i = 0; i < n; i++) scanf("%lf", &p[i].x); for (int i = 0; i < n; i++) scanf("%lf", &p[i].y); std::vector<L> l; for (int i = 0; i < n - 1; i++) l.push_back(L(p[i], p[i + 1])); std::vector<L> half = halfPlaneIS(l); db ans = 1e10; std::vector<P> ss; for (int i = 0, sz = half.size(); i < sz - 1; i++) ss.push_back(isLL(half[i], half[i + 1])); for (int i = 0; i < n; i++) { ans = std::min(ans, std::fabs(gety(p[i], ss, half) - p[i].y)); } for (int i = 0, sz = half.size(); i < sz - 1; i++) { P pp = ss[i]; ans = std::min(ans, std::fabs(getyy(pp, p) - pp.y)); } printf("%.3f\n", ans); return 0; }
1039. [ZJOI2008]无序运动Movement
平移、旋转、放缩对两个相似三角形没有影响,那么一个长度为 $n$ 的轨迹就可以描述为 $n-2$ 个三角形,每个三角形就用相邻两边长来描述,还得加上第二条线段在第一条线段的逆时针还是顺时针方向,因为如果不加这个就会出现翻不翻转带来的影响,然后就变成了字符串匹配了。不过由于字符集很大,得用 map 来存边。然后翻转一下再做一遍。
如果一个轨迹共线的话,翻转后他会被重新算一遍,所以要除以 $2$。
如果一个轨迹长度小于 $3$ 的话, 他就能匹配上所有长度相等的子串。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } struct P { int x, y; void read() { IO::read(x, y); } void print() { printf("%d %d\n", x, y); } P() {} P(int x, int y): x(x), y(y) {} P operator + (const P &p) const { return P(x + p.x, y + p.y); } P operator - (const P &p) const { return P(x - p.x, y - p.y); } int det(const P &p) const { return x * p.y - y * p.x; } int abs2() { return x * x + y * y; } }; struct Node { int a, b, c, dir; bool operator < (const Node &p) const { if (a != p.a) return a < p.a; if (b != p.b) return b < p.b; if (c != p.c) return c < p.c; return dir < p.dir; } bool operator == (const Node &p) const { return !(*this < p) && !(p < *this); } }; const int N = 2e5 + 7; int n, m, par[N], tol, ans[N]; std::vector<P> point[N], all; std::vector<int> flag[N]; std::map<Node, int> mp[N]; int gcd(int a, int b) { while (b) { a %= b; std::swap(a, b); } return a; } Node getnode(const P &A, const P &B, const P &C) { int lena = (B - A).abs2(), lenb = (B - C).abs2(), lenc = (A - C).abs2(); int g = gcd(lena, gcd(lenb, lenc)); lena /= g, lenb /= g, lenc /= g; int crs = 0; if ((B - A).det(C - B) > 0) crs = 1; else if ((B - A).det(C - B) < 0) crs = -1; return {lena, lenb, lenc, crs}; } void done(std::vector<P> vec, int id) { if (vec.size() < 3) { ans[id] = n - vec.size() + 1; return; } par[id] = 1; int rt = 0; for (int i = 0; i < vec.size() - 2; i++) { Node o = getnode(vec[i], vec[i + 1], vec[i + 2]); if (o.dir) par[id] = 0; auto it = mp[rt].find(o); if (it == mp[rt].end()) { mp[rt][o] = ++tol; rt = tol; } else { rt = it->second; } } flag[rt].push_back(id); } int fail[N], last[N], cnt[N]; void build() { std::queue<int> que; for (auto it: mp[0]) que.push(it.second); while (!que.empty()) { int u = que.front(); que.pop(); for (auto it: mp[u]) { Node cur_node = it.first; int f = fail[u], v = it.second; for (; f && mp[f].find(cur_node) == mp[f].end(); f = fail[f]); if (mp[f].find(cur_node) != mp[f].end()) f = mp[f][cur_node]; fail[v] = f; last[v] = flag[fail[v]].empty() ? last[fail[v]] : fail[v]; que.push(v); } } } void solve(const std::vector<P> &vec) { int rt = 0; for (int i = 0; i < n - 2; i++) { Node node = getnode(vec[i], vec[i + 1], vec[i + 2]); for (; rt && mp[rt].find(node) == mp[rt].end(); rt = fail[rt]); if (mp[rt].find(node) != mp[rt].end()) rt = mp[rt][node]; for (int j = rt; j; j = last[j]) ++cnt[j]; } } int main() { IO::read(n, m); for (int i = 1; i <= m; i++) { int k; IO::read(k); point[i].resize(k); for (int j = 0; j < k; j++) point[i][j].read(); done(point[i], i); } build(); all.resize(n); for (int i = 0; i < n; i++) all[i].read(); solve(all); for (int i = 0; i < n; i++) all[i].y *= -1; solve(all); for (int i = 1; i <= tol; i++) for (int u: flag[i]) ans[u] += cnt[i] / (par[u] + 1); for (int i = 1; i <= m; i++) IO::print(ans[i]); IO::flush(); return 0; }
1040. [ZJOI2008]骑士
基环森林上DP。
我刚开始想的就是找到环,然后把环上每个点及它的子树缩成一个点,就变成一个环上的DP了。然后就是强制第一个不取和强制最后一个不取。
看了别人的题解发现可以不用那么麻烦,只要找到环上任意相邻的两点,强制把这条边断开,然后还是DP两次就行了。
DP方程就比较naive了。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } #define ll long long const int N = 1e6 + 7; int n, fa[N], root, _root; ll dp[N][2], val[N]; bool vis[N]; int head[N]; struct E { int v, ne; } e[N << 1]; int cnt = 1, ban; void add(int u, int v) { e[++cnt].v = v; e[cnt].ne = head[u]; head[u] = cnt; } void dfs(int u, int f) { vis[u] = 1; for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (v == f) continue; if (vis[v]) { root = u, _root = v; ban = i; } else { dfs(v, u); } } } void DP(int u, int f) { dp[u][0] = 0; dp[u][1] = val[u]; for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (v == f || i == ban || i == (ban ^ 1)) continue; DP(v, u); dp[u][0] += std::max(dp[v][0], dp[v][1]); dp[u][1] += dp[v][0]; } } int main() { IO::read(n); for (int i = 1, u; i <= n; i++) IO::read(val[i], u), add(u, i), add(i, u); ll ans = 0; for (int i = 1; i <= n; i++) if (!vis[i]) { dfs(i, -1); DP(root, -1); ll temp = dp[root][0]; DP(_root, -1); temp = std::max(temp, dp[_root][0]); ans += temp; } printf("%lld\n", ans); return 0; }
1041. [HAOI2008]圆上的整点
$x^2+y^2=r^2$
$x^2=(r-y)(r+y)$
$(\frac{x}{g})^2=\frac{r-y}{g}\frac{r+y}{g}$
其中 $g = \gcd(r-y,r+y)$
那么 $\frac{r-y}{g}$ 和 $\frac{r+y}{g}$ 互质
并且左边 $(\frac{x}{g})^2$ 是完全平方数,那么右边 $\frac{r-y}{g}$ 和 $\frac{r+y}{g}$ 也必须都是完全平方数
设 $a^2=\frac{r-y}{g}$,$b^2=\frac{r+y}{g}$
$a^2+b^2=\frac{2r}{g}$
枚举 $g$ 之后再枚举 $a$,可以做到复杂度 $O(n^{\frac{3}{4}})$
#include <bits/stdc++.h> #define ll long long template<class T> T gcd(T a, T b) { while (b) { a %= b; std::swap(a, b); } return a; } int main() { ll r; scanf("%lld", &r); int ans = 0; for (ll g = 1; g * g <= 2 * r; g++) { if ((2 * r) % g) continue; for (ll a = 1; a * a < g / 2; a++) { ll b = sqrt(g - a * a + 0.5); if (a * a + b * b == g && gcd(a, b) == 1) ans++; } for (ll a = 1; a * a < r / g; a++) { ll b = sqrt(2 * r / g - a * a + 0.5); if (a * a + b * b == 2 * r / g && gcd(a, b) == 1) ans++; } } printf("%d\n", ans * 4 + 4); return 0; }
1042. [HAOI2008]硬币购物
涨芝士了!
可以先做一次完全背包,然后对每次询问容斥得到答案。
限制用 $d_i$ 个 $c_i$ 硬币,就相当于减去至少用了 $d_i+1$ 个 $c_i$ 硬币,那么就是假设我强制花了 $(d_i+1)*c_i$ 块钱,然后方案就是 $f[s-(d_i+1)*c_i]$。容斥就暴力枚举 $2^4$ 钟情况即可。
#include <bits/stdc++.h> #define ll long long const int N = 1e5 + 7; ll dp[N]; int w[10], T, d[10]; int get(int x) { int cnt = 0; while (x) { cnt++; x &= (x - 1); } return cnt; } int main() { for (int i = 0; i < 4; i++) scanf("%d", w + i); scanf("%d", &T); dp[0] = 1; for (int i = 0; i < 4; i++) for (int j = w[i]; j <= N - 7; j++) dp[j] += dp[j - w[i]]; while (T--) { for (int i = 0; i < 4; i++) scanf("%d", d + i); int s; scanf("%d", &s); ll ans = 0; for (int i = 0; i < 16; i++) { int sz = get(i), sum = s; int c = sz & 1 ? -1 : 1; for (int j = 0; j < 4; j++) if (i >> j & 1) sum -= w[j] * (d[j] + 1); if (sum >= 0) ans += c * dp[sum]; } printf("%lld\n", ans); } return 0; }
1044. [HAOI2008]木棍分割
第一问二分傻逼题。
第二问前缀和优化一下DP
#include <bits/stdc++.h> const int N = 5e4 + 7; const int MOD = 10007; int n, m, a[N], dp[N][2], sum[N]; int dp_sum[N][2]; void M(int &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } inline bool chkmax(int &a, int b) { return a < b ? a = b, 1 : 0; } inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; } bool check(int mid) { int cnt = 1, sum = 0; for (int i = 1; i <= n; i++) { if (sum + a[i] > mid) { cnt++; sum = a[i]; } else { sum += a[i]; } } return cnt <= m; } int que[N]; int main() { scanf("%d%d", &n, &m); m++; int l = 0, r = 0; for (int i = 1; i <= n; i++) scanf("%d", a + i), chkmax(l, a[i]), r += a[i], sum[i] = sum[i - 1] + a[i]; int ans = 0; while (l <= r) { int mid = l + r >> 1; if (check(mid)) ans = mid, r = mid - 1; else l = mid + 1; } for (int i = 1; i <= n; i++) dp[i][1] = (sum[i] <= ans ? 1 : 0), M(dp_sum[i][1] = dp_sum[i - 1][1] + dp[i][1]); int ans2 = dp[n][1]; for (int j = 2; j <= m; j++) { int cur = j & 1; int last = 0; for (int i = 1; i <= n; i++) { dp[i][cur] = 0; while (i > last && sum[i] - sum[last] > ans) last++; M(dp[i][cur] += dp_sum[i - 1][cur ^ 1] - dp_sum[std::max(last - 1, 0)][cur ^ 1]); M(dp_sum[i][cur] = dp_sum[i - 1][cur] + dp[i][cur]); } M(ans2 += dp[n][cur]); } printf("%d %d\n", ans, ans2); return 0; }
1045. [HAOI2008] 糖果传递
设 $x_i$ 表示第 $i$ 个人向第 $i+1$ 个人传的糖果数。
那么
$$\begin{cases}
x_n - x_1 = \overline{a}-a_1\\
x_1 - x_2 = \overline{a}-a_2\\
x_2 - x_3 = \overline{a}-a_3\\
\dots
\end{cases}
$$
再做一下前缀和,能得到
$x_n-x_i=\sum_{j=1}^i(\overline{a}-a_i)$
即 $x_i = x_n - \sum_{j=1}^i(\overline{a}-a_i)$
答案就是 $\sum_{i=1}^n |x_i|$
就是数轴上有 $n$ 个点,要找一个点到他们的距离之和最小,大概就是中位数吧,因为中位数往左往右移增加的距离会比少的距离多一部分,举个例子就知道了。
#include <bits/stdc++.h> typedef long long ll; const int N = 1e6 + 7; ll a[N]; int main() { int n; scanf("%d", &n); ll sum = 0; for (int i = 1; i <= n; i++) scanf("%lld", a + i), sum += a[i]; sum /= n; for (int i = 1; i <= n; i++) a[i] = a[i - 1] + sum - a[i]; std::sort(a + 1, a + 1 + n); sum = a[(n + 1) / 2]; ll ans = 0; for (int i = 1; i <= n; i++) ans += std::abs(a[i] - sum); printf("%lld\n", ans); return 0; }
1046. [HAOI2007]上升序列
先求出 $dp[i]$ 表示以 $i$ 开头的最长下降子序列。
然后因为是 $x$ 序列字典序最小,那就从左往右填就行了。
#include <bits/stdc++.h> const int N = 1e4 + 7; int cnt, a[N], v[N], n; int dp[N]; struct Bit { int tree[N]; inline int lowbit(int x) { return x & -x; } void add(int x, int v) { for (int i = x; i <= n; i += lowbit(i)) tree[i] = std::max(tree[i], v); } int query(int x) { int ans = 0; for (int i = x; i; i -= lowbit(i)) ans = std::max(ans, tree[i]); return ans; } } bit; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), v[i] = a[i]; std::sort(v + 1, v + 1 + n); cnt = std::unique(v + 1, v + 1 + n) - v - 1; int ans = 0; for (int i = n; i >= 1; i--) { a[i] = std::lower_bound(v + 1, v + 1 + cnt, a[i]) - v; a[i] = cnt - a[i] + 1; dp[i] = bit.query(a[i] - 1) + 1; bit.add(a[i], dp[i]); ans = std::max(ans, dp[i]); a[i] = cnt - a[i] + 1; } int m; scanf("%d", &m); for (int i = 1; i <= m; i++) { int len; scanf("%d", &len); if (len > ans) { puts("Impossible"); continue; } int last = 0; for (int i = 1; i <= n; i++) { if (!len) break; if (dp[i] >= len && a[i] > last) { printf("%d%c", v[a[i]], " \n"[len == 1]); len--; last = a[i]; } } } return 0; }
1047. [HAOI2007]理想的正方形
先对每一行做一遍单调队列,然后再对每一列做一遍单调队列即可。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } const int N = 1007; int A[N][N], a, b, n, mx[N][N], mn[N][N]; int main() { IO::read(a, b, n); for (int i = 1; i <= a; i++) for (int j = 1; j <= b; j++) IO::read(A[i][j]); static int que1[N], que2[N]; for (int i = 1; i <= a; i++) { int l1 = 1, r1 = 1, l2 = 1, r2 = 1; for (int j = 1; j <= b; j++) { { while (l1 < r1 && que1[l1] <= j - n) l1++; while (l1 < r1 && A[i][que1[r1 - 1]] >= A[i][j]) r1--; que1[r1++] = j; if (j >= n) mn[i][j] = A[i][que1[l1]]; } { while (l2 < r2 && que2[l2] <= j - n) l2++; while (l2 < r2 && A[i][que2[r2 - 1]] <= A[i][j]) r2--; que2[r2++] = j; if (j >= n) mx[i][j] = A[i][que2[l2]]; } } } int ans = 2e9; for (int j = n; j <= b; j++) { int l1 = 1, r1 = 1, l2 = 1, r2 = 1; for (int i = 1; i <= a; i++) { { while (l1 < r1 && que1[l1] <= i - n) l1++; while (l1 < r1 && mn[que1[r1 - 1]][j] >= mn[i][j]) r1--; que1[r1++] = i; } { while (l2 < r2 && que2[l2] <= i - n) l2++; while (l2 < r2 && mx[que2[r2 - 1]][j] <= mx[i][j]) r2--; que2[r2++] = i; } if (i >= n) ans = std::min(ans, mx[que2[l2]][j] - mn[que1[l1]][j]); } } printf("%d\n", ans); return 0; }
1048. [HAOI2007]分割矩阵
记忆化着搜就行了,$f[x_1][y_1][x_2][y_2][c]$ 表示矩阵 $(x_1,y_1)$,$(x_2,y_2)$ 分成 $c$ 份的最小方差和。
#include <bits/stdc++.h> typedef double db; const db eps = 1e-7; const int N = 12; db f[N][N][N][N][N]; int sum[N][N]; db ave; int A[N][N]; int sign(db x) { if (fabs(x) < eps) return 0; return x > 0 ? 1 : -1; } db sqr(db x) { return x * x; } db Sum(int x1, int x2, int y1, int y2) { return sum[x2][y2] + sum[x1 - 1][y1 - 1] - sum[x1 - 1][y2] - sum[x2][y1 - 1]; } db solve(int x1, int x2, int y1, int y2, int c) { db &ans = f[x1][x2][y1][y2][c]; if (ans != -1) return ans; if (c == 1) return ans = sqr(Sum(x1, x2, y1, y2) - ave); ans = 1e9; for (int i = x1; i < x2; i++) for (int cc = 1; cc < c; cc++) ans = std::min(ans, solve(x1, i, y1, y2, cc) + solve(i + 1, x2, y1, y2, c - cc)); for (int i = y1; i < y2; i++) for (int cc = 1; cc < c; cc++) ans = std::min(ans, solve(x1, x2, y1, i, cc) + solve(x1, x2, i + 1, y2, c - cc)); return ans; } int n, m, c; int main() { scanf("%d%d%d", &n, &m, &c); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= m; k++) for (int l = 1; l <= m; l++) for (int o = 1; o <= c; o++) f[i][j][k][l][o] = -1; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &A[i][j]), sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + A[i][j]; ave = 1.0 * sum[n][m] / c; printf("%.2f\n", sqrt(solve(1, n, 1, m, c) / c)); return 0; }
1049. [HAOI2006]数字序列
第一问套路,每一个数字减去它的下标就是求最长不下降子序列了。
第二问,$g[i]$ 表示 $1$ 到 $i$ 都已经符合条件的最小花费
那么可以列出方程 $g[i]=\min \{g[j]+cost(j+1,i)\},dp[j]+1=dp[i]$
cost 有个结论就是存在一个分割点 $k$,左边的值都变成 $b[j]$,右边的值都变成 $b[i]$ 最优,然后暴力求就行了,因为数据随机
#include <bits/stdc++.h> typedef long long ll; const int N = 4e4 + 7; const ll INF = 0x3f3f3f3f3f3f3f3f; const int inf = 0x3f3f3f3f; template<class T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; } template<class T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; } int n, a[N], dp[N], v[N], cnt, b[N]; std::vector<int> vec[N]; ll g[N], s1[N], s2[N]; struct BIT { int tree[N]; inline int lowbit(int x) { return x & -x; } void add(int x, int v) { for (int i = x; i <= cnt; i += lowbit(i)) chkmax(tree[i], v); } int query(int x) { int ans = 0; for (int i = x; i; i -= lowbit(i)) chkmax(ans, tree[i]); return ans; } } bit; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), b[i] = a[i] - i, v[i] = b[i]; a[++n] = inf, b[n] = a[n] - n, v[n] = b[n]; std::sort(v + 1, v + 1 + n); cnt = std::unique(v + 1, v + 1 + n) - v - 1; int ans = 0; for (int i = 1; i <= n; i++) { b[i] = std::lower_bound(v + 1, v + 1 + cnt, b[i]) - v; dp[i] = bit.query(b[i]) + 1; bit.add(b[i], dp[i]); chkmax(ans, dp[i]); } ans = n - ans; printf("%d\n", ans); for (int i = 0; i <= n; i++) { vec[dp[i]].push_back(i); b[i] = v[b[i]]; g[i] = INF; } g[0] = 0; b[0] = -inf; for (int i = 1; i <= n; i++) for (int j = 0, sz = vec[dp[i] - 1].size(); j < sz; j++) { int pos = vec[dp[i] - 1][j]; if (pos > i) break; if (b[pos] > b[i]) continue; for (int k = pos; k <= i; k++) s1[k] = std::abs(b[k] - b[pos]), s2[k] = std::abs(b[i] - b[k]); for (int k = pos + 1; k <= i; k++) s1[k] += s1[k - 1], s2[k] += s2[k - 1]; for (int k = pos; k <= i; k++) chkmin(g[i], g[pos] + s1[k] - s1[pos] + s2[i] - s2[k]); } printf("%lld\n", g[n]); return 0; }
1188. [HNOI2007]分裂游戏
每次操作相当于在一个位置取一个豆子,放到后面的两个位置上,即 $i$ -> $(j, k)$。那么每个豆子只跟位置有关,而与它所在的那一堆有多少豆子无关。游戏的和就是所有豆子的 SG 函数的异或和。
那么一个位置有奇数个豆子时对游戏的和有贡献,理解起来就是如果一个位置有两个豆子的话,第二个人可以模仿第一个人的动作。
SG 函数可以倒着求出来,然后枚举第一步即可。
这里不是一堆石子是单个游戏,而是每个石子都是单个游戏。
#include <bits/stdc++.h> const int N = 64; int a[N], SG[N]; bool vis[N]; int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), SG[i] = 0; for (int i = n - 1; i; i--) { memset(vis, 0, sizeof(vis)); for (int j = i + 1; j <= n; j++) for (int k = j; k <= n; k++) vis[SG[j] ^ SG[k]] = 1; for (int j = 0; ; j++) if (!vis[j]) { SG[i] = j; break; } } int A = 0, B = 0, C = 0, cnt = 0; int ans = 0; for (int i = 1; i <= n; i++) if (a[i] & 1) ans ^= SG[i]; for (int i = 1; i <= n; i++) if (a[i]) for (int j = i + 1; j <= n; j++) for (int k = j; k <= n; k++) if (!(ans ^ SG[i] ^ SG[j] ^ SG[k])) { if (!cnt) A = i, B = j, C = k; cnt++; } printf("%d %d %d\n%d\n", A - 1, B - 1, C - 1, cnt); } return 0; }
1791. [Ioi2008]Island 岛屿
求基环森林的直径之和。
这个刚好是个基环内向树,那么可以拓扑排序做,之后再拆环,单调队列优化一下DP。
#include <bits/stdc++.h> #define ll long long namespace IO { void read() {} template<class T, class ... T2> inline void read(T &x, T2 &... oth) { x = 0; T f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); x *= f; read(oth...); } } const int N = 1e6 + 7; int to[N], w[N], n, in[N]; ll f[N], dp[N], d[N]; bool vis[N]; struct Deque { static const int LEN = 1e6; int l, r, que[LEN]; Deque() { l = r = 0; } void clear() { l = r = 0; } bool empty() { return !(l ^ r); } void push_back(int v) { que[r++] = v; r %= LEN; } void push_front(int v) { que[l = (l - 1 + LEN) % LEN] = v; } void pop_front() { ++l; l %= LEN; } void pop_back() { r = (r - 1 + LEN) % LEN; } int front() { return que[l]; } int back() { return que[r - 1]; } } que; template<class T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; } int cur[N * 2]; ll s[N * 2]; int main() { IO::read(n); for (int i = 1; i <= n; i++) { IO::read(to[i], w[i]); in[to[i]]++; } for (int i = 1; i <= n; i++) if (!in[i]) que.push_back(i); while (!que.empty()) { int u = que.front(); que.pop_front(); int v = to[u]; if (!--in[v]) que.push_back(v); // 经过 v 的最长路径 chkmax(f[v], d[u] + d[v] + w[u]); // 从 v 出发的最长路径 chkmax(d[v], d[u] + w[u]); // v 的子树中最长路径 chkmax(dp[v], std::max(dp[u], f[v])); } ll ans = 0; for (int i = 1; i <= n; i++) if (in[i] && !vis[i]) { int cnt = 0; ll temp = 0; for (int j = i; !vis[j]; j = to[j]) { cur[++cnt] = j; vis[j] = 1; chkmax(temp, dp[j]); } int dcnt = cnt << 1; for (int i = cnt + 1; i <= dcnt; i++) cur[i] = cur[i - cnt]; que.clear(); for (int j = 1; j <= dcnt; j++) { s[j] = s[j - 1] + w[cur[j - 1]]; while (!que.empty() && j - que.front() >= cnt) que.pop_front(); if (!que.empty()) chkmax(temp, d[cur[j]] + d[cur[que.front()]] + s[j] - s[que.front()]); while (!que.empty() && d[cur[j]] - s[j] > d[cur[que.back()]] - s[que.back()]) que.pop_back(); que.push_back(j); } ans += temp; } printf("%lld\n", ans); return 0; }
2038. [2009国家集训队]小Z的袜子(hose)
莫队板子题。
#include <bits/stdc++.h> const int N = 5e4 + 7; int n, m, B, ans[N][2], fz, fm; int cnt[N], len, col[N]; struct Q { int l, r, id; bool operator < (const Q &p) const { return l / B == p.l / B ? r < p.r : l < p.l; } } q[N]; void add(int x) { fz += cnt[x]; cnt[x]++; fm += len; len++; } void del(int x) { cnt[x]--; fz -= cnt[x]; len--; fm -= len; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", col + i); for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i; B = sqrt(n); std::sort(q + 1, q + 1 + m); int l = 1, r = 0; for (int i = 1; i <= m; i++) { while (l > q[i].l) add(col[--l]); while (l < q[i].l) del(col[l++]); while (r > q[i].r) del(col[r--]); while (r < q[i].r) add(col[++r]); if (fm == 0) { ans[q[i].id][0] = ans[q[i].id][1] = 0; continue; } int g = std::__gcd(fz, fm); ans[q[i].id][0] = fz / g; ans[q[i].id][1] = fm / g; } for (int i = 1; i <= m; i++) printf("%d/%d\n", ans[i][0], ans[i][1]); return 0; }
2120. 数颜色
带修莫队板子题。
#include <bits/stdc++.h> const int N = 1e4 + 7; int cnt[N * 100], ans, B, a[N]; int n, m; struct Q { int l, r, t, id; bool operator < (const Q &p) const { if (l / B != p.l / B) return l < p.l; if (r / B != p.r / B) return r < p.r; return t < p.t; } } q[N]; struct C { int p, col; } change[N]; void add(int x) { ++cnt[x]; if (cnt[x] == 1) ans++; } void del(int x) { --cnt[x]; if (cnt[x] == 0) ans--; } void update(int cur_q, int cur_c) { if (change[cur_c].p >= q[cur_q].l && change[cur_c].p <= q[cur_q].r) del(a[change[cur_c].p]), add(change[cur_c].col); std::swap(a[change[cur_c].p], change[cur_c].col); } int res[N]; int main() { char s[10]; scanf("%d%d", &n, &m); B = n / sqrt(m * 2 / 3); for (int i = 1; i <= n; i++) scanf("%d", a + i); int qcnt = 0, ccnt = 0; for (int i = 1; i <= m; i++) { scanf("%s", s); if (s[0] == 'Q') { ++qcnt; scanf("%d%d", &q[qcnt].l, &q[qcnt].r); q[qcnt].t = ccnt; q[qcnt].id = qcnt; } else { ++ccnt; scanf("%d%d", &change[ccnt].p, &change[ccnt].col); } } int l = 1, r = 0, cur = 0; std::sort(q + 1, q + 1 + qcnt); for (int i = 1; i <= qcnt; i++) { while (l < q[i].l) del(a[l++]); while (l > q[i].l) add(a[--l]); while (r < q[i].r) add(a[++r]); while (r > q[i].r) del(a[r--]); while (cur < q[i].t) update(i, ++cur); while (cur > q[i].t) update(i, cur--); res[q[i].id] = ans; } for (int i = 1; i <= qcnt; i++) printf("%d\n", res[i]); }
2154. Crash的数字表格
$$\sum_{i = 1}^n \sum_{j= 1}^m lcm(i, j)=\sum_{i=1}^n\sum_{j=1}^m \dfrac{i*j}{(i,j)}$$
$$=\sum_{i=1}^n\sum_{j=1}^m \sum_d \dfrac{ij}{d}[(\frac{i}{d}, \frac{j}{d})==1]=\sum_{d}d\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor \frac{m}{d}\rfloor}ij[(i,j)==1]$$
$$=\sum_d d\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor \frac{m}{d}\rfloor}ij \sum_{p|(i,j)}\mu(p)=\sum_d d\sum_p p^2\mu(p)\sum_{i=1}^{\lfloor \frac{n}{dp}\rfloor}i\sum_{j=1}^{\lfloor \frac{m}{dp}\rfloor}j$$
$$=\sum_dd\sum_pp^2\mu(p)(\dfrac{(\lfloor \frac{n}{dp} \rfloor + 1)\lfloor \frac{n}{dp} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{dp} \rfloor + 1)\lfloor \frac{m}{dp} \rfloor}{2})$$
$$=\sum_T(\dfrac{(\lfloor \frac{n}{T} \rfloor + 1)\lfloor \frac{n}{T} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{T} \rfloor + 1)\lfloor \frac{m}{T} \rfloor}{2})\sum_{p|T}\dfrac{T}{p}p^2\mu(p)$$
$$=\sum_T(\dfrac{(\lfloor \frac{n}{T} \rfloor + 1)\lfloor \frac{n}{T} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{T} \rfloor + 1)\lfloor \frac{m}{T} \rfloor}{2})\sum_{p|T}Tp\mu(p)$$
需要预处理 $\sum_{d|n}nd\mu(d)$,只需要预处理 $f(n)=\sum_{d|n}d\mu(d)$,$f(n)$ 为积性函数,且 $f(p^t) = 1 - p$。
所以这道题可以搞成多组询问的。
复杂度为 $O(n+q\sqrt n)$
#include <bits/stdc++.h> const int MOD = 20101009; const int N = 1e7 + 7; int prime[N], prin; int f[N]; bool vis[N]; void M(int &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } void init(int N) { f[1] = 1; for (int i = 2; i < N; i++) { if (!vis[i]) { prime[++prin] = i; M(f[i] = 1 - i); } for (int j = 1; j <= prin && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) { int temp = i; while (temp % prime[j] == 0) temp /= prime[j]; f[i * prime[j]] = 1LL * (1 - prime[j] + MOD) * f[temp] % MOD; break; } f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD; } } for (int i = 1; i < N; i++) M(f[i] = (f[i - 1] + 1LL * f[i] * i % MOD)); } int cal(int n, int m) { int x = 1LL * n * (n + 1) / 2 % MOD; int y = 1LL * m * (m + 1) / 2 % MOD; return 1LL * x * y % MOD; } int solve(int n, int m) { if (n > m) std::swap(n, m); int ans = 0; for (int i = 1, j; i <= n; i = j + 1) { j = std::min(n / (n / i), m / (m / i)); M(ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - 1] + MOD) % MOD); } return ans; } signed main() { int n, m; scanf("%d%d", &n, &m); init(std::min(n, m) + 1); printf("%d\n", solve(n, m)); return 0; }
2179. FFT快速傅立叶
FFT板子
#include <bits/stdc++.h> struct Complex { double r, i; Complex(double r = 0.0, double i = 0.0): r(r), i(i) {} Complex operator + (const Complex &p) const { return Complex(r + p.r, i + p.i); } Complex operator - (const Complex &p) const { return Complex(r - p.r, i - p.i); } Complex operator * (const Complex &p) const { return Complex(r * p.r - i * p.i, r * p.i + i * p.r); } }; const double pi = acos(-1.0); const int N = 2e5 + 7; int n, limit, r[N], l; int v[N], A[N], B[N], C[N]; Complex a[N], b[N], c[N]; void FFT(Complex *a, int pd) { for (int i = 0; i < limit; i++) if (i < r[i]) std::swap(a[i], a[r[i]]); for (int mid = 1; mid < limit; mid <<= 1) { Complex wn = Complex(cos(pi / mid), pd * sin(pi / mid)); for (int l = mid << 1, j = 0; j < limit; j += l) { Complex w = Complex(1.0, 0.0); for (int k = 0; k < mid; k++, w = w * wn) { Complex u = a[k + j], v = w * a[k + j + mid]; a[k + j] = u + v; a[k + j + mid] = u - v; } } } if (pd == -1) for (int i = 0; i < limit; i++) a[i] = Complex(a[i].r / limit, a[i].i / limit); } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) { int x; scanf("%d", &x); x += 20000; A[x]++; B[x * 2]++; C[x * 3]++; } for (int i = 0; i <= 40000; i++) a[i] = Complex((double)A[i], 0.0); for (int i = 0; i <= 80000; i++) b[i] = Complex((double)B[i], 0.0); limit = 1; while (limit <= 40000 + 80000) limit <<= 1, l++; for (int i = 0; i < limit; i++) r[i] = r[i >> 1] >> 1 | ((i & 1) << (l - 1)); FFT(a, 1); FFT(b, 1); for (int i = 0; i < limit; i++) b[i] = b[i] * a[i]; for (int i = 0; i < limit; i++) a[i] = a[i] * a[i] * a[i]; FFT(a, -1); FFT(b, -1); for (int i = 0; i <= 120000; i++) { long long ans = (long long)((a[i].r - 3.0 * b[i].r + 2.0 * C[i]) / 6.0 + 0.5); if (ans > 0) printf("%d : %lld\n", i - 60000, ans); } return 0; }
2238. Mst
先求出 Mst,如果要删的边不在 Mst 上,那么对答案不影响。
如果删的边 $(u, v)$ 在 Mst 上,相当于把树分成以 $u$ 为根和以 $v$ 为根两棵树,现在就要在两棵树里挑出一条权值最小的非树边。
预处理每一条非树边,它们之间的路径的边的值取个min,那么就树剖一下,建线段树,区间里面比这个大的数都变成这个数。
我以为要吉司机线段树,但是查询是单点查询,标记永久化即可。
#include <bits/stdc++.h> const int N = 1e5 + 7; inline bool chkmax(int &a, int b) { return a < b ? a = b, 1 : 0; } inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; } struct Edge { int u, v, w, id; bool operator < (const Edge &p) const { return this->w < p.w; } } edge[N], sort_edge[N]; int n, m, sz[N], son[N], fa[N], top[N]; int dep[N], dfn[N], tol; bool vis[N]; std::vector<int> vec[N]; void dfs1(int u, int f) { fa[u] = f; sz[u] = 1; dep[u] = dep[f] + 1; for (int v: vec[u]) { if (v == f) continue; dfs1(v, u); sz[u] += sz[v]; if (sz[son[u]] < sz[v]) son[u] = v; } } void dfs2(int u, int tp) { top[u] = tp; dfn[u] = ++tol; if (!son[u]) return; dfs2(son[u], tp); for (int v: vec[u]) if (v != fa[u] && v != son[u]) dfs2(v, v); } const int INF = 0x3f3f3f3f; struct Seg { #define lp p << 1 #define rp p << 1 | 1 int tree[N << 2]; void build(int p, int l, int r) { tree[p] = INF; if (l == r) return; int mid = l + r >> 1; build(lp, l, mid); build(rp, mid + 1, r); } void update(int p, int l, int r, int x, int y, int z) { if (x <= l && y >= r) { chkmin(tree[p], z); return; } int mid = l + r >> 1; if (x <= mid) update(lp, l, mid, x, y, z); if (y > mid) update(rp, mid + 1, r, x, y, z); } int query(int p, int l, int r, int pos) { if (l == r) return tree[p]; int mid = l + r >> 1; if (pos <= mid) return std::min(tree[p], query(lp, l, mid, pos)); return std::min(tree[p], query(rp, mid + 1, r, pos)); } } seg; void solve(int u, int v, int w) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) std::swap(u, v); seg.update(1, 1, n, dfn[top[u]], dfn[u], w); u = fa[top[u]]; } if (dep[u] > dep[v]) std::swap(u, v); seg.update(1, 1, n, dfn[u] + 1, dfn[v], w); } struct DSU { int fa[N]; void init(int n) { for (int i = 1; i <= n; i++) fa[i] = i; } int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } bool merge(int u, int v) { u = find(u), v = find(v); if (u == v) return 0; fa[u] = v; return 1; } } dsu; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w); edge[i].id = i; sort_edge[i] = edge[i]; } dsu.init(n); std::sort(sort_edge + 1, sort_edge + 1 + m); int ed = 0, ans = 0; for (int i = 1; i <= m; i++) { int u = sort_edge[i].u, v = sort_edge[i].v; if (!dsu.merge(u, v)) continue; vec[u].push_back(v); vec[v].push_back(u); ed++; ans += sort_edge[i].w; vis[sort_edge[i].id] = 1; } int q; scanf("%d", &q); if (ed < n - 1) { while (q--) { int x; scanf("%d", &x); puts("Not connected"); } return 0; } dfs1(1, 0); dfs2(1, 1); seg.build(1, 1, n); assert(n == tol); for (int i = 1; i <= m; i++) { if (vis[i]) continue; solve(edge[i].u, edge[i].v, edge[i].w); } while (q--) { int i; scanf("%d", &i); if (!vis[i]) { printf("%d\n", ans); continue; } int u = dep[edge[i].u] < dep[edge[i].v] ? edge[i].v : edge[i].u; int temp = seg.query(1, 1, n, dfn[u]); if (temp == INF) puts("Not connected"); else printf("%d\n", ans - edge[i].w + temp); } return 0; }
2301. [HAOI2011]Problem b
询问拆成四个,就像矩阵数点一样。
每一个询问的形式为 $\sum_{i=1}^n\sum_{j=1}^m[(i,j)==k]$。
$$\sum_{i=1}^n\sum_{j=1}^m[(i,j)==k]=\sum_{i=1}^{\lfloor \frac{n}{k} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{k} \rfloor}[(i,j)==1]$$
把 $\lfloor \dfrac{n}{k} \rfloor$ 换成 $n$,把 $\lfloor \dfrac{m}{k} \rfloor$ 换成 $m$
$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d|(i,j)}\mu(d)=\sum_d \mu(d)\sum_{i=1}^{n}[d|i]\sum_{j=1}^{m}[d|j]$$
$$=\sum_d\mu(d)\lfloor \dfrac{n}{d}\rfloor\lfloor \dfrac{m}{d}\rfloor$$
求个 $\mu$ 的前缀和加数论分块。
#include <bits/stdc++.h> const int N = 5e4 + 7; int prime[N], prin, mu[N]; bool vis[N]; void init() { mu[1] = 1; for (int i = 2; i < N; i++) { if (!vis[i]) prime[++prin] = i, mu[i] = -1; for (int j = 1; j <= prin && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) { mu[i * prime[j]] = 0; break; } mu[i * prime[j]] = -mu[i]; } } for (int i = 1; i < N; i++) mu[i] += mu[i - 1]; } int solve(int n, int m) { int res = 0; for (int i = 1, j; i <= std::min(n, m); i = j + 1) { j = std::min(n / (n / i), m / (m / i)); res += (mu[j] - mu[i - 1]) * (n / i) * (m / i); } return res; } int main() { init(); int n; scanf("%d", &n); while (n--) { int a, b, c, d, k; scanf("%d%d%d%d%d", &a, &b, &c, &d, &k); printf("%d\n", solve(b / k, d / k) - solve(b / k, (c - 1) / k) - solve((a - 1) / k, d / k) + solve((a - 1) / k, (c - 1) / k)); } return 0; }
2693. jzptab
$$\sum_{i=1}^n\sum_{j=1}^m \text{lcm}(i, j)$$
$$=\sum_{i=1}^n\sum_{j=1}^m \frac{ij}{\text{gcd}(i,j)}$$
$$=\sum_{d}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij[(i,j)=1]$$
$$=\sum_{d}d\sum_{d'}\mu(d')d'^2 s(\lfloor\frac{n}{dd'}\rfloor)s(\lfloor\frac{m}{dd'}\rfloor)$$
$$=\sum_{T} s(\lfloor\frac{n}{T}\rfloor)s(\lfloor\frac{m}{T}\rfloor)\sum_{d|T}\mu(d)d^2\frac{T}{d}$$$$=\sum_{T} s(\lfloor\frac{n}{T}\rfloor)s(\lfloor\frac{m}{T}\rfloor)T\sum_{d|T}\mu(d)d$$
其中 $s(n) = \frac{n\times (n+1)}{2}$,设 $f(n)=\sum_{d|n}d\mu(d)$,$g(n)=nf(n)$,$f(n)$ 是积性函数,可以直接筛,问题解决。
#include <bits/stdc++.h> const int MOD = 100000009; const int N = 1e7 + 7; int prime[N], prin; int f[N]; bool vis[N]; void M(int &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } void init(int N) { f[1] = 1; for (int i = 2; i < N; i++) { if (!vis[i]) { prime[++prin] = i; M(f[i] = 1 - i + MOD); } for (int j = 1; j <= prin && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) { int temp = i; while (temp % prime[j] == 0) temp /= prime[j]; f[i * prime[j]] = 1LL * (1 - prime[j] + MOD) * f[temp] % MOD; break; } f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD; } } for (int i = 1; i < N; i++) f[i] = (1LL * f[i - 1] + 1LL * f[i] * i % MOD) % MOD; } int cal(int n, int m) { int x = 1LL * n * (n + 1) / 2 % MOD; int y = 1LL * m * (m + 1) / 2 % MOD; return 1LL * x * y % MOD; } int solve(int n, int m) { if (n > m) std::swap(n, m); int ans = 0; for (int i = 1, j; i <= n; i = j + 1) { j = std::min(n / (n / i), m / (m / i)); (ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - 1] + MOD) % MOD) %= MOD; } return ans; } signed main() { int T; scanf("%d", &T); init(N - 6); while (T--) { int n, m; scanf("%d%d", &n, &m); printf("%d\n", solve(n, m)); } return 0; }
2820. YY的GCD
$$\sum_{i=1}^{n}\sum_{i=1}^{m}\left[\left(i,j\right)\in P\right]$$
$$=\sum_{p\in P}\sum_{i=1}^{n}\sum_{i=1}^{m}\left[\left(i,j\right)=p\right]$$$$=\sum_{p\in P}\sum_{i=1}^{\lfloor \frac{n}{p} \rfloor}\sum_{i=1}^{\lfloor \frac{m}{p} \rfloor}\left[\left(i,j\right)=1\right]$$
$$=\sum_{p\in P}\sum_{d=1}^{\min\{\lfloor \frac{n}{p} \rfloor,\lfloor \frac{m}{p} \rfloor\}}\mu(d)\lfloor\frac{n}{pd}\rfloor\lfloor\frac{m}{pd}\rfloor$$
$$=\sum_{T=1}^{\min\{n, m\}}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{p|T \wedge p\in P}\mu(\frac{T}{p})$$
$f(n) = \sum_{p|n \wedge p \in P}\mu(\frac{n}{p})$ 不是积性函数,但是可以线性筛预处理出来。
对于质数 $p$,$f(p)=1$
当 $p < M(i)$ 时,$f(i \times p)=-f(i)+\mu(i)$
当 $p=M(i)$ 时,$i \times p$ 里含有 $p^2$,所以 $\forall p' \neq p$,$\mu(\frac{i\times p}{p'})=0$,所以 $f(i\times p)=\mu(i)$
#include <bits/stdc++.h> #define ll long long const int N = 1e7 + 7; int prime[N], prin, mu[N]; bool vis[N]; ll f[N]; void init() { mu[1] = 1; for (int i = 2; i < N; i++) { if (!vis[i]) { prime[++prin] = i; mu[i] = -1; f[i] = 1; } for (int j = 1; j <= prin && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) { mu[i * prime[j]] = 0; f[i * prime[j]] = mu[i]; break; } mu[i * prime[j]] = -mu[i]; f[i * prime[j]] = -f[i] + mu[i]; } } for (int i = 1; i < N; i++) f[i] += f[i - 1]; } ll solve(int n, int m) { ll ans = 0; for (int i = 1, j; i <= std::min(n, m); i = j + 1) { j = std::min(n / (n / i), m / (m / i)); ans += (f[j] - f[i - 1]) * (n / i) * (m / i); } return ans; } int main() { init(); int T; scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); printf("%lld\n", solve(n, m)); } return 0; }
2940. [Poi2000]条纹
就是枚举放一种条纹下去会变成啥样,要么变成两段要么变成一段,求下 SG 函数就好了。
复杂度 $O(n ^ 2)$
#include <bits/stdc++.h> const int N = 1100; int SG[N]; bool vis[N]; int main() { int a, b, c; scanf("%d%d%d", &a, &b, &c); for (int i = std::min(a, std::min(b, c)); i <= 1000; i++) { memset(vis, 0, sizeof(vis)); for (int j = 0; j <= i - a; j++) vis[SG[j] ^ SG[i - a - j]] = 1; for (int j = 0; j <= i - b; j++) vis[SG[j] ^ SG[i - b - j]] = 1; for (int j = 0; j <= i - c; j++) vis[SG[j] ^ SG[i - c - j]] = 1; for (int j = 0; ; j++) if (!vis[j]) { SG[i] = j; break; } } int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); puts(SG[n] ? "1" : "2"); } return 0; }
3529. [Sdoi2014]数表
$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_{1}(\gcd(i, j))[\sigma_1(\gcd(i,j))\leq a]$$
首先忽略 $\sigma_1(\gcd(i,j))\leq a$ 的限制
即求$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_{1}(\gcd(i, j))$$
$$=\sum_{d=1}^{\min\{n,m\}}\sigma_{1}(d)\sum_{i=1}^{n}\sum_{j=1}^{m}[(i,j)=d]$$
$$=\sum_{d=1}^{\min\{n,m\}}\sigma_{1}(d)\sum_{d'=1}^{\min\{\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor\}}\mu(d')\lfloor\frac{n}{dd'}\rfloor\lfloor\frac{m}{dd'}\rfloor$$
$$=\sum_{T}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{d|T}\sigma_1(d)\mu(\frac{T}{d})$$
设 $f(n)=\sum_{d|n}\sigma_1(d)\mu(\frac{n}{d})$,要维护这个的前缀和,会受 $\sigma_1(\gcd(i,j))\leq a$ 的就只有这里的 $\sigma_1(d)$,离线一下询问,用树状数组维护这个前缀和,然后就像扫描线一样去修改这个前缀和,总共需要修改 $n\log n$ 次,所以复杂度为 $O(n\log ^2 n)$
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } // using namespace IO const int N = 1e5; int prime[N + 7], prin, mu[N + 7], n, d[N + 7]; bool vis[N + 7]; std::pair<int, int> p[N + 7]; void init() { mu[1] = 1; for (int i = 2; i <= N; i++) { if (!vis[i]) { prime[++prin] = i; mu[i] = -1; } for (int j = 1; j <= prin && i * prime[j] <= N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) break; mu[i * prime[j]] = -mu[i]; } } for (int i = 1; i <= N; i++) for (int j = 1; 1LL * i * j <= N; j++) d[i * j] += i; for (int i = 1; i <= N; i++) { p[i].first = d[i], p[i].second = i; } std::sort(p + 1, p + 1 + N); } struct Bit { unsigned tree[N]; int lowbit(int x) { return x & -x; } void add(int x, unsigned v) { for (int i = x; i <= N; i += lowbit(i)) tree[i] += v; } unsigned query(int x) { unsigned ans = 0; for (int i = x; i; i -= lowbit(i)) ans += tree[i]; return ans; } } bit; struct QUERY{ int n, m, a, id; bool operator < (const QUERY &p) const { return a < p.a; } } que[N]; unsigned ans[N]; unsigned solve(int n, int m) { unsigned ans = 0; for (int i = 1, j; i <= std::min(n, m); i = j + 1) { j = std::min(n / (n / i), m / (m / i)); ans += 1u * (n / i) * (m / i) * (bit.query(j) - bit.query(i - 1)); } return ans; } int main() { init(); int q; IO::read(q); for (int i = 1; i <= q; i++) { IO::read(que[i].n, que[i].m, que[i].a); que[i].id = i; } std::sort(que + 1, que + 1 + q); int cur = 1; for (int i = 1; i <= q; i++) { while (cur <= N && p[cur].first <= que[i].a) { for (int j = 1; j * p[cur].second <= N; j++) bit.add(j * p[cur].second, p[cur].first * mu[j]); cur++; } ans[que[i].id] = solve(que[i].n, que[i].m); } for (int i = 1; i <= q; i++) IO::print(ans[i] & ((1u << 31) - 1)); IO::flush(); return 0; }
3576. [Hnoi2014]江南乐
有一个很显然的 $O(n^2)$ 的方法。枚举要分成 $i$ 份,然后大堆是 $n % i$,小堆有 $n - n \% i$ 的奇偶性,就可以求出 SG 函数。
但其实很多情况是冗余的,小堆的大小只有 $\sqrt n$ 种,即 $\lfloor \dfrac {n}{i}\rfloor$。
然后因为在同一段中 $n \% i$ 和 $n \% (i + 2)$ 的奇偶性相同,所以只需要求一下 $n \% i$ 和 $n \% (i + 1)$ 的情况即可。
#include <bits/stdc++.h> const int N = 1e5 + 7; int SG[N], vis[N], T, F; std::vector<int> a[110]; int get(int n) { for (int i = 2, j, s; i <= n; i = j + 1) { j = n / (n / i); s = n / i; int y = n % i, x = i - y; vis[((x & 1) * SG[s]) ^ ((y & 1) * SG[s + 1])] = n; if (i < j) { y = n % (i + 1); x = i + 1 - y; vis[((x & 1) * SG[s]) ^ ((y & 1) * SG[s + 1])] = n; } } for (int i = 0; ; i++) if (vis[i] != n) return i; } void init(int n) { for (int i = F; i <= n; i++) SG[i] = get(i); } int main() { scanf("%d%d", &T, &F); int mx = 0; for (int i = 1; i <= T; i++) { int n; scanf("%d", &n); a[i].resize(n); for (int j = 0; j < n; j++) scanf("%d", &a[i][j]), mx = std::max(mx, a[i][j]); } init(mx); for (int i = 1; i <= T; i++) { int n = a[i].size(); int ans = 0; for (int j = 0; j < n; j++) { int x = a[i][j]; ans ^= SG[x]; } printf("%d%c", ans ? 1 : 0, " \n"[i == T]); } return 0; }
3722. 精神污染
查询每一条路径覆盖了多少条路径。
一条路径 $(u, v)$,如果另一条路径 $(u_0, v_0)$ 被其覆盖,那么 $u_0$ 和 $v_0$ 一定在这条路径上。
那么可以以dfs序建主席树,每个节点代表一个版本的线段树,一条路径就将 $v$ 加到 $u$ 版本的线段树上。
在 $in[v]$ 处 +1, $out[v]$ 处 -1。
查询的时候就是差分查询,在 $root[u], root[v], root[lca], root[fa_lca]$ 查询 $(lca, u)$ + $(lca, v)$ - $(lca, lca)$ - $1$
毒瘤卡空间,得用树剖求LCA
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second #define ll long long const int N = 1e5 + 5; int n, m, in[N], out[N], fa[N], sz[N], son[N], dep[N], root[N], cnt, top[N]; std::vector<int> vec[N], query[N]; std::pii edge[N]; ll gcd(ll a, ll b) { while (b) { a %= b; std::swap(a, b); } return a; } struct Seg { struct Node { int lp, rp, sum; } tree[N * 40]; int tol; void update(int &p, int q, int l, int r, int pos, int v) { tree[p = ++tol] = tree[q]; tree[p].sum += v; if (l == r) return; int mid = l + r >> 1; if (pos <= mid) update(tree[p].lp, tree[q].lp, l, mid, pos, v); else update(tree[p].rp, tree[q].rp, mid + 1, r, pos, v); } int query(int p, int q, int f, int ff, int l, int r, int x, int y) { if (x <= l && y >= r) return tree[p].sum + tree[q].sum - tree[f].sum - tree[ff].sum; int mid = l + r >> 1; ll ans = 0; if (x <= mid) ans += query(tree[p].lp, tree[q].lp, tree[f].lp, tree[ff].lp, l, mid, x, y); if (y > mid) ans += query(tree[p].rp, tree[q].rp, tree[f].rp, tree[ff].rp, mid + 1, r, x, y); return ans; } } seg; void dfs1(int u, int f = 0) { fa[u] = f; dep[u] = dep[f] + 1; sz[u] = 1; in[u] = ++cnt; for (int v: vec[u]) if (v != f) { dfs1(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } out[u] = ++cnt; } void dfs2(int u, int tp) { top[u] = tp; if (!son[u]) return; dfs2(son[u], tp); for (int v: vec[u]) if (v != fa[u] && v != son[u]) dfs2(v, v); } void dfs(int u, int f = 0) { root[u] = root[f]; for (int v: query[u]) { seg.update(root[u], root[u], 1, cnt, in[v], 1); seg.update(root[u], root[u], 1, cnt, out[v], -1); } for (int v: vec[u]) if (v != f) dfs(v, u); } int Lca(int u, int v) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) std::swap(u, v); u = fa[top[u]]; } if (dep[u] > dep[v]) std::swap(u, v); return u; } int main() { freopen("in.txt", "r", stdin); //freopen("out1.txt", "w", stdout); scanf("%d%d", &n, &m); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } for (int i = 1; i <= m; i++) { scanf("%d%d", &edge[i].fi, &edge[i].se); query[edge[i].fi].push_back(edge[i].se); } std::sort(edge + 1, edge + 1 + m); dfs1(1); dfs2(1, 1); dfs(1); ll ans = 0; for (int i = 1; i <= m; i++) { int u = edge[i].fi, v = edge[i].se, f = Lca(u, v), ff = fa[f]; //printf("%d %d %d\n", u, v, f); ans += seg.query(root[u], root[v], root[f], root[ff], 1, cnt, in[f], in[u]); ans += seg.query(root[u], root[v], root[f], root[ff], 1, cnt, in[f], in[v]); ans -= seg.query(root[u], root[v], root[f], root[ff], 1, cnt, in[f], in[f]); ans--; } //printf("%lld\n", ans); long long all = 1LL * m * (m - 1) / 2; long long g = gcd(all, ans); printf("%lld/%lld\n", ans / g, all / g); return 0; }
3781. 小B的询问
莫队。
#include <bits/stdc++.h> #define ll long long const int N = 5e4 + 7; int cnt[N], a[N]; ll res, ans[N]; int B; struct Q { int l, r, id; bool operator < (const Q &p) const { return l / B == p.l / B ? r < p.r : l < p.l; } } q[N]; void add(int x) { res -= 1LL * cnt[x] * cnt[x]; cnt[x]++; res += 1LL * cnt[x] * cnt[x]; } void del(int x) { res -= 1LL * cnt[x] * cnt[x]; cnt[x]--; res += 1LL * cnt[x] * cnt[x]; } int main() { int n, m, k; scanf("%d%d%d", &n, &m, &k); B = n / sqrt(m); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i; std::sort(q + 1, q + 1 + m); int l = 1, r = 0; for (int i = 1; i <= m; i++) { while (l < q[i].l) del(a[l++]); while (l > q[i].l) add(a[--l]); while (r > q[i].r) del(a[r--]); while (r < q[i].r) add(a[++r]); ans[q[i].id] = res; } for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]); return 0; }
3994. [SDOI2015]约数个数和
求 $$\sum_{i=1}^n\sum_{j=1}^md(ij)$$
$$d(ij)=\sum_{x|i}\sum_{y|j}[(i,j)==1]=\sum_{x|i}\sum_{y|j}\sum_{p|(x,y)}\mu(p)$$
$$=\sum_p \mu(p)\sum_{x|i}[d|x]\sum_{y|j}[d|y]=\sum_{p|i,p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})$$
代回原式得
$$\sum_{i=1}^n\sum_{j=1}^m\sum_{p|i,p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})=\sum_{p}\mu(p)\sum_{p|i}d(\frac{i}{p})\sum_{p|j}d(\frac{j}{p})$$
$$=\sum_{p}\mu(p)\sum_{i=1}^{\lfloor \frac{n}{p} \rfloor}d(i)\sum_{j=1}^{\lfloor \frac{m}{p} \rfloor}d(j)$$
令 $s(n)=\sum_{i=1}^n d(i)$
原式为$$\sum_{p}\mu(p)s(\lfloor \frac{n}{p} \rfloor)s(\lfloor \frac{m}{p} \rfloor)$$
#include <bits/stdc++.h> const int N = 5e4 + 7; int mu[N], prime[N], prin, d[N], t[N]; bool vis[N]; void init(int n) { mu[1] = d[1] = t[1] = 1; for (int i = 2; i <= n; i++) { if (!vis[i]) { prime[++prin] = i; d[i] = 2; t[i] = 1; mu[i] = -1; } for (int j = 1; j <= prin && i * prime[j] < N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) { t[i * prime[j]] = t[i] + 1; mu[i * prime[j]] = 0; d[i * prime[j]] = d[i] / (t[i] + 1) * (t[i] + 2); break; } d[i * prime[j]] = d[i] * 2; t[i * prime[j]] = 1; mu[i * prime[j]] = -mu[i]; } } for (int i = 1; i <= n; i++) mu[i] += mu[i - 1], d[i] += d[i - 1]; } #define ll long long ll solve(int n, int m) { ll ans = 0; if (n > m) std::swap(n, m); for (int i = 1, j; i <= n; i = j + 1) { j = std::min(n / (n / i), m / (m / i)); ans += 1LL * (mu[j] - mu[i - 1]) * 1LL * d[n / i] * d[m / i]; } return ans; } int main() { init(N - 1); int T; scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); printf("%lld\n", solve(n, m)); } return 0; }
4025. 二分图
线段树分治+按秩合并的并查集解决加边删边的问题。
一个图是二分图当且仅当点数大于等于二并且不存在奇环。
那么可以用带权并查集维护路径长度,会出现环就是当加入一条边是产生环并且原路径长度为偶数。
#include <bits/stdc++.h> namespace IO { void read() {} template<class T, class... T2> void read(T &x, T2 &... oth) { x = 0; T f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); x *= f; read(oth...); } } const int N = 2e5 + 7; int st[N], top; struct DSU { int fa[N], d[N], dep[N]; void init(int n) { for (int i = 0; i <= n; i++) fa[i] = i, d[i] = dep[i] = 0; } int getfa(int x) { while (x != fa[x]) x = fa[x]; return x; } int getdis(int x) { int dis = 0; while (x != fa[x]) dis ^= d[x], x = fa[x]; return dis; } bool merge(int x, int y) { int D = getdis(x) ^ getdis(y) ^ 1; x = getfa(x), y = getfa(y); if (x == y) return D == 0; if (dep[x] < dep[y]) std::swap(x, y); if (dep[x] == dep[y]) dep[x]++, st[++top] = -x; fa[y] = x; d[y] = D; st[++top] = y; return 1; } void del(int tp) { while (top > tp) { int x = st[top--]; if (x < 0) dep[-x]--; else fa[x] = x, d[x] = 0; } } } dsu; int n, m, T; struct P { int u, v, x, y; } p[N]; int ans[N]; void solve(int l, int r, const std::vector<int> &vec) { int tp = top; int mid = l + r >> 1; std::vector<int> L, R; for (int i = 0, sz = vec.size(); i < sz; i++) { int id = vec[i]; if (p[id].x <= l && r <= p[id].y) { if (!dsu.merge(p[id].u, p[id].v)) { for (int j = l; j <= r; j++) ans[j] = 1; dsu.del(tp); return; } } else { if (p[id].x <= mid) L.push_back(id); if (p[id].y > mid) R.push_back(id); } } if (l == r) { dsu.del(tp); return; } solve(l, mid, L); solve(mid + 1, r, R); dsu.del(tp); } int main() { IO::read(n, m, T); dsu.init(n); std::vector<int> vec; for (int i = 1; i <= m; i++) IO::read(p[i].u, p[i].v, p[i].x, p[i].y), p[i].x++, vec.push_back(i); solve(1, T, vec); for (int i = 1; i <= T; i++) puts(ans[i] ? "No" : "Yes"); return 0; }
4299. Codechef FRBSUM
来补徐州的锅...其实在暑假补过这题...但是当时是把它拿来练主席树板子的...没懂原理...
若 $[1,x]$ 都能组成,那么 $[1, x + 1]$ 的区间和肯定也都能组成。证明就是数学归纳法...或者现在区间和为 $s$,那么就是倒着把 $1$、$2$、$3$、都取走就能得到其他的值了。
这样每次区间长度至少翻倍。再用主席树维护就行了...
复杂度 $O(nlog^2 n)$,徐州加了个单点修改,三 $log$ 可过...
View Code
4424. Cf19E Fairy
一个图为二分图的充要条件就是不存在奇环。
先求出一个dfs树,然后考虑非树边对dfs树的影响。
有几种情况需要考虑。
一、不存在自环及奇环
都可以删。
二、自环
如果存在两个自环及以上,就不可能了,因为它只能删除一条边。
有一个自环,当不存在奇环的时候就只能删除这个自环,否则也没边可删了。
三、存在一个奇环
那么这个奇环上的树边及非树边都可以删。也只有这种情况能删非树边。
四、存在多个奇环
那么能删除的边就是这些奇环的树边的交集。同时,这个交集的边不能出现在偶环上,否则奇环+偶环还是会得到奇环。
那么树上差分一下得到每条边在多少个奇环上,如果在偶环上就把路径减一下,就能处理出不能在偶环上的情况。最后就判断一下每一条边的值是否为奇环的个数。
#include <bits/stdc++.h> namespace IO { void read() {} template<class T, class... T2> void read(T &x, T2 &... oth) { x = 0; T f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); x *= f; read(oth...); } } const int N = 1e6 + 7; struct E { int v, ne, id; } e[N << 1]; int head[N], tag[N], dep[N], cnt = 1; bool vis[N]; int self, n, m; void add(int u, int v, int id) { e[++cnt].v = v; e[cnt].ne = head[u]; e[cnt].id = id; head[u] = cnt; e[++cnt].v = u; e[cnt].ne = head[v]; e[cnt].id = id; head[v] = cnt; } int tol, fei; void dfs(int u, int f) { for (int i = head[u]; i; i = e[i].ne) { if (i == (f ^ 1)) continue; int v = e[i].v; if (dep[v]) { if (dep[v] > dep[u]) continue; if ((dep[u] - dep[v] + 1) & 1) { tol++; fei = e[i].id; tag[u]++; tag[v]--; } else { tag[u]--; tag[v]++; } } else { dep[v] = dep[u] + 1; dfs(v, i); tag[u] += tag[v]; } } } std::vector<int> vec; void dfs(int u) { vis[u] = 1; for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (vis[v]) continue; if (tag[v] == tol) vec.push_back(e[i].id); dfs(v); } } int main() { IO::read(n, m); for (int i = 1; i <= m; i++) { int u, v; IO::read(u, v); if (u == v && !self) { self = i; continue; } if (u == v) { self = -1; continue; } add(u, v, i); } if (self == -1) { puts("0"); return 0; } for (int i = 1; i <= n; i++) { if (!dep[i]) dep[i] = 1, dfs(i, 0); } if (tol == 0) { if (self) { printf("1\n%d\n", self); } else { printf("%d\n", m); for (int i = 1; i <= m; i++) printf("%d%c", i, " \n"[i == m]); } return 0; } if (self) { puts("0"); return 0; } for (int i = 1; i <= n; i++) if (!vis[i]) dfs(i); if (tol == 1) vec.push_back(fei); printf("%d\n", (int)vec.size()); std::sort(vec.begin(), vec.end()); for (int i = 0; i < vec.size(); i++) printf("%d%c", vec[i], " \n"[i + 1 == vec.size()]); return 0; }
4652. [Noi2016]循环之美
首先,一个分数 $\frac{p}{q}$ 在 $k$ 进制下是纯循环小数,就是 $\exists t$,$p \equiv p \times k^t \pmod q$,其中 $p \bot q$,那么即为 $k^t \equiv 1 \pmod q$,要存在解,就得 $k \bot q$。
所以答案就是 $\sum_{i=1}^n\sum_{j=1}^m [i\bot j][k\bot q]$
$$\sum_{i=1}^n\sum_{j=1}^m [i\bot j][k\bot j]$$
$$=\sum_{i=1}^n\sum_{j=1}^m[k\bot j]\sum_{d|(i,j)}\mu(d)$$
$$=\sum_{d=1}^{\min\{n,m\}}[d\bot k]\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[j\bot k]$$
$$=\sum_{d=1}^{\min\{n,m\}}[d\bot k]\mu(d)\lfloor\frac{n}{d}\rfloor s(\lfloor\frac{m}{d}\rfloor)$$
其中 $s(n)=\sum_{i=1}^{n}[i\bot k]=\lfloor\frac{n}{k}\rfloor s(k) + s(n$ $\text{mod}$ $k)$,这个暴力预处理即可。
要求 $f(n)=\mu(n)[n\bot k]$ 的前缀和,设 $g(n)=[n\bot k]$。
$$(f\circ g)(n)$$
$$=\sum_{d\mid n}\mu(d)[d\bot k][\frac{n}{d}\bot k]$$
$$=[n\bot k]\sum_{d\mid n}\mu(d)$$
$$=[n \bot k][n=1]$$
所以 $\sum_{i=1}^n (f\circ g)(i)=1$
所以 $f$ 的前缀和 $S(n)=1-\sum_{i=2}^{n}[i\bot k]S(\lfloor\frac{n}{i}\rfloor)$
至此解决。
#include <bits/stdc++.h> const int N = 1e6, XN = N + 7; int prin, prime[XN], mu[XN]; int fs[XN], gs[XN]; int gcd(int a, int b) { while (b) { a %= b; std::swap(a, b); } return a; } void init(int k) { static bool vis[XN]; mu[1] = 1; for (int i = 2; i <= N; i++) { if (!vis[i]) { prime[++prin] = i; mu[i] = -1; } for (int j = 1; j <= prin && i * prime[j] <= N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) break; mu[i * prime[j]] = -mu[i]; } } for (int i = 1; i <= N; i++) { fs[i] = fs[i - 1] + mu[i] * (gcd(i, k) == 1); gs[i] = gs[i - 1] + (gcd(i, k) == 1); } } int k, n, m; int GS(int n) { return (n / k) * gs[k] + gs[n % k]; } #define ll long long std::unordered_map<int, ll> mp; ll solve(int n) { if (n <= N) return fs[n]; if (mp.count(n)) return mp[n]; ll ans = 1; for (int i = 2, j; i <= n; i = j + 1) { j = n / (n / i); ans -= 1LL * (GS(j) - GS(i - 1)) * solve(n / i); } return mp[n] = ans; } signed main() { scanf("%d%d%d", &n, &m, &k); init(k); ll ans = 0; for (int i = 1, j; i <= std::min(n, m); i = j + 1) { j = std::min(n / (n / i), m / (m / i)); ans += 1LL * (solve(j) - solve(i - 1)) * (n / i) * GS(m / i); } printf("%lld\n", ans); return 0; }
4742. [Usaco2016 Dec]Team Building
考虑将所有牛的分数从大到小排序,当分数相同时第二队的牛在前面。
这样把第一队的牛看成左括号,第二队的牛看成右括号,那就相当于要选出一个合法的括号序列。
$dp[i][j][k]$ 表示前 $i$ 头牛选了 $j$ 个第一队的,选了 $k$ 个第二队的方案数。
转移过程中保证 $j$ 大于 $k$ 即可。
View Code
4816. [Sdoi2017]数字表格
$$\prod_{i=1}^{n}\prod_{j=1}^{m}f(\gcd(i,j))$$
$$=\prod_{d=1}^{\min\{n,m\}}f(d)^{\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[(i,j)=1]}$$
$$=\prod_{d=1}^{\min\{n,m\}}f(d)^{\sum_{d'}\mu(d')\lfloor\frac{n}{dd'}\rfloor\lfloor\frac{m}{dd'}\rfloor}$$
$$=\prod_{T}(\prod_{d|T}f(d)^{\mu(\frac{T}{d})})^{\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor}$$
求出 $g(n) =\prod_{d|n}f(d)^{\mu(\frac{n}{d})}$ 的前缀积即可。
#include <bits/stdc++.h> const int MOD = 1e9 + 7; int qp(int a, int b = MOD - 2) { int ans = 1; while (b) { if (b & 1) ans = 1LL * ans * a % MOD; b >>= 1; a = 1LL * a * a % MOD; } return ans; } const int N = 1e6; int prime[N + 7], prin, mu[N + 7], g[N + 7], f[N + 7], fi[N + 7]; bool vis[N + 7]; inline int add(int x, int y) { return x + y >= MOD ? x + y - MOD : x + y; } void init() { mu[1] = 1; f[0] = 0; f[1] = 1; fi[1] = 1; g[0] = g[1] = 1; for (int i = 2; i <= N; i++) { g[i] = 1; f[i] = add(f[i - 1], f[i - 2]); fi[i] = qp(f[i]); if (!vis[i]) { prime[++prin] = i; mu[i] = -1; } for (int j = 1; j <= prin && i * prime[j] <= N; j++) { vis[i * prime[j]] = 1; if (i % prime[j] == 0) break; mu[i * prime[j]] = -mu[i]; } } for (int i = 1; i <= N; i++) if (mu[i]) for (int j = 1; 1LL * i * j <= N; j++) g[i * j] = 1LL * g[i * j] * (mu[i] == 1 ? f[j] : fi[j]) % MOD; for (int i = 2; i <= N; i++) g[i] = 1LL * g[i] * g[i - 1] % MOD; } int main() { init(); int T; scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); if (n > m) std::swap(n, m); int ans = 1; for (int i = 1, j; i <= n; i = j + 1) { j = std::min(n / (n / i), m / (m / i)); ans = 1LL * ans * qp(1LL * g[j] * qp(g[i - 1]) % MOD, 1LL * (n / i) * (m / i) % (MOD - 1)) % MOD; } printf("%d\n", ans); } return 0; }

浙公网安备 33010602011771号