BZOJ练习记
决定从头到尾干一波BZOJ!
可能会写没几题就停下吧,但还是想学学新姿势啦。
1001. [BeiJing2006]狼抓兔子
即求 $(1, 1)$ 到 $(n, m)$ 的最小割。跑 dinic 即可。
- #include <bits/stdc++.h>
- using namespace std;
- inline int read() {
- int x = , f = ; char ch = getchar();
- while (ch < '' || ch > '') { if (ch == '-') f = -; ch = getchar(); }
- while (ch >= '' && ch <= '') { x = x * + ch - ; ch = getchar(); }
- return x * f;
- }
- const int N = 1e6 + ;
- const int INF = 0x3f3f3f3f;
- struct E { int v, ne, f; } e[N * ];
- 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 = ; i <= t; i++) level[i] = -, iter[i] = head[i];
- queue<int> que;
- que.push(s);
- level[s] = ;
- 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] < && f) {
- level[v] = level[u] + ;
- que.push(v);
- }
- }
- }
- return level[t] != -;
- }
- int dfs(int u, int t, int f) {
- if (u == t || !f) return f;
- int flow = ;
- for (int i = iter[u]; ~i; i = e[i].ne) {
- iter[u] = i;
- int v = e[i].v;
- if (level[v] == level[u] + && e[i].f) {
- int w = dfs(v, t, min(f, e[i].f));
- if (!w) continue;
- e[i].f -= w, e[i^].f += w;
- flow += w, f -= w;
- if (f <= ) break;
- }
- }
- return flow;
- }
- int main() {
- memset(head, -, sizeof(head));
- n = read(), m = read();
- for (int i = ; i <= n; i++) {
- for (int j = ; j < m; j++) {
- int f = read();
- add((i - ) * m + j, (i - ) * m + j + , f);
- }
- }
- for (int i = ; i < n; i++) {
- for (int j = ; j <= m; j++) {
- int f = read();
- add((i - ) * m + j, i * m + j, f);
- }
- }
- for (int i = ; i < n; i++) {
- for (int j = ; j < m; j++) {
- int f = read();
- add((i - ) * m + j, i * m + j + , f);
- }
- }
- int ans = ;
- int s = , t = n * m;
- for (; bfs(s, t); ans += dfs(s, t, INF));
- printf("%d\n", ans);
- return ;
- }
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[], len;
- Bigi() {
- memset(a, , sizeof(a));
- len = ;
- }
- friend Bigi operator * (int x, Bigi b) {
- Bigi res;
- res.len = b.len + ;
- for (int i = ; i <= res.len; i++) {
- res.a[i] += b.a[i] * x;
- res.a[i + ] += res.a[i] / ;
- res.a[i] %= ;
- }
- while (res.len > && res.a[res.len] == ) res.len--;
- return res;
- }
- friend Bigi operator - (Bigi x, Bigi y) {
- Bigi res;
- res.len = x.len;
- for (int i = ; i <= res.len; i++) {
- res.a[i] = x.a[i] - y.a[i];
- while (res.a[i] < ) {
- res.a[i] += ;
- x.a[i + ]--;
- }
- }
- while (res.len > && res.a[res.len] == ) res.len--;
- return res;
- }
- friend Bigi operator + (Bigi x, int y) {
- Bigi res = x;
- res.a[] += y;
- for (int i = ; i <= res.len; i++) {
- if (res.a[i] >= ) {
- res.a[i] -= ;
- res.a[i + ]++;
- } else {
- break;
- }
- }
- while (res.a[res.len + ]) res.len++;
- return res;
- }
- void print() {
- for (int i = len; i; i--)
- printf("%d", a[i]);
- puts("");
- }
- } big[], ans;
- int main() {
- int n;
- scanf("%d", &n);
- big[] = big[] + ;
- big[] = big[] + ;
- if (n <= ) {
- big[n].print();
- return ;
- }
- for (int i = ; i <= n; i++)
- big[i] = * big[i - ] - big[i - ];
- ans = * big[n - ] - * big[n - ];
- ans = ans + (-);
- ans.print();
- return ;
- }
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 = ;
- const int N = ;
- 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 = ; i <= m; i++) {
- unable[i] = ;
- done[i] = ;
- dis[i] = INF;
- for (int j = l; j <= r; j++)
- unable[i] |= ban[j][i];
- }
- if (unable[] || unable[m]) return INF;
- priority_queue<pii, vector<pii>, greater<pii> > que;
- dis[] = ;
- que.push(pii(, ));
- while (!que.empty()) {
- auto pp = que.top(); que.pop();
- int u = pp.se;
- if (done[u]) continue;
- done[u] = ;
- 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] = ;
- }
- for (int i = ; i <= n; i++)
- for (int j = ; j <= n; j++)
- cost[i][j] = dijkstra(i, j);
- for (int i = ; i <= n; i++) {
- dp[i] = cost[][i] * i;
- for (int j = ; j < i; j++)
- checkmin(dp[i], dp[j] + cost[j + ][i] * (i - j) + k);
- }
- printf("%d\n", dp[n]);
- return ;
- }
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 = ;
- const int MOD = ;
- int num[N];
- int prime[N], tol, d[N], c[N];
- bool vis[N];
- void init() {
- for (int i = ; i < N; i++) {
- if (!vis[i]) prime[++tol] = i;
- for (int j = ; j <= tol && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) break;
- }
- }
- }
- void add(int x, int o) {
- for (int i = ; i <= tol; i++) {
- while (x % prime[i] == )
- c[i] += o, x /= prime[i];
- }
- }
- int main() {
- init();
- int n;
- scanf("%d", &n);
- bool flag = ;
- int sum = , cnt = ;
- for (int i = ; i <= n; i++) {
- scanf("%d", d + i);
- if (!d[i] || d[i] > n - ) flag = ;
- if (d[i] != -) sum += d[i] - , cnt++;
- }
- if (n == ) {
- if (!d[]) puts("");
- else puts("");
- return ;
- }
- if (sum > n - || flag) {
- puts("");
- return ;
- }
- for (int i = n - - sum + ; i <= n - ; i++)
- add(i, );
- for (int i = ; i <= n; i++) {
- if (d[i] > -) {
- for (int j = ; j < d[i]; j++)
- add(j, -);
- }
- }
- int len = ;
- num[++len] = ;
- for (int i = ; i <= n - - sum; i++) {
- for (int j = ; j <= len; j++) num[j] *= n - cnt;
- for (int j = ; j <= len; j++) {
- if (num[j] >= MOD) {
- num[j + ] += num[j] / MOD;
- num[j] %= MOD;
- }
- }
- while (num[len + ]) {
- num[len + ] += num[len] / MOD;
- num[len] %= MOD;
- len++;
- }
- }
- for (int i = ; i <= tol; i++) {
- while (c[i]) {
- for (int j = ; j <= len; j++) num[j] *= prime[i];
- for (int j = ; j <= len; j++) {
- if (num[j] >= MOD) {
- num[j + ] += num[j] / MOD;
- num[j] %= MOD;
- }
- }
- while (num[len + ]) {
- num[len + ] += num[len] / MOD;
- num[len] %= MOD;
- len++;
- }
- c[i]--;
- }
- }
- printf("%d", num[len]);
- for (int i = len - ; i; i--) printf("%04d", num[i]);
- puts("");
- return ;
- }
1007. [HNOI2008]水平可见直线
可见的直线为一下凸壳。
先按斜率和截距从小到大排序,再用单调栈判断交点的相对位置即可。
- #include <bits/stdc++.h>
- const int N = 5e4 + ;
- const double eps = 1e-;
- inline int dcmp(double x) {
- if (fabs(x) < eps) return ;
- return x < ? - : ;
- }
- struct P {
- double x, y;
- int id;
- inline bool operator < (const P &rhs) const {
- if (dcmp(x - rhs.x) == ) 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) == ) top--;
- else if (top > && dcmp(crossx(p[st[top]], cur) - crossx(p[st[top]], p[st[top - ]])) <= ) top--;
- else break;
- }
- st[++top] = id;
- }
- int ans[N];
- int main() {
- int n;
- scanf("%d", &n);
- for (int i = ; i <= n; i++) {
- scanf("%lf%lf", &p[i].x, &p[i].y);
- p[i].id = i;
- }
- std::sort(p + , p + + n);
- for (int i = ; i <= n; i++)
- ins(i);
- for (int i = ; i <= top; i++)
- ans[p[st[i]].id] = ;
- for (int i = ; i <= n; i++)
- if (ans[i])
- printf("%d ", i);
- return ;
- }
1008. [HNOI2008]越狱
总方案数 - 相邻颜色均不同的方案数。
- #include <bits/stdc++.h>
- #define ll long long
- const int MOD = ;
- int qp(int a, ll b) {
- a %= MOD;
- int ans = ;
- while (b) {
- if (b & ) ans = 1LL * ans * a % MOD;
- a = 1LL * a * a % MOD;
- b >>= ;
- }
- return ans;
- }
- int main() {
- int m;
- ll n;
- scanf("%d%lld", &m, &n);
- int ans = qp(m, n);
- ans = (ans - 1LL * m * qp(m - , n - ) % MOD) % MOD;
- ans = (ans + MOD) % MOD;
- printf("%d\n", ans);
- return ;
- }
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 = ;
- int MOD, n, m;
- char s[N];
- int ne[N];
- struct Mat {
- int mat[][];
- Mat() {
- memset(mat, , sizeof mat);
- }
- Mat operator * (const Mat &rhs) const {
- Mat c;
- for (int i = ; i < n; i++)
- for (int j = ; j < n; j++)
- for (int k = ; 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 & ) ans = ans * a;
- a = a * a;
- b >>= ;
- }
- return ans;
- }
- void kmp() {
- int i = , j = ne[] = -;
- while (i < n) {
- while (j != - && 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 = ; i < n; i++) {
- for (int j = ; j < ; j++) {
- int k = i;
- while (k != - && s[k] != j + '') k = ne[k];
- k++;
- if (k < n) b.mat[i][k]++;
- }
- }
- Mat a;
- a.mat[][] = ;
- a = qp(a, b, m);
- int ans = ;
- for (int i = ; i < n; i++)
- ans += a.mat[][i];
- ans %= MOD;
- printf("%d\n", ans);
- return ;
- }
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 + ;
- 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]) + * 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 = ; i <= n; i++)
- scanf("%lld", &c[i]), sum[i] = sum[i - ] + c[i];
- for (int i = ; i <= n; i++)
- sum[i] += i;
- int head = , tail = ;
- l++;
- for (int i = ; i <= n; i++) {
- while (head < tail && K(que[head], que[head + ]) < * sum[i]) head++;
- int j = que[head];
- dp[i] = dp[j] + sqr(sum[i] - sum[j] - l);
- while (head < tail && K(que[tail - ], que[tail]) > K(que[tail], i)) tail--;
- que[++tail] = i;
- }
- printf("%lld\n", dp[n]);
- return ;
- }
1011. [HNOI2008]遥远的行星
怀疑是没有spj。反正是个瞎搞题?
1012. [JSOI2008]最大数maxnumber
开足够位置就变成单点修改区间查询。
线段树维护即可。
- #include <bits/stdc++.h>
- #define ll long long
- const int N = 2e5 + ;
- struct Seg {
- #define lp p << 1
- #define rp p << 1 | 1
- ll tree[N << ];
- 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 >> ;
- if (pos <= mid) update(lp, l, mid, pos, v);
- else update(rp, mid + , 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 >> ;
- ll ans = ;
- if (x <= mid) ans = std::max(ans, query(lp, l, mid, x, y));
- if (y > mid) ans = std::max(ans, query(rp, mid + , r, x, y));
- return ans;
- }
- } seg;
- int main() {
- int n;
- ll D;
- scanf("%d%lld", &n, &D);
- ll ans = ;
- int cnt = ;
- for (int i = ; i <= n; i++) {
- char s[];
- ll num;
- scanf("%s%lld", s, &num);
- if (s[] == 'A') {
- num += ans;
- num %= D;
- seg.update(, , n, ++cnt, num);
- } else {
- printf("%lld\n", ans = seg.query(, , n, cnt - num + , cnt));
- }
- }
- return ;
- }
1013. [JSOI2008]球形空间产生器sphere
圆心坐标为 $(a_1, a_2, a_3, \cdots, a_n)$
那么可以列出 $n$ 条等式,两边的平方项都消掉了。那么高斯消元求解即可。
- #include <bits/stdc++.h>
- const int N = ;
- const double eps = 1e-;
- int n;
- double a[N][N];
- struct Node {
- double d[N];
- } p[N];
- void gauss() {
- for (int i = ; i <= n; i++) {
- int r = i;
- for (int j = i + ; 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 = ; j <= n; j++) {
- if (j != i) {
- double t = a[j][i] / a[i][i];
- for (int k = i; k <= n + ; k++)
- a[j][k] -= a[i][k] * t;
- }
- }
- }
- for (int i = ; i <= n; i++)
- a[i][n + ] /= a[i][i];
- }
- int main() {
- scanf("%d", &n);
- for (int i = ; i <= n + ; i++)
- for (int j = ; j <= n; j++)
- scanf("%lf", &p[i].d[j]);
- for (int i = ; i <= n; i++) {
- for (int j = ; j <= n; j++) {
- a[i][j] = * p[i].d[j] - * p[i + ].d[j];
- a[i][n + ] += p[i].d[j] * p[i].d[j] - p[i + ].d[j] * p[i + ].d[j];
- }
- }
- gauss();
- for (int i = ; i <= n; i++)
- printf("%.3f%c", a[i][n + ], " \n"[i == n]);
- return ;
- }
1014. [JSOI2008]火星人prefix
如果没有插入操作,仅仅只有修改操作,那么可以用线段树维护区间哈希值,对于一个查询,二分长度,再判是否相等即可。
现在多了插入操作,那么就用 splay 来维护哈希值,与线段树不同的只是多了非叶子节点也代表一个字符。
- #include <bits/stdc++.h>
- #define ull unsigned long long
- const int N = 2e5 + ;
- ull base[N];
- const ull BASE = ;
- int tol, root, m;
- char s[N];
- struct Splay {
- int ch[N][], fa[N], sz[N];
- ull val[N], ha[N];
- inline bool chk(int x) {
- return ch[fa[x]][] == x;
- }
- inline void pushup(int x) {
- sz[x] = sz[ch[x][]] + sz[ch[x][]] + ;
- ha[x] = ha[ch[x][]] + val[x] * base[sz[ch[x][]]] + ha[ch[x][]] * base[(sz[ch[x][]] + )];
- }
- void rotate(int x) {
- int y = fa[x], z = fa[y], k = chk(x), w = ch[x][k ^ ];
- ch[y][k] = w; fa[w] = y;
- ch[z][chk(y)] = x; fa[x] = z;
- ch[x][k ^ ] = y; fa[y] = x;
- pushup(y); pushup(x);
- }
- void splay(int x, int goal = ) {
- 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 () {
- if (ch[cur][] && k <= sz[ch[cur][]])
- cur = ch[cur][];
- else if (k > + sz[ch[cur][]])
- k -= + sz[ch[cur][]], cur = ch[cur][];
- else
- break;
- }
- return cur;
- }
- ull gethash(int l, int len) {
- int x = kth(l), y = kth(l + len + );
- splay(x), splay(y, x);
- return ha[ch[y][]];
- }
- void insert(int x, int v) {
- int l = kth(x + ), r = kth(x + );
- splay(l); splay(r, l);
- val[++tol] = v; fa[tol] = r; ch[r][] = tol;
- splay(tol);
- }
- void update(int x, int v) {
- int l = kth(x), r = kth(x + );
- splay(l); splay(r, l);
- val[ch[r][]] = v;
- splay(ch[r][]);
- }
- int query(int x, int y) {
- int l = , r = tol - std::max(x, y) - ;
- int ans = ;
- while (l <= r) {
- int mid = l + r >> ;
- if (gethash(x, mid) == gethash(y, mid)) ans = mid, l = mid + ;
- else r = mid - ;
- }
- return ans;
- }
- void init() {
- ch[][] = ;
- root = , tol = ;
- fa[] = ;
- pushup();
- pushup();
- }
- } splay;
- int main() {
- for (int i = base[] = ; i < N; i++)
- base[i] = base[i - ] * BASE;
- scanf("%s%d", s + , &m);
- int n = strlen(s + );
- splay.init();
- for (int i = ; i <= n; i++)
- splay.insert(i - , s[i]);
- for (int x, y; m--; ) {
- char s[];
- scanf("%s", s);
- if (s[] == 'Q') {
- scanf("%d%d", &x, &y);
- printf("%d\n", splay.query(x, y));
- } else if (s[] == 'R') {
- char ss[];
- scanf("%d%s", &x, s);
- splay.update(x, s[]);
- } else {
- char ss[];
- scanf("%d%s", &x, s);
- splay.insert(x, s[]);
- }
- }
- return ;
- }
1015. [JSOI2008]星球大战starwar
并查集不好直接删除边,那么考虑时光倒流,反向加边即可。
- #include <bits/stdc++.h>
- const int N = 4e5 + ;
- 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 = ; i < n; i++)
- fa[i] = i;
- for (int i = ; 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 = ; i <= k; i++)
- scanf("%d", a + i), vis[a[i]] = ;
- res = n - k;
- for (int i = ; 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] = ;
- res++;
- for (int v: vec[u])
- if (!vis[v])
- merge(u, v);
- }
- printf("%d\n", res);
- for (int i = ; i <= k; i++)
- printf("%d\n", ans[i]);
- return ;
- }
1016. [JSOI2008]最小生成树计数
最小生成树会出现多个是因为权值相同的边可替换。把处理同权值的边称为一个阶段,若处理一个阶段中的边的顺序会影响该阶段后连通块的连通性,那么这就与可替换相矛盾。
所以处理完一个阶段后这棵树的连通性应该是固定的。(感觉解释不太清楚,但是应该可以意会吧)。
用并查集 $fa$ 和 邻接矩阵 $G$ 表示每一时刻树的连通情况以及邻接情况。
用并查集 $pre$ 表示上一阶段树的连通情况。
处理一个阶段后,对每个连通块用矩阵树定理求一遍生成树个数,根据乘法原理相乘。
然后再把连通块缩成点再处理下一阶段。
- #include <bits/stdc++.h>
- using namespace std;
- const int N = ;
- const int MOD = ;
- 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 * ];
- 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 = ;
- for (int i = ; i <= n; i++) {
- for (int k = i + ; 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 = ; i < m; i++)
- edge[i].read();
- sort(edge, edge + m);
- for (int i = ; i <= n; i++) pre[i] = i;
- int last = -;
- int ans = ;
- for (int k = ; k <= m; k++) {
- if (last != edge[k].cost || k == m) {
- for (int i = ; i <= n; i++) {
- if (vis[i]) {
- int u = getfa(i, fa);
- vec[u].push_back(i);
- vis[i] = ;
- }
- }
- for (int i = ; i <= n; i++) {
- if (vec[i].size() > ) {
- memset(A, , sizeof(A));
- int len = vec[i].size();
- for (int a = ; a < len; a++)
- for (int b = a + ; 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 - ) % MOD;
- for (int a = ; a < len; a++)
- pre[vec[i][a]] = i;
- }
- }
- for (int i = ; 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] = ;
- fa[getfa(u, fa)] = getfa(v, fa);
- G[u][v]++;
- G[v][u]++;
- }
- int flag = ;
- for (int i = ; i <= n; i++) {
- if (getfa(i, fa) != getfa(i - , fa)) {
- puts("");
- return ;
- }
- }
- if (!m) {
- puts("");
- return ;
- }
- printf("%d\n", ans);
- return ;
- }
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 = ;
- const int INF = 0x3f3f3f3f;
- const int M = 2e3 + ;
- int dp[N][][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 = ; 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[][] = ;
- for (int c = mx[u]; ~c; c--) {
- int cur = ;
- for (auto p: vec[u]) {
- int v = p.fi;
- cur++;
- for (int j = ; j <= m; j++)
- for (int k = ; k <= j; k++)
- f[cur][j] = std::max(f[cur][j], f[cur - ][j - k] + dp[v][c * p.se][k]);
- }
- for (int i = ; i <= c; i++)
- for (int j = ; 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 = ; i <= n; i++) {
- scanf("%d", val + i);
- char s[];
- scanf("%s", s);
- if (s[] == '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 = ;
- for (int i = ; i <= n; i++)
- if (degree[i])
- flag = ;
- int ans = ;
- memset(dp, 0xcf, sizeof dp);
- if (flag) {
- for (int i = ; i <= n; i++)
- if (!degree[i]) {
- dfs(i);
- for (int j = ; j <= mx[i]; j++)
- for (int k = ; k <= m; k++)
- ans = std::max(ans, dp[i][j][k]);
- }
- } else {
- f[][] = ;
- for (int i = ; i <= n; i++)
- for (int k = ; k <= mx[i]; k++)
- for (int j = k * cost[i]; j <= m; j++)
- f[i][j] = std::max(f[i][j], f[i - ][j - k * cost[i]] + k * val[i]);
- for (int i = ; i <= m; i++)
- ans = std::max(ans, f[n][i]);
- }
- printf("%d\n", ans);
- return ;
- }
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 + ;
- int fa[N], sz[N], top, n, q, cnt, id[][N], tol;
- std::pii st[N], query[N];
- std::map<int, int> mp[N];
- struct Node {
- int u, v, st, ed;
- Node(int u = , int v = , int st = , int ed = ): 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 >> ;
- 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 + , 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 = ; i <= n; i++)
- id[][i] = ++tol, id[][i] = ++tol;
- while () {
- char s[];
- scanf("%s", s);
- if (s[] == 'E') break;
- int x1, y1, x2, y2;
- scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
- if (s[] == '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 + , -));
- } else if (s[] == '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 == -)
- p.ed = q;
- }
- for (int i = ; i <= tol; i++)
- fa[i] = i, sz[i] = ;
- solve(, q, E);
- return ;
- }
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 = ;
- ll f[N][];
- int g[N][];
- bool vis[];
- int main() {
- int n;
- scanf("%d", &n);
- for (int i = ; i <= ; i++) {
- static char s[];
- scanf("%s", s);
- int a = s[] - 'A', b = s[] - 'A';
- if (vis[a]) continue;
- vis[a] = ;
- f[][a] = ;
- g[][a] = b;
- }
- for (int j = ; j <= n; j++)
- for (int i = ; i < ; i++) {
- int a = g[j - ][i], b = - a - i;
- if (g[j - ][a] == b) f[j][i] = f[j - ][i] + + f[j - ][a], g[j][i] = b;
- else f[j][i] = f[j - ][i] + + f[j - ][a] + + f[j - ][i], g[j][i] = a;
- }
- printf("%lld\n", f[n][]);
- return ;
- }
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-;
- const int N = ;
- int dcmp(double x) {
- if (fabs(x) < eps) return ;
- return x > ? : -;
- }
- struct Point {
- double x, y;
- Point(double x = , double y = ): 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)) <= ; }
- 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[ + ];
- struct Polygon {
- std::vector<Point> poly;
- int n;
- bool In(const Point &p) {
- int wn = ;
- for (int i = ; i <= n; i++) {
- if (On(p, poly[i], poly[i % n + ])) return ;
- int k = dcmp(Cross(poly[i % n + ] - poly[i], p - poly[i]));
- int d1 = dcmp(poly[i].y - p.y);
- int d2 = dcmp(poly[i % n + ].y - p.y);
- if (k > && d1 <= && d2 > ) wn++;
- if (k < && d2 <= && d1 > ) wn--;
- }
- if (wn) return ;
- return ;
- }
- } 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 = ; i <= m; i++)
- flight[i].read();
- for (int i = ; i <= n; i++) {
- scanf("%d", &island[i].n);
- island[i].poly.resize(island[i].n + );
- for (int j = ; j <= island[i].n; j++)
- island[i].poly[j].read();
- }
- }
- bool check(const Point &p) {
- for (int i = ; i <= n; i++)
- if (island[i].In(p)) return ;
- return ;
- }
- 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)) <= ) return near(b, Len(v2));
- if (dcmp(Dot(v1, v3)) >= ) 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, );
- near ans1;
- ans1.dis = 1e10;
- for (int i = ; i <= n; i++)
- for (int j = ; j <= island[i].n; j++) {
- near cur = DISPS(p, island[i].poly[j], island[i].poly[j % island[i].n + ]);
- if (dcmp(ans1.dis - cur.dis) >= ) ans1 = cur;
- }
- ans = std::max(ans, ans1.dis);
- return ans1;
- }
- const int M = 1e5;
- void solve() {
- int head = , tail = ;
- for (int i = ; i < m; i++)
- queue[++tail] = Seg(flight[i], flight[i + ]), Find(flight[i]);
- Find(flight[m]);
- while (head != tail) {
- Seg cur = queue[head = head % M + ];
- Point p1 = Find(cur.a).p, p2 = Find(cur.b).p, l = cur.a, r = cur.b;
- while (Len(r - l) > 1e-) {
- Point mid = (l + r) / ;
- 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 + ] = Seg(cur.a, l);
- queue[tail = tail % M + ] = Seg(l, cur.b);
- }
- }
- }
- int main() {
- freopen("in.txt", "r", stdin);
- init();
- solve();
- printf("%.2f\n", ans);
- return ;
- }
1021. [SHOI2008]Debt 循环的债务
自己的DP真是弱爆了...虽然其他也弱...看完别人的题解恍然大悟...
$dp[i][j][k]$ 表示前 $i$ 种钞票,第一个人有 $j$ 块钱,第二个人有 $k$ 块钱所需要交换的次数。
$dp[0][第一个人初始钱数][第二个人初始钱数] = 0$
然后就暴力枚举两个人要多少张 $i$ 钞票转移即可。
- #include <bits/stdc++.h>
- const int N = ;
- const int INF = 0x3f3f3f3f;
- const int val[] = {, , , , , , };
- int dp[][N][N], sum, cnt[][], d[], e[];
- inline bool chkmin(int &a, const int &b) {
- return a > b ? a = b, : ;
- }
- int main() {
- int a, b, c;
- scanf("%d%d%d", &a, &b, &c);
- for (int i = ; i <= ; i++)
- for (int j = ; j <= ; 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[] - a + c, lastb = d[] - b + a, lastc = sum - lasta - lastb;
- if (lasta < || lastb < || lastc < ) {
- puts("impossible");
- return ;
- }
- memset(dp, 0x3f, sizeof dp);
- dp[][d[]][d[]] = ;
- for (int i = ; i <= ; i++)
- for (int j = ; j <= sum; j++)
- for (int k = ; k + j <= sum; k++) if (dp[i - ][j][k] != INF) {
- chkmin(dp[i][j][k], dp[i - ][j][k]);
- for (int m = ; m <= e[i]; m++)
- for (int n = ; n + m <= e[i]; n++) {
- int difa = m - cnt[][i], difb = n - cnt[][i];
- int jj = j + difa * val[i], kk = k + difb * val[i];
- if (jj < || kk < || sum - jj - kk < ) continue;
- chkmin(dp[i][jj][kk], dp[i - ][j][k] + (std::abs(difa) + std::abs(difb) + std::abs(difa + difb)) / );
- }
- }
- if (dp[][lasta][lastb] == INF) puts("impossible");
- else printf("%d\n", dp[][lasta][lastb]);
- return ;
- }
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 = ;
- int sg = ;
- while (n--) {
- int x;
- scanf("%d", &x);
- sg ^= x;
- if (x > ) flag = ;
- }
- if (flag)
- puts(sg ? "John" : "Brother");
- else
- puts(!sg ? "John" : "Brother");
- }
- return ;
- }
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[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
- int p, p3 = -;
- void read() {}
- void print() {}
- inline int getc() {
- return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
- }
- inline void flush() {
- fwrite(buf2, , p3 + , stdout), p3 = -;
- }
- template <typename T, typename... T2>
- inline void read(T &x, T2 &... oth) {
- T f = ; x = ;
- char ch = getc();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
- while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
- x *= f;
- read(oth...);
- }
- template <typename T, typename... T2>
- inline void print(T x, T2... oth) {
- if (p3 > << ) flush();
- if (x < ) buf2[++p3] = , x = -x;
- do {
- a[++p] = x % + ;
- } while (x /= );
- 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, : ; }
- inline bool chkmax(int &x, int y) { return x < y ? x = y, : ; }
- const int N = 1e5 + ;
- 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] + ;
- for (int i = v; i != u; i = fa[i])
- a[cnt--] = dp[i];
- a[] = dp[u];
- cnt = dep[v] - dep[u] + ;
- for (int i = ; i <= cnt; i++)
- a[i + cnt] = a[i];
- int l = , r = ;
- que[l = r = ] = ;
- for (int i = ; i <= cnt + (cnt >> ); i++) {
- if (i - que[l] > (cnt >> )) 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 = ; i <= cnt; i++)
- chkmax(dp[u], a[i] + std::min(i - , cnt - i + ));
- }
- void dfs(int u, int pre) {
- fa[u] = pre; dep[u] = dep[pre] + ;
- 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] + ), chkmax(dp[u], dp[v] + );
- }
- for (int v: vec[u]) {
- if (dfn[u] < dfn[v] && fa[v] != u)
- solve(u, v);
- }
- }
- int main() {
- read(n, m);
- for (int i = ; i <= m; i++) {
- int k, u;
- read(k, u);
- for (int j = ; j <= k; j++) {
- int v;
- read(v);
- vec[u].push_back(v);
- vec[v].push_back(u);
- u = v;
- }
- }
- dfs(, );
- print(ans);
- IO::flush();
- return ;
- }
1024. [SCOI2009]生日快乐
由于 $n$ 很小,那么可以暴力搜一波。
因为所有面积必须相同,所以考虑每一刀的时候,左右两部分必须长度符合比例。
- #include <bits/stdc++.h>
- double solve(double x, double y, int n) {
- if (x < y) std::swap(x, y);
- if (n == ) return x / y;
- double ans = 1e9;
- for (int i = ; i <= n / ; 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 ;
- }
1026. [SCOI2009]windy数
数位DP裸题啦。
- #include <bits/stdc++.h>
- #define int long long
- using namespace std;
- int dp[][];
- int a[];
- int DP(int pos, int pre, bool limit, bool lead) {
- if (pos < ) return ;
- if (!limit && !lead && dp[pos][pre] != -) return dp[pos][pre];
- int ans = ;
- int up = limit ? a[pos] : ;
- if (lead) {
- ans += DP(pos - , , limit && == up, );
- for (int i = ; i <= up; i++)
- ans += DP(pos - , i, limit && i == up, );
- } else {
- for (int i = ; i <= pre - && i <= up; i++)
- ans += DP(pos - , i, limit && i == up, );
- for (int i = pre + ; i <= up; i++)
- ans += DP(pos - , i, limit && i == up, );
- }
- if (!limit && !lead) dp[pos][pre] = ans;
- return ans;
- }
- int solve(int x) {
- //if (x == 0) return 1;
- int pos = ;
- while (x) {
- a[pos++] = x % ;
- x /= ;
- }
- return DP(pos - , , , );
- }
- signed main() {
- int l, r;
- scanf("%lld%lld", &l, &r);
- memset(dp, -, sizeof(dp));
- printf("%lld\n", solve(r) - solve(l - ));
- return ;
- }
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-;
- inline int sign(db k) { return k < -eps ? - : 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 == - : cmp(y, rhs.y) == -; }
- 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) == || (sign(y) == && sign(x) >= ); }
- 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) != ;
- }
- 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) == - || cmp(r2, l1) == -);
- }
- 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) <=
- && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= ;
- }
- bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
- return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <
- && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < ;
- }
- bool isMiddle(db a, db m, db b) {
- return sign(a - m) == || sign(b - m) == || (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) == && isMiddle(p1, q, p2);
- }
- bool onSeg_strict(const P &p1, const P &p2, const P &q) {
- return crossOp(p1, p2, q) == && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < ;
- }
- 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) * - 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 ;
- 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 = ;
- int n, m;
- P material[N], alloy[N];
- bool spj() {
- for (int i = ; i < n; i++)
- if (!(alloy[i] == alloy[i - ]))
- return ;
- for (int i = ; i < m; i++)
- if (alloy[] == material[i])
- return ;
- return ;
- }
- const int INF = 0x3f3f3f3f;
- int dis[N][N];
- inline bool chkmin(int &a, int b) { return a > b ? a = b, : ; }
- int floyd() {
- for (int k = ; k < m; k++)
- for (int i = ; i < m; i++)
- if (dis[i][k] < INF)
- for (int j = ; j < m; j++)
- chkmin(dis[i][j], dis[i][k] + dis[k][j]);
- int ans = INF;
- for (int i = ; i < m; i++)
- chkmin(ans, dis[i][i]);
- return ans;
- }
- void addedge(int p, int q) {
- for (int i = ; i < n; i++) {
- int k = crossOp(material[p], material[q], alloy[i]);
- if (k > ) continue;
- if (k < ) return;
- if (!onSeg(material[p], material[q], alloy[i])) return;
- }
- dis[p][q] = ;
- }
- int main() {
- scanf("%d%d", &m, &n);
- for (int i = ; i < m; i++) {
- material[i].read();
- db x;
- scanf("%lf", &x);
- }
- for (int i = ; i < n; i++) {
- alloy[i].read();
- db x;
- scanf("%lf", &x);
- }
- if (spj()) {
- puts("");
- return ;
- }
- memset(dis, 0x3f, sizeof dis);
- for (int i = ; i < m; i++)
- for (int j = ; j < m; j++)
- if (i != j)
- addedge(i, j);
- int ans = floyd();
- printf("%d\n", ans == INF ? - : ans);
- return ;
- }
1028. [JSOI2007]麻将
暴力枚举要补哪张牌,再枚举谁是对子,最后 check 一下剩下能否组成顺子或刻子,优先 check 刻子。
- #include <cstdio>
- #include <vector>
- #include <algorithm>
- const int N = ;
- int a[N], cnt[N], n, m, temp[N];
- bool solve() {
- for (int i = ; i <= n; i++) {
- if (cnt[i] < ) continue;
- bool flag = ;
- cnt[i] -= ;
- for (int j = ; j <= n + ; j++)
- temp[j] = cnt[j];
- for (int j = ; j <= n + ; j++) {
- if (temp[j] < ) { flag = ; break; }
- temp[j] %= ;
- temp[j + ] -= temp[j];
- temp[j + ] -= temp[j];
- }
- cnt[i] += ;
- if (flag) return ;
- }
- return ;
- }
- int main() {
- scanf("%d%d", &n, &m);
- for (int i = ; i <= * m + ; i++) {
- int x;
- scanf("%d", &x);
- cnt[x]++;
- }
- std::vector<int> ans;
- for (int i = ; i <= n; i++) {
- cnt[i]++;
- if (solve())
- ans.push_back(i);
- cnt[i]--;
- }
- if (ans.size() == ) puts("NO");
- else for (int x: ans)
- printf("%d ", x);
- puts("");
- return ;
- }
1029. [JSOI2007]建筑抢修
ddl 靠前的先做,如果做不了,看看之前有没有耗时比它多的,有的话换掉,用堆维护。
- #include <bits/stdc++.h>
- #define ll long long
- const int N = 2e5 + ;
- 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 = ; i <= n; i++)
- scanf("%lld%lld", &p[i].x, &p[i].y);
- ll cur = ;
- std::sort(p + , p + + n);
- std::priority_queue<int> que;
- que.push();
- int ans = ;
- for (int i = ; 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 ;
- }
1030. [JSOI2007]文本生成器
建出AC自动机后DP,注意第一维要枚举长度,因为第一维枚举节点的话一些节点会往回跳。
- #include <bits/stdc++.h>
- const int N = ;
- const int sz = ;
- const int MOD = ;
- 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][];
- void init() {
- tol = ;
- memset(dp, , sizeof(dp));
- newnode();
- }
- int newnode() {
- for (int i = ; i < sz; i++)
- trie[tol][i] = ;
- fail[tol] = flag[tol] = last[tol] = ;
- return tol++;
- }
- void insert(char *s) {
- int n = strlen(s), cur = ;;
- for (int i = ; i < n; i++) {
- int id = s[i] - 'A';
- if (!trie[cur][id]) trie[cur][id] = newnode();
- cur = trie[cur][id];
- }
- flag[cur] = ;
- }
- int build() {
- std::queue<int> que;
- for (int i = ; i < sz; i++)
- if (trie[][i]) que.push(trie[][i]);
- while (!que.empty()) {
- int u = que.front(); que.pop();
- flag[u] |= flag[fail[u]];
- for (int i = ; 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[][] = ;
- for (int j = ; j < m; j++)
- for (int i = ; i < tol; i++) if (dp[i][j] && !flag[i])
- for (int k = ; k < sz; k++) {
- M(dp[trie[i][k]][j + ] += dp[i][j]);
- }
- int ans = ;
- for (int i = ; 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 = ;
- while (b) {
- if (b & ) ans = ans * a % MOD;
- a = a * a % MOD;
- b >>= ;
- }
- return ans;
- }
- int main() {
- while (~scanf("%d%d", &n, &m)) {
- ac.init();
- for (int i = ; i < n; i++)
- scanf("%s", s), ac.insert(s);
- printf("%d\n", (qp(, m) - ac.build() + MOD) % MOD);
- }
- return ;
- }
1031. [JSOI2007]字符加密Cipher
把字符串复制一下,求个后缀数组,再按sa枚举后缀,判断长度是否大于等于原字符串长度,注意坑就是第 $1$ 和 第 $len + 1$ 个后缀可能会重复。
- #include <bits/stdc++.h>
- const int N = 2e5 + ;
- char s[N];
- namespace SA {
- int sa[N], rk[N], fir[N], sec[N], c[N], height[N];
- void build(int len, int num = ) {
- register int i, j, k;
- for (i = ; i <= num; i++) c[i] = ;
- for (i = ; i <= len; i++) ++c[fir[i] = s[i]];
- for (i = ; i <= num; i++) c[i] += c[i - ];
- for (i = len; i >= ; i--) sa[c[fir[i]]--] = i;
- for (k = ; k <= len; k <<= ) {
- int cnt = ;
- for (i = len - k + ; i <= len; i++) sec[++cnt] = i;
- for (i = ; i <= len; i++) if (sa[i] > k) sec[++cnt] = sa[i] - k;
- for (i = ; i <= num; i++) c[i] = ;
- for (i = ; i <= len; i++) ++c[fir[i]];
- for (i = ; i <= num; i++) c[i] += c[i - ];
- for (i = len; i >= ; i--) sa[c[fir[sec[i]]]--] = sec[i], sec[i] = ;
- std::swap(fir, sec);
- fir[sa[]] = ; cnt = ;
- for (i = ; i <= len; i++)
- fir[sa[i]] = (sec[sa[i]] == sec[sa[i - ]] && sec[sa[i] + k] == sec[sa[i - ] + k]) ? cnt : ++cnt;
- if (cnt == len) break;
- num = cnt;
- }
- k = ;
- for (i = ; i <= len; i++) rk[sa[i]] = i;
- for (i = ; i <= len; i++) {
- if (rk[i] == ) continue;
- if (k) k--;
- j = sa[rk[i] - ];
- 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 + );
- int len = strlen(s + );
- for (int i = ; i <= len; i++)
- s[i + len] = s[i];
- build(len * );
- for (int i = ; i <= * len; i++) {
- if (sa[i] <= len + && !vis[id(sa[i], len)]) {
- ans[++cnt] = s[sa[i] + len - ];
- vis[id(sa[i], len)] = ;
- if (cnt == len) {
- puts(ans + );
- return ;
- }
- }
- }
- return ;
- }
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 = ;
- 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 = ; i <= n; i++)
- scanf("%d", a + i);
- int cur = ;
- for (int i = ; i <= n; i++) {
- if (a[i] != a[i - ]) {
- color[++cnt] = a[i - ];
- num[cnt] = cur;
- cur = ;
- } else {
- cur++;
- }
- }
- color[++cnt] = a[n];
- num[cnt] = cur;
- for (int i = ; i <= cnt; i++)
- f[i][i] = num[i] > ? : ;
- for (int len = ; len <= cnt; len++)
- for (int l = ; l <= cnt; l++) {
- int r = l + len - ;
- if (r > cnt) break;
- if (color[l] == color[r])
- f[l][r] = f[l + ][r - ] + ((num[l] + num[r]) > ? : );
- for (int k = l; k < r; k++)
- f[l][r] = std::min(f[l][r], f[l][k] + f[k + ][r]);
- }
- printf("%d\n", f[][cnt]);
- return ;
- }
1034. [ZJOI2008]泡泡堂BNB
感觉我双指针不行啊...写了个multiset过的。看了别人代码改了发双指针。
写几个样例大概就知道跟田忌赛马一样了。
- #include <bits/stdc++.h>
- const int N = 1e5 + ;
- int a[N], b[N];
- std::multiset<int> st1, st2;
- bool vis[N];
- int solve(int A[], int B[], int n) {
- int ans = ;
- int l1 = , l2 = , r1 = n, r2 = n;
- while (l1 <= r1 && l2 <= r2) {
- if (A[l1] > B[l2]) ans += , l1++, l2++;
- else if (A[r1] > B[r2]) ans += , r1--, r2--;
- else ans += (A[l1] == B[r2]) ? : , l1++, r2--;
- }
- return ans;
- }
- int main() {
- int n;
- scanf("%d", &n);
- for (int i = ; i <= n; i++)
- scanf("%d", a + i);
- for (int i = ; i <= n; i++)
- scanf("%d", b + i);
- std::sort(a + , a + + n);
- std::sort(b + , b + + n);
- printf("%d %d\n", solve(a, b, n), * n - solve(b, a, n));
- }
1036. [ZJOI2008]树的统计Count
树剖板子题。
- #include <bits/stdc++.h>
- using namespace std;
- template<typename T>
- inline void read(T &x) {
- x = ; T f = ; char ch = getchar();
- while (ch < '' || ch > '') { if (ch == '-') f = -; ch = getchar(); }
- while (ch >= '' && ch <= '') { x = x * + ch - ; ch = getchar(); }
- x *= f;
- }
- const int INF = 0x3f3f3f3f;
- const int N = ;
- 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 * ];
- int head[N], cnt;
- struct Seg {
- #define lp p << 1
- #define rp p << 1 | 1
- int sum[N << ], mx[N << ];
- 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 >> ;
- build(lp, l, mid);
- build(rp, mid + , 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 >> ;
- if (pos <= mid) update(lp, l, mid, pos, val);
- else update(rp, mid + , 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 = ;
- int mid = l + r >> ;
- if (x <= mid) ans += query1(lp, l, mid, x, y);
- if (y > mid) ans += query1(rp, mid + , 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 >> ;
- if (x <= mid) ans = max(ans, query2(lp, l, mid, x, y));
- if (y > mid) ans = max(ans, query2(rp, mid + , 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] = ;
- for (int i = head[u]; i; i = e[i].ne) {
- int v = e[i].v;
- if (v == pre) continue;
- dfs1(v, u, d + );
- 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 = ;
- while (top[u] != top[v]) {
- if (dep[top[u]] < dep[top[v]]) swap(u, v);
- ans += seg.query1(, , n, dfn[top[u]], dfn[u]);
- u = fa[top[u]];
- }
- if (dep[u] > dep[v]) swap(u, v);
- ans += seg.query1(, , 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(, , n, dfn[top[u]], dfn[u]));
- u = fa[top[u]];
- }
- if (dep[u] > dep[v]) swap(u, v);
- ans = max(ans, seg.query2(, , n, dfn[u], dfn[v]));
- return ans;
- }
- char s[];
- int main() {
- read(n);
- for (int i = ; i < n; i++) {
- int u, v;
- read(u), read(v);
- add(u, v);
- }
- for (int i = ; i <= n; i++) read(w[i]);
- dfs1(, , );
- dfs2(, );
- seg.build(, , n);
- read(q);
- while (q--) {
- scanf("%s", s);
- int u, v;
- read(u), read(v);
- if (s[] == 'M') {
- printf("%d\n", solve2(u, v));
- } else if (s[] == 'S') {
- printf("%d\n", solve1(u, v));
- } else {
- seg.update(, , n, dfn[u], v);
- }
- }
- return ;
- }
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 = ;
- const int N = ;
- int dp[N][N][][];
- void M(int &x) {
- if (x >= MOD) x -= MOD;
- if (x < ) x += MOD;
- }
- int main() {
- int n, m, k;
- scanf("%d%d%d", &n, &m, &k);
- dp[][][][] = ;
- for (int i = ; i <= n; i++)
- for (int j = ; j <= m; j++)
- for (int o = ; o <= k; o++)
- for (int p = ; p <= k; p++) if (dp[i][j][o][p]) {
- if (i < n && o < k)
- M(dp[i + ][j][o + ][std::max(p - , )] += dp[i][j][o][p]);
- if (j < m && p < k)
- M(dp[i][j + ][std::max(o - , )][p + ] += dp[i][j][o][p]);
- }
- int ans = ;
- for (int i = ; i <= k; i++)
- for (int j = ; j <= k; j++)
- M(ans += dp[n][m][i][j]);
- printf("%d\n", ans);
- return ;
- }
1038. [ZJOI2008]瞭望塔
能看到其他所有点的区域就是轮廓线的半平面交。
然后最小高度就是半平面交与轮廓线这两个一次分段函数的差,极值肯定出现在分段点上,分别求一下即可。
- #include <bits/stdc++.h>
- #define db double
- const db eps = 1e-;
- inline int sign(db k) { return k < -eps ? - : 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 == - : cmp(y, rhs.y) == -; }
- 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) == || (sign(y) == && sign(x) >= ); }
- 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[];
- L() {}
- L(const P &p0, const P &p1) {
- ps[] = p0; ps[] = p1;
- }
- P &operator[](int i) { return ps[i]; }
- P dir() { return ps[] - ps[]; }
- bool include(const P &p) { return sign((ps[] - ps[]).det(p - ps[])) > ; }
- L push() { // push eps outawrd
- const db Eps = 1e-;
- P delta = (ps[] - ps[]).normal() * Eps;
- return {ps[] - delta, ps[] - 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) != ;
- }
- // 直线交点
- 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[], l1[], l2[], l2[]);
- }
- /***** 线段相交 *****/
- 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) == - || cmp(r2, l1) == -);
- }
- 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) <=
- && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= ;
- }
- bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
- return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <
- && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < ;
- }
- /***************/
- /***** 点在线段上判定 *****/
- bool isMiddle(db a, db m, db b) {
- return sign(a - m) == || sign(b - m) == || (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) == && isMiddle(p1, q, p2);
- }
- bool onSeg_strict(const P &p1, const P &p2, const P &q) {
- return crossOp(p1, p2, q) == && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < ;
- }
- /*******************/
- // 投影
- 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) * - 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 ;
- 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 = ;
- for (int i = , n = ps.size(); i < n; i++)
- ans += ps[i].det(ps[(i + ) % n]);
- return ans;
- }
- // 点包含 2: inside 1: onSeg 0: outside
- int contain(const std::vector<P> &ps, const P &p) {
- int n = ps.size(), ret = ;
- for (int i = ; i < n; i++) {
- P u = ps[i], v = ps[(i + ) % n];
- if (onSeg(u, v, p)) return ;
- if (cmp(u.y, v.y) <= ) std::swap(u, v);
- if (cmp(p.y, u.y) > || cmp(p.y, v.y) <= ) continue;
- ret ^= crossOp(p, u, v) > ;
- }
- return ret * ;
- }
- // 凸包
- std::vector<P> convexHull(std::vector<P> ps) {
- int n = ps.size(); if (n <= ) return ps;
- std::sort(ps.begin(), ps.end());
- std::vector<P> qs(n * ); int k = ;
- for (int i = ; i < n; qs[k++] = ps[i++])
- while (k > && crossOp(qs[k - ], qs[k - ], ps[i]) <= ) --k;
- for (int i = n - , t = k; i >= ; qs[k++] = ps[i--])
- while (k > t && crossOp(qs[k - ], qs[k - ], ps[i]) <= ) --k;
- qs.resize(k - );
- return qs;
- }
- std::vector<P> convexHullNonStrict(std::vector<P> ps) {
- int n = ps.size(); if (n <= ) return ps;
- std::sort(ps.begin(), ps.end());
- std::vector<P> qs(n * ); int k = ;
- for (int i = ; i < n; qs[k++] = ps[i++])
- while (k > && crossOp(qs[k - ], qs[k - ], ps[i]) < ) --k;
- for (int i = n - , t = k; i >= ; qs[k++] = ps[i--])
- while (k > t && crossOp(qs[k - ], qs[k - ], ps[i]) < ) --k;
- qs.resize(k - );
- return qs;
- }
- // 点集直径
- db convexDiameter(const std::vector<P> &ps) {
- int n = ps.size(); if (n <= ) return ;
- int is = , js = ;
- for (int k = ; 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 + ) % n] - ps[i]).det(ps[(j + ) % n] - ps[j]) >= )
- (++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 = ; i < n; i++) {
- P p1 = ps[i], p2 = ps[(i + ) % n];
- int d1 = crossOp(q1, q2, p1), d2 = crossOp(q1, q2, p2);
- if (d1 >= ) qs.push_back(p1);
- if (d1 * d2 < ) 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 <= ) {
- 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 >> ;
- 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) <= ) qs.push_back(ps[i]);
- std::sort(qs.begin(), qs.end(), [](const P & a, const P & b) -> bool { return cmp(a.y, b.y) < ; });
- for (int i = ; i < qs.size(); i++)
- for (int j = i - ; j >= && cmp(qs[j].y, qs[i].y - ret) >= ; 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) == ) return ; // 相离
- if (cmp(d, r1 + r2) == ) return ; // 外切
- if (cmp(d, fabs(r1 - r2)) == ) return ; // 相交
- if (cmp(d, fabs(r1 - r2)) == ) return ; // 内切
- return ;
- }
- bool parallel(L l0, L l1) {
- return sign(l0.dir().det(l1.dir())) == ;
- }
- bool sameDir(L l0, L l1) {
- return parallel(l0, l1) && sign(l0.dir().dot(l1.dir())) == ;
- }
- bool cmp(P a, P b) {
- if (a.quad() != b.quad()) {
- return a.quad() < b.quad();
- } else {
- return sign(a.det(b)) > ;
- }
- }
- bool operator < (L l0, L l1) {
- if (sameDir(l0, l1)) {
- return l1.include(l0[]);
- } 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 + ;
- L que[N];
- std::vector<L> halfPlaneIS(std::vector<L> &l) {
- std::sort(l.begin(), l.end());
- int head = , tail = ;
- for (int i = ; i < l.size(); i++) {
- if (i && sameDir(l[i], l[i - ])) continue;
- while (tail - head > && !check(que[tail - ], que[tail - ], l[i])) tail--;
- while (tail - head > && !check(que[head + ], que[head], l[i])) head++;
- que[tail++] = l[i];
- }
- while (tail - head > && !check(que[tail - ], que[tail - ], que[])) tail--;
- while (tail - head > && !check(que[], que[], que[tail - ])) 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[].x) <= ) return isLL(line[], L(p, P(p.x, p.y + ))).y;
- if (sign(p.x - point[n - ].x) >= ) return isLL(line[n], L(p, P(p.x, p.y + ))).y;
- for (int i = ; i < n - ; i++) {
- if (isMiddle(point[i].x, p.x, point[i + ].x))
- return isLL(line[i + ], L(p, P(p.x, p.y + ))).y;
- }
- return 1e11;
- }
- db getyy(P p, std::vector<P> point) {
- for (int i = , sz = point.size(); i < sz - ; i++) {
- if (isMiddle(point[i].x, p.x, point[i + ].x))
- return isLL(point[i], point[i + ], p, P(p.x, p.y + )).y;
- }
- return 1e11;
- }
- int main() {
- int n;
- scanf("%d", &n);
- if (n <= ) {
- puts("");
- return ;
- }
- std::vector<P> p(n);
- for (int i = ; i < n; i++)
- scanf("%lf", &p[i].x);
- for (int i = ; i < n; i++)
- scanf("%lf", &p[i].y);
- std::vector<L> l;
- for (int i = ; i < n - ; i++)
- l.push_back(L(p[i], p[i + ]));
- std::vector<L> half = halfPlaneIS(l);
- db ans = 1e10;
- std::vector<P> ss;
- for (int i = , sz = half.size(); i < sz - ; i++)
- ss.push_back(isLL(half[i], half[i + ]));
- for (int i = ; i < n; i++) {
- ans = std::min(ans, std::fabs(gety(p[i], ss, half) - p[i].y));
- }
- for (int i = , sz = half.size(); i < sz - ; i++) {
- P pp = ss[i];
- ans = std::min(ans, std::fabs(getyy(pp, p) - pp.y));
- }
- printf("%.3f\n", ans);
- return ;
- }
1039. [ZJOI2008]无序运动Movement
平移、旋转、放缩对两个相似三角形没有影响,那么一个长度为 $n$ 的轨迹就可以描述为 $n-2$ 个三角形,每个三角形就用相邻两边长来描述,还得加上第二条线段在第一条线段的逆时针还是顺时针方向,因为如果不加这个就会出现翻不翻转带来的影响,然后就变成了字符串匹配了。不过由于字符集很大,得用 map 来存边。然后翻转一下再做一遍。
如果一个轨迹共线的话,翻转后他会被重新算一遍,所以要除以 $2$。
如果一个轨迹长度小于 $3$ 的话, 他就能匹配上所有长度相等的子串。
- #include <bits/stdc++.h>
- namespace IO {
- char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
- int p, p3 = -;
- void read() {}
- void print() {}
- inline int getc() {
- return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
- }
- inline void flush() {
- fwrite(buf2, , p3 + , stdout), p3 = -;
- }
- template <typename T, typename... T2>
- inline void read(T &x, T2 &... oth) {
- T f = ; x = ;
- char ch = getc();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
- while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
- x *= f;
- read(oth...);
- }
- template <typename T, typename... T2>
- inline void print(T x, T2... oth) {
- if (p3 > << ) flush();
- if (x < ) buf2[++p3] = , x = -x;
- do {
- a[++p] = x % + ;
- } while (x /= );
- 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 + ;
- 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 = ;
- if ((B - A).det(C - B) > ) crs = ;
- else if ((B - A).det(C - B) < ) crs = -;
- return {lena, lenb, lenc, crs};
- }
- void done(std::vector<P> vec, int id) {
- if (vec.size() < ) {
- ans[id] = n - vec.size() + ;
- return;
- }
- par[id] = ;
- int rt = ;
- for (int i = ; i < vec.size() - ; i++) {
- Node o = getnode(vec[i], vec[i + ], vec[i + ]);
- if (o.dir) par[id] = ;
- 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[])
- 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 = ;
- for (int i = ; i < n - ; i++) {
- Node node = getnode(vec[i], vec[i + ], vec[i + ]);
- 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 = ; i <= m; i++) {
- int k;
- IO::read(k);
- point[i].resize(k);
- for (int j = ; j < k; j++)
- point[i][j].read();
- done(point[i], i);
- }
- build();
- all.resize(n);
- for (int i = ; i < n; i++)
- all[i].read();
- solve(all);
- for (int i = ; i < n; i++)
- all[i].y *= -;
- solve(all);
- for (int i = ; i <= tol; i++)
- for (int u: flag[i])
- ans[u] += cnt[i] / (par[u] + );
- for (int i = ; i <= m; i++)
- IO::print(ans[i]);
- IO::flush();
- return ;
- }
1040. [ZJOI2008]骑士
基环森林上DP。
我刚开始想的就是找到环,然后把环上每个点及它的子树缩成一个点,就变成一个环上的DP了。然后就是强制第一个不取和强制最后一个不取。
看了别人的题解发现可以不用那么麻烦,只要找到环上任意相邻的两点,强制把这条边断开,然后还是DP两次就行了。
DP方程就比较naive了。
- #include <bits/stdc++.h>
- namespace IO {
- char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
- int p, p3 = -;
- void read() {}
- void print() {}
- inline int getc() {
- return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
- }
- inline void flush() {
- fwrite(buf2, , p3 + , stdout), p3 = -;
- }
- template <typename T, typename... T2>
- inline void read(T &x, T2 &... oth) {
- T f = ; x = ;
- char ch = getc();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
- while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
- x *= f;
- read(oth...);
- }
- template <typename T, typename... T2>
- inline void print(T x, T2... oth) {
- if (p3 > << ) flush();
- if (x < ) buf2[++p3] = , x = -x;
- do {
- a[++p] = x % + ;
- } while (x /= );
- do {
- buf2[++p3] = a[p];
- } while (--p);
- buf2[++p3] = hh;
- print(oth...);
- }
- }
- #define ll long long
- const int N = 1e6 + ;
- int n, fa[N], root, _root;
- ll dp[N][], val[N];
- bool vis[N];
- int head[N];
- struct E {
- int v, ne;
- } e[N << ];
- int cnt = , 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] = ;
- 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][] = ; dp[u][] = val[u];
- for (int i = head[u]; i; i = e[i].ne) {
- int v = e[i].v;
- if (v == f || i == ban || i == (ban ^ )) continue;
- DP(v, u);
- dp[u][] += std::max(dp[v][], dp[v][]);
- dp[u][] += dp[v][];
- }
- }
- int main() {
- IO::read(n);
- for (int i = , u; i <= n; i++)
- IO::read(val[i], u), add(u, i), add(i, u);
- ll ans = ;
- for (int i = ; i <= n; i++) if (!vis[i]) {
- dfs(i, -);
- DP(root, -);
- ll temp = dp[root][];
- DP(_root, -);
- temp = std::max(temp, dp[_root][]);
- ans += temp;
- }
- printf("%lld\n", ans);
- return ;
- }
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 = ;
- for (ll g = ; g * g <= * r; g++) {
- if (( * r) % g) continue;
- for (ll a = ; a * a < g / ; a++) {
- ll b = sqrt(g - a * a + 0.5);
- if (a * a + b * b == g && gcd(a, b) == ) ans++;
- }
- for (ll a = ; a * a < r / g; a++) {
- ll b = sqrt( * r / g - a * a + 0.5);
- if (a * a + b * b == * r / g && gcd(a, b) == ) ans++;
- }
- }
- printf("%d\n", ans * + );
- return ;
- }
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 + ;
- ll dp[N];
- int w[], T, d[];
- int get(int x) {
- int cnt = ;
- while (x) {
- cnt++;
- x &= (x - );
- }
- return cnt;
- }
- int main() {
- for (int i = ; i < ; i++)
- scanf("%d", w + i);
- scanf("%d", &T);
- dp[] = ;
- for (int i = ; i < ; i++)
- for (int j = w[i]; j <= N - ; j++)
- dp[j] += dp[j - w[i]];
- while (T--) {
- for (int i = ; i < ; i++)
- scanf("%d", d + i);
- int s;
- scanf("%d", &s);
- ll ans = ;
- for (int i = ; i < ; i++) {
- int sz = get(i), sum = s;
- int c = sz & ? - : ;
- for (int j = ; j < ; j++)
- if (i >> j & )
- sum -= w[j] * (d[j] + );
- if (sum >= )
- ans += c * dp[sum];
- }
- printf("%lld\n", ans);
- }
- return ;
- }
1044. [HAOI2008]木棍分割
第一问二分傻逼题。
第二问前缀和优化一下DP
- #include <bits/stdc++.h>
- const int N = 5e4 + ;
- const int MOD = ;
- int n, m, a[N], dp[N][], sum[N];
- int dp_sum[N][];
- void M(int &x) {
- if (x >= MOD) x -= MOD;
- if (x < ) x += MOD;
- }
- inline bool chkmax(int &a, int b) {
- return a < b ? a = b, : ;
- }
- inline bool chkmin(int &a, int b) {
- return a > b ? a = b, : ;
- }
- bool check(int mid) {
- int cnt = , sum = ;
- for (int i = ; 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 = , r = ;
- for (int i = ; i <= n; i++)
- scanf("%d", a + i), chkmax(l, a[i]), r += a[i], sum[i] = sum[i - ] + a[i];
- int ans = ;
- while (l <= r) {
- int mid = l + r >> ;
- if (check(mid)) ans = mid, r = mid - ;
- else l = mid + ;
- }
- for (int i = ; i <= n; i++)
- dp[i][] = (sum[i] <= ans ? : ), M(dp_sum[i][] = dp_sum[i - ][] + dp[i][]);
- int ans2 = dp[n][];
- for (int j = ; j <= m; j++) {
- int cur = j & ;
- int last = ;
- for (int i = ; i <= n; i++) {
- dp[i][cur] = ;
- while (i > last && sum[i] - sum[last] > ans) last++;
- M(dp[i][cur] += dp_sum[i - ][cur ^ ] - dp_sum[std::max(last - , )][cur ^ ]);
- M(dp_sum[i][cur] = dp_sum[i - ][cur] + dp[i][cur]);
- }
- M(ans2 += dp[n][cur]);
- }
- printf("%d %d\n", ans, ans2);
- return ;
- }
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 + ;
- ll a[N];
- int main() {
- int n;
- scanf("%d", &n);
- ll sum = ;
- for (int i = ; i <= n; i++)
- scanf("%lld", a + i), sum += a[i];
- sum /= n;
- for (int i = ; i <= n; i++)
- a[i] = a[i - ] + sum - a[i];
- std::sort(a + , a + + n);
- sum = a[(n + ) / ];
- ll ans = ;
- for (int i = ; i <= n; i++)
- ans += std::abs(a[i] - sum);
- printf("%lld\n", ans);
- return ;
- }
1046. [HAOI2007]上升序列
先求出 $dp[i]$ 表示以 $i$ 开头的最长下降子序列。
然后因为是 $x$ 序列字典序最小,那就从左往右填就行了。
- #include <bits/stdc++.h>
- const int N = 1e4 + ;
- 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 = ;
- 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 = ; i <= n; i++)
- scanf("%d", a + i), v[i] = a[i];
- std::sort(v + , v + + n);
- cnt = std::unique(v + , v + + n) - v - ;
- int ans = ;
- for (int i = n; i >= ; i--) {
- a[i] = std::lower_bound(v + , v + + cnt, a[i]) - v;
- a[i] = cnt - a[i] + ;
- dp[i] = bit.query(a[i] - ) + ;
- bit.add(a[i], dp[i]);
- ans = std::max(ans, dp[i]);
- a[i] = cnt - a[i] + ;
- }
- int m;
- scanf("%d", &m);
- for (int i = ; i <= m; i++) {
- int len;
- scanf("%d", &len);
- if (len > ans) {
- puts("Impossible");
- continue;
- }
- int last = ;
- for (int i = ; i <= n; i++) {
- if (!len) break;
- if (dp[i] >= len && a[i] > last) {
- printf("%d%c", v[a[i]], " \n"[len == ]);
- len--;
- last = a[i];
- }
- }
- }
- return ;
- }
1047. [HAOI2007]理想的正方形
先对每一行做一遍单调队列,然后再对每一列做一遍单调队列即可。
- #include <bits/stdc++.h>
- namespace IO {
- char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
- int p, p3 = -;
- void read() {}
- void print() {}
- inline int getc() {
- return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
- }
- inline void flush() {
- fwrite(buf2, , p3 + , stdout), p3 = -;
- }
- template <typename T, typename... T2>
- inline void read(T &x, T2 &... oth) {
- T f = ; x = ;
- char ch = getc();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
- while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
- x *= f;
- read(oth...);
- }
- template <typename T, typename... T2>
- inline void print(T x, T2... oth) {
- if (p3 > << ) flush();
- if (x < ) buf2[++p3] = , x = -x;
- do {
- a[++p] = x % + ;
- } while (x /= );
- do {
- buf2[++p3] = a[p];
- } while (--p);
- buf2[++p3] = hh;
- print(oth...);
- }
- }
- const int N = ;
- int A[N][N], a, b, n, mx[N][N], mn[N][N];
- int main() {
- IO::read(a, b, n);
- for (int i = ; i <= a; i++)
- for (int j = ; j <= b; j++)
- IO::read(A[i][j]);
- static int que1[N], que2[N];
- for (int i = ; i <= a; i++) {
- int l1 = , r1 = , l2 = , r2 = ;
- for (int j = ; j <= b; j++) {
- {
- while (l1 < r1 && que1[l1] <= j - n) l1++;
- while (l1 < r1 && A[i][que1[r1 - ]] >= 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 - ]] <= 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 = , r1 = , l2 = , r2 = ;
- for (int i = ; i <= a; i++) {
- {
- while (l1 < r1 && que1[l1] <= i - n) l1++;
- while (l1 < r1 && mn[que1[r1 - ]][j] >= mn[i][j]) r1--;
- que1[r1++] = i;
- }
- {
- while (l2 < r2 && que2[l2] <= i - n) l2++;
- while (l2 < r2 && mx[que2[r2 - ]][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 ;
- }
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-;
- const int N = ;
- 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 ;
- return x > ? : -;
- }
- db sqr(db x) {
- return x * x;
- }
- db Sum(int x1, int x2, int y1, int y2) {
- return sum[x2][y2] + sum[x1 - ][y1 - ] - sum[x1 - ][y2] - sum[x2][y1 - ];
- }
- db solve(int x1, int x2, int y1, int y2, int c) {
- db &ans = f[x1][x2][y1][y2][c];
- if (ans != -) return ans;
- if (c == ) return ans = sqr(Sum(x1, x2, y1, y2) - ave);
- ans = 1e9;
- for (int i = x1; i < x2; i++)
- for (int cc = ; cc < c; cc++)
- ans = std::min(ans, solve(x1, i, y1, y2, cc) + solve(i + , x2, y1, y2, c - cc));
- for (int i = y1; i < y2; i++)
- for (int cc = ; cc < c; cc++)
- ans = std::min(ans, solve(x1, x2, y1, i, cc) + solve(x1, x2, i + , y2, c - cc));
- return ans;
- }
- int n, m, c;
- int main() {
- scanf("%d%d%d", &n, &m, &c);
- for (int i = ; i <= n; i++)
- for (int j = ; j <= n; j++)
- for (int k = ; k <= m; k++)
- for (int l = ; l <= m; l++)
- for (int o = ; o <= c; o++)
- f[i][j][k][l][o] = -;
- for (int i = ; i <= n; i++)
- for (int j = ; j <= m; j++)
- scanf("%d", &A[i][j]), sum[i][j] = sum[i - ][j] + sum[i][j - ] - sum[i - ][j - ] + A[i][j];
- ave = 1.0 * sum[n][m] / c;
- printf("%.2f\n", sqrt(solve(, n, , m, c) / c));
- return ;
- }
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 + ;
- const ll INF = 0x3f3f3f3f3f3f3f3f;
- const int inf = 0x3f3f3f3f;
- template<class T>
- inline bool chkmax(T &a, T b) {
- return a < b ? a = b, : ;
- }
- template<class T>
- inline bool chkmin(T &a, T b) {
- return a > b ? a = b, : ;
- }
- 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 = ;
- for (int i = x; i; i -= lowbit(i))
- chkmax(ans, tree[i]);
- return ans;
- }
- } bit;
- int main() {
- scanf("%d", &n);
- for (int i = ; 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 + , v + + n);
- cnt = std::unique(v + , v + + n) - v - ;
- int ans = ;
- for (int i = ; i <= n; i++) {
- b[i] = std::lower_bound(v + , v + + cnt, b[i]) - v;
- dp[i] = bit.query(b[i]) + ;
- bit.add(b[i], dp[i]);
- chkmax(ans, dp[i]);
- }
- ans = n - ans;
- printf("%d\n", ans);
- for (int i = ; i <= n; i++) {
- vec[dp[i]].push_back(i);
- b[i] = v[b[i]];
- g[i] = INF;
- }
- g[] = ; b[] = -inf;
- for (int i = ; i <= n; i++)
- for (int j = , sz = vec[dp[i] - ].size(); j < sz; j++) {
- int pos = vec[dp[i] - ][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 + ; k <= i; k++)
- s1[k] += s1[k - ], s2[k] += s2[k - ];
- 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 ;
- }
1188. [HNOI2007]分裂游戏
每次操作相当于在一个位置取一个豆子,放到后面的两个位置上,即 $i$ -> $(j, k)$。那么每个豆子只跟位置有关,而与它所在的那一堆有多少豆子无关。游戏的和就是所有豆子的 SG 函数的异或和。
那么一个位置有奇数个豆子时对游戏的和有贡献,理解起来就是如果一个位置有两个豆子的话,第二个人可以模仿第一个人的动作。
SG 函数可以倒着求出来,然后枚举第一步即可。
这里不是一堆石子是单个游戏,而是每个石子都是单个游戏。
- #include <bits/stdc++.h>
- const int N = ;
- 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 = ; i <= n; i++)
- scanf("%d", a + i), SG[i] = ;
- for (int i = n - ; i; i--) {
- memset(vis, , sizeof(vis));
- for (int j = i + ; j <= n; j++)
- for (int k = j; k <= n; k++)
- vis[SG[j] ^ SG[k]] = ;
- for (int j = ; ; j++)
- if (!vis[j]) {
- SG[i] = j;
- break;
- }
- }
- int A = , B = , C = , cnt = ;
- int ans = ;
- for (int i = ; i <= n; i++)
- if (a[i] & )
- ans ^= SG[i];
- for (int i = ; i <= n; i++) if (a[i])
- for (int j = i + ; 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 - , B - , C - , cnt);
- }
- return ;
- }
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 = ; T f = ; char ch = getchar();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
- while (isdigit(ch)) x = x * + ch - '', ch = getchar();
- x *= f;
- read(oth...);
- }
- }
- const int N = 1e6 + ;
- 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 = ; }
- void clear() { l = r = ; }
- bool empty() { return !(l ^ r); }
- void push_back(int v) { que[r++] = v; r %= LEN; }
- void push_front(int v) { que[l = (l - + LEN) % LEN] = v; }
- void pop_front() { ++l; l %= LEN; }
- void pop_back() { r = (r - + LEN) % LEN; }
- int front() { return que[l]; }
- int back() { return que[r - ]; }
- } que;
- template<class T>
- inline bool chkmax(T &a, T b) {
- return a < b ? a = b, : ;
- }
- int cur[N * ];
- ll s[N * ];
- int main() {
- IO::read(n);
- for (int i = ; i <= n; i++) {
- IO::read(to[i], w[i]);
- in[to[i]]++;
- }
- for (int i = ; 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 = ;
- for (int i = ; i <= n; i++) if (in[i] && !vis[i]) {
- int cnt = ;
- ll temp = ;
- for (int j = i; !vis[j]; j = to[j]) {
- cur[++cnt] = j;
- vis[j] = ;
- chkmax(temp, dp[j]);
- }
- int dcnt = cnt << ;
- for (int i = cnt + ; i <= dcnt; i++)
- cur[i] = cur[i - cnt];
- que.clear();
- for (int j = ; j <= dcnt; j++) {
- s[j] = s[j - ] + w[cur[j - ]];
- 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 ;
- }
2038. [2009国家集训队]小Z的袜子(hose)
莫队板子题。
- #include <bits/stdc++.h>
- const int N = 5e4 + ;
- int n, m, B, ans[N][], 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 = ; i <= n; i++)
- scanf("%d", col + i);
- for (int i = ; i <= m; i++)
- scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- B = sqrt(n);
- std::sort(q + , q + + m);
- int l = , r = ;
- for (int i = ; 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 == ) {
- ans[q[i].id][] = ans[q[i].id][] = ;
- continue;
- }
- int g = std::__gcd(fz, fm);
- ans[q[i].id][] = fz / g;
- ans[q[i].id][] = fm / g;
- }
- for (int i = ; i <= m; i++)
- printf("%d/%d\n", ans[i][], ans[i][]);
- return ;
- }
2120. 数颜色
带修莫队板子题。
- #include <bits/stdc++.h>
- const int N = 1e4 + ;
- int cnt[N * ], 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] == )
- ans++;
- }
- void del(int x) {
- --cnt[x];
- if (cnt[x] == )
- 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[];
- scanf("%d%d", &n, &m);
- B = n / sqrt(m * / );
- for (int i = ; i <= n; i++)
- scanf("%d", a + i);
- int qcnt = , ccnt = ;
- for (int i = ; i <= m; i++) {
- scanf("%s", s);
- if (s[] == '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 = , r = , cur = ;
- std::sort(q + , q + + qcnt);
- for (int i = ; 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 = ; 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 = ;
- const int N = 1e7 + ;
- int prime[N], prin;
- int f[N];
- bool vis[N];
- void M(int &x) {
- if (x >= MOD) x -= MOD;
- if (x < ) x += MOD;
- }
- void init(int N) {
- f[] = ;
- for (int i = ; i < N; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- M(f[i] = - i);
- }
- for (int j = ; j <= prin && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) {
- int temp = i;
- while (temp % prime[j] == ) temp /= prime[j];
- f[i * prime[j]] = 1LL * ( - prime[j] + MOD) * f[temp] % MOD;
- break;
- }
- f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
- }
- }
- for (int i = ; i < N; i++)
- M(f[i] = (f[i - ] + 1LL * f[i] * i % MOD));
- }
- int cal(int n, int m) {
- int x = 1LL * n * (n + ) / % MOD;
- int y = 1LL * m * (m + ) / % MOD;
- return 1LL * x * y % MOD;
- }
- int solve(int n, int m) {
- if (n > m) std::swap(n, m);
- int ans = ;
- for (int i = , j; i <= n; i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- M(ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - ] + MOD) % MOD);
- }
- return ans;
- }
- signed main() {
- int n, m;
- scanf("%d%d", &n, &m);
- init(std::min(n, m) + );
- printf("%d\n", solve(n, m));
- return ;
- }
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 + ;
- 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 = ; i < limit; i++)
- if (i < r[i])
- std::swap(a[i], a[r[i]]);
- for (int mid = ; mid < limit; mid <<= ) {
- Complex wn = Complex(cos(pi / mid), pd * sin(pi / mid));
- for (int l = mid << , j = ; j < limit; j += l) {
- Complex w = Complex(1.0, 0.0);
- for (int k = ; 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 == -)
- for (int i = ; i < limit; i++)
- a[i] = Complex(a[i].r / limit, a[i].i / limit);
- }
- int main() {
- scanf("%d", &n);
- for (int i = ; i < n; i++) {
- int x;
- scanf("%d", &x);
- x += ;
- A[x]++;
- B[x * ]++;
- C[x * ]++;
- }
- for (int i = ; i <= ; i++)
- a[i] = Complex((double)A[i], 0.0);
- for (int i = ; i <= ; i++)
- b[i] = Complex((double)B[i], 0.0);
- limit = ;
- while (limit <= + )
- limit <<= , l++;
- for (int i = ; i < limit; i++)
- r[i] = r[i >> ] >> | ((i & ) << (l - ));
- FFT(a, );
- FFT(b, );
- for (int i = ; i < limit; i++)
- b[i] = b[i] * a[i];
- for (int i = ; i < limit; i++)
- a[i] = a[i] * a[i] * a[i];
- FFT(a, -);
- FFT(b, -);
- for (int i = ; i <= ; i++) {
- long long ans = (long long)((a[i].r - 3.0 * b[i].r + 2.0 * C[i]) / 6.0 + 0.5);
- if (ans > )
- printf("%d : %lld\n", i - , ans);
- }
- return ;
- }
2238. Mst
先求出 Mst,如果要删的边不在 Mst 上,那么对答案不影响。
如果删的边 $(u, v)$ 在 Mst 上,相当于把树分成以 $u$ 为根和以 $v$ 为根两棵树,现在就要在两棵树里挑出一条权值最小的非树边。
预处理每一条非树边,它们之间的路径的边的值取个min,那么就树剖一下,建线段树,区间里面比这个大的数都变成这个数。
我以为要吉司机线段树,但是查询是单点查询,标记永久化即可。
- #include <bits/stdc++.h>
- const int N = 1e5 + ;
- inline bool chkmax(int &a, int b) { return a < b ? a = b, : ; }
- inline bool chkmin(int &a, int b) { return a > b ? a = b, : ; }
- 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] = ;
- dep[u] = dep[f] + ;
- 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 << ];
- void build(int p, int l, int r) {
- tree[p] = INF;
- if (l == r) return;
- int mid = l + r >> ;
- build(lp, l, mid);
- build(rp, mid + , 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 >> ;
- if (x <= mid) update(lp, l, mid, x, y, z);
- if (y > mid) update(rp, mid + , r, x, y, z);
- }
- int query(int p, int l, int r, int pos) {
- if (l == r) return tree[p];
- int mid = l + r >> ;
- if (pos <= mid) return std::min(tree[p], query(lp, l, mid, pos));
- return std::min(tree[p], query(rp, mid + , 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(, , n, dfn[top[u]], dfn[u], w);
- u = fa[top[u]];
- }
- if (dep[u] > dep[v]) std::swap(u, v);
- seg.update(, , n, dfn[u] + , dfn[v], w);
- }
- struct DSU {
- int fa[N];
- void init(int n) { for (int i = ; 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 ;
- fa[u] = v;
- return ;
- }
- } dsu;
- int main() {
- scanf("%d%d", &n, &m);
- for (int i = ; 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 + , sort_edge + + m);
- int ed = , ans = ;
- for (int i = ; 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] = ;
- }
- int q;
- scanf("%d", &q);
- if (ed < n - ) {
- while (q--) {
- int x;
- scanf("%d", &x);
- puts("Not connected");
- }
- return ;
- }
- dfs1(, );
- dfs2(, );
- seg.build(, , n);
- assert(n == tol);
- for (int i = ; 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(, , n, dfn[u]);
- if (temp == INF) puts("Not connected");
- else printf("%d\n", ans - edge[i].w + temp);
- }
- return ;
- }
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 + ;
- int prime[N], prin, mu[N];
- bool vis[N];
- void init() {
- mu[] = ;
- for (int i = ; i < N; i++) {
- if (!vis[i]) prime[++prin] = i, mu[i] = -;
- for (int j = ; j <= prin && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) {
- mu[i * prime[j]] = ;
- break;
- }
- mu[i * prime[j]] = -mu[i];
- }
- }
- for (int i = ; i < N; i++)
- mu[i] += mu[i - ];
- }
- int solve(int n, int m) {
- int res = ;
- for (int i = , j; i <= std::min(n, m); i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- res += (mu[j] - mu[i - ]) * (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 - ) / k) - solve((a - ) / k, d / k) + solve((a - ) / k, (c - ) / k));
- }
- return ;
- }
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 = ;
- const int N = 1e7 + ;
- int prime[N], prin;
- int f[N];
- bool vis[N];
- void M(int &x) {
- if (x >= MOD) x -= MOD;
- if (x < ) x += MOD;
- }
- void init(int N) {
- f[] = ;
- for (int i = ; i < N; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- M(f[i] = - i + MOD);
- }
- for (int j = ; j <= prin && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) {
- int temp = i;
- while (temp % prime[j] == ) temp /= prime[j];
- f[i * prime[j]] = 1LL * ( - prime[j] + MOD) * f[temp] % MOD;
- break;
- }
- f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
- }
- }
- for (int i = ; i < N; i++)
- f[i] = (1LL * f[i - ] + 1LL * f[i] * i % MOD) % MOD;
- }
- int cal(int n, int m) {
- int x = 1LL * n * (n + ) / % MOD;
- int y = 1LL * m * (m + ) / % MOD;
- return 1LL * x * y % MOD;
- }
- int solve(int n, int m) {
- if (n > m) std::swap(n, m);
- int ans = ;
- for (int i = , j; i <= n; i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- (ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - ] + MOD) % MOD) %= MOD;
- }
- return ans;
- }
- signed main() {
- int T;
- scanf("%d", &T);
- init(N - );
- while (T--) {
- int n, m;
- scanf("%d%d", &n, &m);
- printf("%d\n", solve(n, m));
- }
- return ;
- }
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 + ;
- int prime[N], prin, mu[N];
- bool vis[N];
- ll f[N];
- void init() {
- mu[] = ;
- for (int i = ; i < N; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- mu[i] = -;
- f[i] = ;
- }
- for (int j = ; j <= prin && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) {
- mu[i * prime[j]] = ;
- f[i * prime[j]] = mu[i];
- break;
- }
- mu[i * prime[j]] = -mu[i];
- f[i * prime[j]] = -f[i] + mu[i];
- }
- }
- for (int i = ; i < N; i++)
- f[i] += f[i - ];
- }
- ll solve(int n, int m) {
- ll ans = ;
- for (int i = , j; i <= std::min(n, m); i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- ans += (f[j] - f[i - ]) * (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 ;
- }
2940. [Poi2000]条纹
就是枚举放一种条纹下去会变成啥样,要么变成两段要么变成一段,求下 SG 函数就好了。
复杂度 $O(n ^ 2)$
- #include <bits/stdc++.h>
- const int N = ;
- 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 <= ; i++) {
- memset(vis, , sizeof(vis));
- for (int j = ; j <= i - a; j++)
- vis[SG[j] ^ SG[i - a - j]] = ;
- for (int j = ; j <= i - b; j++)
- vis[SG[j] ^ SG[i - b - j]] = ;
- for (int j = ; j <= i - c; j++)
- vis[SG[j] ^ SG[i - c - j]] = ;
- for (int j = ; ; j++)
- if (!vis[j]) {
- SG[i] = j;
- break;
- }
- }
- int T;
- scanf("%d", &T);
- while (T--) {
- int n;
- scanf("%d", &n);
- puts(SG[n] ? "" : "");
- }
- return ;
- }
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[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
- int p, p3 = -;
- void read() {}
- void print() {}
- inline int getc() {
- return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
- }
- inline void flush() {
- fwrite(buf2, , p3 + , stdout), p3 = -;
- }
- template <typename T, typename... T2>
- inline void read(T &x, T2 &... oth) {
- T f = ; x = ;
- char ch = getc();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
- while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
- x *= f;
- read(oth...);
- }
- template <typename T, typename... T2>
- inline void print(T x, T2... oth) {
- if (p3 > << ) flush();
- if (x < ) buf2[++p3] = , x = -x;
- do {
- a[++p] = x % + ;
- } while (x /= );
- do {
- buf2[++p3] = a[p];
- } while (--p);
- buf2[++p3] = hh;
- print(oth...);
- }
- } // using namespace IO
- const int N = 1e5;
- int prime[N + ], prin, mu[N + ], n, d[N + ];
- bool vis[N + ];
- std::pair<int, int> p[N + ];
- void init() {
- mu[] = ;
- for (int i = ; i <= N; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- mu[i] = -;
- }
- for (int j = ; j <= prin && i * prime[j] <= N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) break;
- mu[i * prime[j]] = -mu[i];
- }
- }
- for (int i = ; i <= N; i++)
- for (int j = ; 1LL * i * j <= N; j++)
- d[i * j] += i;
- for (int i = ; i <= N; i++) {
- p[i].first = d[i], p[i].second = i;
- }
- std::sort(p + , p + + 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 = ;
- 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 = ;
- for (int i = , j; i <= std::min(n, m); i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- ans += 1u * (n / i) * (m / i) * (bit.query(j) - bit.query(i - ));
- }
- return ans;
- }
- int main() {
- init();
- int q;
- IO::read(q);
- for (int i = ; i <= q; i++) {
- IO::read(que[i].n, que[i].m, que[i].a);
- que[i].id = i;
- }
- std::sort(que + , que + + q);
- int cur = ;
- for (int i = ; i <= q; i++) {
- while (cur <= N && p[cur].first <= que[i].a) {
- for (int j = ; 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 = ; i <= q; i++)
- IO::print(ans[i] & ((1u << ) - ));
- IO::flush();
- return ;
- }
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 + ;
- int SG[N], vis[N], T, F;
- std::vector<int> a[];
- int get(int n) {
- for (int i = , j, s; i <= n; i = j + ) {
- j = n / (n / i);
- s = n / i;
- int y = n % i, x = i - y;
- vis[((x & ) * SG[s]) ^ ((y & ) * SG[s + ])] = n;
- if (i < j) {
- y = n % (i + );
- x = i + - y;
- vis[((x & ) * SG[s]) ^ ((y & ) * SG[s + ])] = n;
- }
- }
- for (int i = ; ; 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 = ;
- for (int i = ; i <= T; i++) {
- int n;
- scanf("%d", &n);
- a[i].resize(n);
- for (int j = ; j < n; j++)
- scanf("%d", &a[i][j]), mx = std::max(mx, a[i][j]);
- }
- init(mx);
- for (int i = ; i <= T; i++) {
- int n = a[i].size();
- int ans = ;
- for (int j = ; j < n; j++) {
- int x = a[i][j];
- ans ^= SG[x];
- }
- printf("%d%c", ans ? : , " \n"[i == T]);
- }
- return ;
- }
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 + ;
- 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 * ];
- 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 >> ;
- if (pos <= mid) update(tree[p].lp, tree[q].lp, l, mid, pos, v);
- else update(tree[p].rp, tree[q].rp, mid + , 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 >> ;
- ll ans = ;
- 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 + , r, x, y);
- return ans;
- }
- } seg;
- void dfs1(int u, int f = ) {
- fa[u] = f;
- dep[u] = dep[f] + ;
- sz[u] = ;
- 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 = ) {
- root[u] = root[f];
- for (int v: query[u]) {
- seg.update(root[u], root[u], , cnt, in[v], );
- seg.update(root[u], root[u], , cnt, out[v], -);
- }
- 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 = ; i < n; i++) {
- int u, v;
- scanf("%d%d", &u, &v);
- vec[u].push_back(v);
- vec[v].push_back(u);
- }
- for (int i = ; i <= m; i++) {
- scanf("%d%d", &edge[i].fi, &edge[i].se);
- query[edge[i].fi].push_back(edge[i].se);
- }
- std::sort(edge + , edge + + m);
- dfs1();
- dfs2(, );
- dfs();
- ll ans = ;
- for (int i = ; 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], , cnt, in[f], in[u]);
- ans += seg.query(root[u], root[v], root[f], root[ff], , cnt, in[f], in[v]);
- ans -= seg.query(root[u], root[v], root[f], root[ff], , cnt, in[f], in[f]);
- ans--;
- }
- //printf("%lld\n", ans);
- long long all = 1LL * m * (m - ) / ;
- long long g = gcd(all, ans);
- printf("%lld/%lld\n", ans / g, all / g);
- return ;
- }
3781. 小B的询问
莫队。
- #include <bits/stdc++.h>
- #define ll long long
- const int N = 5e4 + ;
- 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 = ; i <= n; i++)
- scanf("%d", a + i);
- for (int i = ; i <= m; i++)
- scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- std::sort(q + , q + + m);
- int l = , r = ;
- for (int i = ; 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 = ; i <= m; i++)
- printf("%lld\n", ans[i]);
- return ;
- }
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 + ;
- int mu[N], prime[N], prin, d[N], t[N];
- bool vis[N];
- void init(int n) {
- mu[] = d[] = t[] = ;
- for (int i = ; i <= n; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- d[i] = ;
- t[i] = ;
- mu[i] = -;
- }
- for (int j = ; j <= prin && i * prime[j] < N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) {
- t[i * prime[j]] = t[i] + ;
- mu[i * prime[j]] = ;
- d[i * prime[j]] = d[i] / (t[i] + ) * (t[i] + );
- break;
- }
- d[i * prime[j]] = d[i] * ;
- t[i * prime[j]] = ;
- mu[i * prime[j]] = -mu[i];
- }
- }
- for (int i = ; i <= n; i++)
- mu[i] += mu[i - ], d[i] += d[i - ];
- }
- #define ll long long
- ll solve(int n, int m) {
- ll ans = ;
- if (n > m) std::swap(n, m);
- for (int i = , j; i <= n; i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- ans += 1LL * (mu[j] - mu[i - ]) * 1LL * d[n / i] * d[m / i];
- }
- return ans;
- }
- int main() {
- init(N - );
- int T;
- scanf("%d", &T);
- while (T--) {
- int n, m;
- scanf("%d%d", &n, &m);
- printf("%lld\n", solve(n, m));
- }
- return ;
- }
4025. 二分图
线段树分治+按秩合并的并查集解决加边删边的问题。
一个图是二分图当且仅当点数大于等于二并且不存在奇环。
那么可以用带权并查集维护路径长度,会出现环就是当加入一条边是产生环并且原路径长度为偶数。
- #include <bits/stdc++.h>
- namespace IO {
- void read() {}
- template<class T, class... T2>
- void read(T &x, T2 &... oth) {
- x = ; T f = ; char ch = getchar();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
- while (isdigit(ch)) x = x * + ch - '', ch = getchar();
- x *= f;
- read(oth...);
- }
- }
- const int N = 2e5 + ;
- int st[N], top;
- struct DSU {
- int fa[N], d[N], dep[N];
- void init(int n) {
- for (int i = ; i <= n; i++)
- fa[i] = i, d[i] = dep[i] = ;
- }
- int getfa(int x) {
- while (x != fa[x])
- x = fa[x];
- return x;
- }
- int getdis(int x) {
- int dis = ;
- while (x != fa[x])
- dis ^= d[x], x = fa[x];
- return dis;
- }
- bool merge(int x, int y) {
- int D = getdis(x) ^ getdis(y) ^ ;
- x = getfa(x), y = getfa(y);
- if (x == y) return D == ;
- 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 ;
- }
- void del(int tp) {
- while (top > tp) {
- int x = st[top--];
- if (x < )
- dep[-x]--;
- else
- fa[x] = x, d[x] = ;
- }
- }
- } 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 >> ;
- std::vector<int> L, R;
- for (int i = , 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] = ;
- 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 + , r, R);
- dsu.del(tp);
- }
- int main() {
- IO::read(n, m, T);
- dsu.init(n);
- std::vector<int> vec;
- for (int i = ; 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(, T, vec);
- for (int i = ; i <= T; i++)
- puts(ans[i] ? "No" : "Yes");
- return ;
- }
4299. Codechef FRBSUM
来补徐州的锅...其实在暑假补过这题...但是当时是把它拿来练主席树板子的...没懂原理...
若 $[1,x]$ 都能组成,那么 $[1, x + 1]$ 的区间和肯定也都能组成。证明就是数学归纳法...或者现在区间和为 $s$,那么就是倒着把 $1$、$2$、$3$、都取走就能得到其他的值了。
这样每次区间长度至少翻倍。再用主席树维护就行了...
复杂度 $O(nlog^2 n)$,徐州加了个单点修改,三 $log$ 可过...
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 = ; T f = ; char ch = getchar();
- while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
- while (isdigit(ch)) x = x * + ch - '', ch = getchar();
- x *= f;
- read(oth...);
- }
- }
- const int N = 1e6 + ;
- struct E {
- int v, ne, id;
- } e[N << ];
- int head[N], tag[N], dep[N], cnt = ;
- 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 ^ )) continue;
- int v = e[i].v;
- if (dep[v]) {
- if (dep[v] > dep[u]) continue;
- if ((dep[u] - dep[v] + ) & ) {
- tol++;
- fei = e[i].id;
- tag[u]++; tag[v]--;
- } else {
- tag[u]--; tag[v]++;
- }
- } else {
- dep[v] = dep[u] + ;
- dfs(v, i);
- tag[u] += tag[v];
- }
- }
- }
- std::vector<int> vec;
- void dfs(int u) {
- vis[u] = ;
- 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 = ; i <= m; i++) {
- int u, v;
- IO::read(u, v);
- if (u == v && !self) {
- self = i;
- continue;
- }
- if (u == v) {
- self = -;
- continue;
- }
- add(u, v, i);
- }
- if (self == -) {
- puts("");
- return ;
- }
- for (int i = ; i <= n; i++) {
- if (!dep[i])
- dep[i] = , dfs(i, );
- }
- if (tol == ) {
- if (self) {
- printf("1\n%d\n", self);
- } else {
- printf("%d\n", m);
- for (int i = ; i <= m; i++)
- printf("%d%c", i, " \n"[i == m]);
- }
- return ;
- }
- if (self) {
- puts("");
- return ;
- }
- for (int i = ; i <= n; i++)
- if (!vis[i])
- dfs(i);
- if (tol == )
- vec.push_back(fei);
- printf("%d\n", (int)vec.size());
- std::sort(vec.begin(), vec.end());
- for (int i = ; i < vec.size(); i++)
- printf("%d%c", vec[i], " \n"[i + == vec.size()]);
- return ;
- }
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 + ;
- 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[] = ;
- for (int i = ; i <= N; i++) {
- if (!vis[i]) {
- prime[++prin] = i;
- mu[i] = -;
- }
- for (int j = ; j <= prin && i * prime[j] <= N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) break;
- mu[i * prime[j]] = -mu[i];
- }
- }
- for (int i = ; i <= N; i++) {
- fs[i] = fs[i - ] + mu[i] * (gcd(i, k) == );
- gs[i] = gs[i - ] + (gcd(i, k) == );
- }
- }
- 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 = ;
- for (int i = , j; i <= n; i = j + ) {
- j = n / (n / i);
- ans -= 1LL * (GS(j) - GS(i - )) * solve(n / i);
- }
- return mp[n] = ans;
- }
- signed main() {
- scanf("%d%d%d", &n, &m, &k);
- init(k);
- ll ans = ;
- for (int i = , j; i <= std::min(n, m); i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- ans += 1LL * (solve(j) - solve(i - )) * (n / i) * GS(m / i);
- }
- printf("%lld\n", ans);
- return ;
- }
4742. [Usaco2016 Dec]Team Building
考虑将所有牛的分数从大到小排序,当分数相同时第二队的牛在前面。
这样把第一队的牛看成左括号,第二队的牛看成右括号,那就相当于要选出一个合法的括号序列。
$dp[i][j][k]$ 表示前 $i$ 头牛选了 $j$ 个第一队的,选了 $k$ 个第二队的方案数。
转移过程中保证 $j$ 大于 $k$ 即可。
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 + ;
- int qp(int a, int b = MOD - ) {
- int ans = ;
- while (b) {
- if (b & ) ans = 1LL * ans * a % MOD;
- b >>= ;
- a = 1LL * a * a % MOD;
- }
- return ans;
- }
- const int N = 1e6;
- int prime[N + ], prin, mu[N + ], g[N + ], f[N + ], fi[N + ];
- bool vis[N + ];
- inline int add(int x, int y) {
- return x + y >= MOD ? x + y - MOD : x + y;
- }
- void init() {
- mu[] = ;
- f[] = ; f[] = ;
- fi[] = ;
- g[] = g[] = ;
- for (int i = ; i <= N; i++) {
- g[i] = ;
- f[i] = add(f[i - ], f[i - ]);
- fi[i] = qp(f[i]);
- if (!vis[i]) {
- prime[++prin] = i;
- mu[i] = -;
- }
- for (int j = ; j <= prin && i * prime[j] <= N; j++) {
- vis[i * prime[j]] = ;
- if (i % prime[j] == ) break;
- mu[i * prime[j]] = -mu[i];
- }
- }
- for (int i = ; i <= N; i++) if (mu[i])
- for (int j = ; 1LL * i * j <= N; j++)
- g[i * j] = 1LL * g[i * j] * (mu[i] == ? f[j] : fi[j]) % MOD;
- for (int i = ; i <= N; i++)
- g[i] = 1LL * g[i] * g[i - ] % 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 = ;
- for (int i = , j; i <= n; i = j + ) {
- j = std::min(n / (n / i), m / (m / i));
- ans = 1LL * ans * qp(1LL * g[j] * qp(g[i - ]) % MOD, 1LL * (n / i) * (m / i) % (MOD - )) % MOD;
- }
- printf("%d\n", ans);
- }
- return ;
- }
BZOJ练习记的更多相关文章
- BZOJ开荒记
2019/4/16 1:04 使用Yinku2017提交了第一发,当然是A+B Problem. 看一下排行榜,算一下区域赛还有180多天吧?先用30天过50道题(含A+B Problem)怎么样?
- BZOJ.4738.[清华集训2016]汽水(点分治 分数规划)
BZOJ UOJ 记\(val_i\)是每条边的边权,\(s\)是边权和,\(t\)是经过边数,\(k\)是给定的\(k\). 在点分治的时候二分答案\(x\),设\(|\frac st-k|=x\) ...
- 【BZOJ】【3157】&【BZOJ】【3516】国王奇遇记
数论 题解:http://www.cnblogs.com/zhuohan123/p/3726933.html copy一下推导过程: 令$$S_i=\sum_{k=1}^{n}k^im^k$$ 我们有 ...
- [BZOJ 3157] 国王奇遇记
Link: BZOJ 3157 传送门 Solution: 题意:求解$\sum_{i=1}^n m^i \cdot {i^m}$ $O(m^2)$做法: 定义一个函数$f[i]$,$f[i]=\su ...
- 3157: 国王奇遇记 & 3516: 国王奇遇记加强版 - BZOJ
果然我数学不行啊,题解君: http://www.cnblogs.com/zhuohan123/p/3726933.html const h=; var fac,facinv,powm,s:..]of ...
- BZOJ 3157: 国王奇遇记 (数学)
题面:BZOJ3157 一句话题意: 求: \[ \sum_{i=1}^ni^m\ \times m^i\ (mod\ 1e9+7)\ \ (n \leq 1e9,m\leq200)\] 题解 令 \ ...
- BZOJ 3516 国王奇遇记加强版(乱推)
题意 求\(\sum_{k=1}^{n}k^mm^k (n\leq1e9,m\leq1e3)\) 思路 在<>中有一个方法用来求和,称为摄动法. 我们考虑用摄动法来求这个和式,看能不能得到 ...
- bzoj 3157 && bzoj 3516 国王奇遇记——推式子
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...
- bzoj 3157 & bzoj 3516 国王奇遇记 —— 推式子
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...
随机推荐
- C++ TCP客户端网络消息发送接收同步实现
废话不多说, 直入主题, 我们在写客户单的时候希望在哪里发消息出去,然后在哪里返回消息(同步), 然后继续往下运行-, 而不是在这里发送了一个消息给服务端, 在另一个地方接受消息(异步) , 也不知道 ...
- 软件工程实践2019——idea表述及组队
时间:2019-10-08 随堂 欢迎每个有想法的同学都积极参与idea表述,用心呈现你的心中所想.你心中热爱的,希望在软工实践项目中完成的项目作品.每个愿意表达idea的同学,都有一分钟时间来呈现作 ...
- 解决office365无法登录以及同步的问题
解决office365无法登录以及同步的问题 You better need to test them one by one. You better need to test them one by ...
- Mysql中使用JDBC流式查询避免数据量过大导致OOM
一.前言 java 中MySQL JDBC 封装了流式查询操作,通过设置几个参数,就可以避免一次返回数据过大导致 OOM. 二.如何使用 2.1 之前查询 public void selectData ...
- ASP.NET Core快速入门(第1章:介绍与引入)--学习笔记
课程链接:http://video.jessetalk.cn/course/explore 良心课程,大家一起来学习哈! 任务1:课程介绍 1.介绍与引入 2.配置管理 3.依赖注入 4.ASP.NE ...
- Grafana的Docker部署方式
docker run -d -p : --name=grafana544 -v D:/grafana/grafana-/data:/var/lib/grafana -v D:/grafana/graf ...
- Winform 窗体皮肤美化_IrisSkin
1 先把IrisSkin2.dll文件添加到当前项目引用(解决方案资源管理器->当前项目->引用->右键->添加引用,找到IrisSkin2.dll文件.....之后就不用我说 ...
- UWP使用Microsoft.Data.Sqlite的记录
我在UWP中使用SQLite数据库时,并没有使用网上的SQLite for Universal App Platform方案,而使用了Microsoft和SQLite社区一起维护的Microsoft. ...
- 模拟退火算法SA原理及python、java、php、c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径
模拟退火算法SA原理及python.java.php.c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径 模拟退火算法(Simulated Annealing,SA)最早的思 ...
- Git上传到码云及其常见问题详解
1.git init 初始化 2.git remote origin add https://gitee.com/su_yong_qing/SyqSystem.git 这里注意把链接替换为自己的仓库 ...