A
先考虑怎么判断区间 排序后是否能构成等差数列。
假设最后构成等差为 的等差数列
,充要条件就是:
。
如果最后能构成等差数列,就可以将数列 中每个元素减去
(这样仍是等差数列),然后除以次小值(最小值一定是
),得到的一定是
的排列。否则,一定不能构成等差数列。
考虑第二个条件,可以用辗转相除法变成 ,设答案为
, 则两两的差一定是
的倍数。
问题就转化为 。
因为 ,所以用线段树维护这个值的最小值以及最小值个数。
每次移动右端点,可以用单调栈来维护极值,每个数只会弹出一次。若固定某个点为区间左端点,那么随着右端点的移动, 最多只会变化
次,因为每次减至少除以
。对于每一个右端点,从右往左枚举左端点更新
,一旦更新不了,就可以
了。然后会有
段不同的
,对着
个区间减去相应的
。总时间复杂度是
#include <bits/stdc++.h> #define int long long using namespace std; const int N = 1e5 + 9; int read() { int f = 0, x = 0; char ch = getchar(); while (!isdigit(ch)) { f |= (ch == '-'); ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); } return f ? -x : x; } int t, n, w[N]; long long ans; int mn[N << 2], cnt[N << 2], tag[N << 2], d[N << 2]; int sa[N], ta; int sb[N], tb; void push_down(int p) { tag[p << 1] += tag[p]; mn[p << 1] += tag[p]; tag[p << 1 | 1] += tag[p]; mn[p << 1 | 1] += tag[p]; tag[p] = 0; } void push_up(int p) { cnt[p] = 0; mn[p] = min(mn[p << 1], mn[p << 1 | 1]); if (mn[p] == mn[p << 1]) cnt[p] += cnt[p << 1]; if (mn[p] == mn[p << 1 | 1]) cnt[p] += cnt[p << 1 | 1]; d[p] = (d[p << 1] == d[p << 1 | 1] ? d[p << 1] : 0); } void build(int p, int l, int r) { tag[p] = 0; mn[p] = 0; d[p] = 0; cnt[p] = r - l + 1; if (l == r) return; int mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); } void add(int p, int l, int r, int nl, int nr, int v) { if (nl <= l && r <= nr) { mn[p] += v; tag[p] += v; return; } int mid = (l + r) >> 1; push_down(p); if (nl <= mid) add(p << 1, l, mid, nl, nr, v); if (nr > mid) add(p << 1 | 1, mid + 1, r, nl, nr, v); push_up(p); } void change(int p, int l, int r, int nl, int nr, int v) { if (nl <= l && r <= nr && d[p] && v % d[p] == 0) { mn[p] -= d[p]; tag[p] -= d[p]; return; } if (l == r) { mn[p] += (nr - r) * d[p]; d[p] = __gcd(d[p], v); mn[p] -= (nr - r + 1) * d[p]; return; } int mid = (l + r) >> 1; push_down(p); if (nl <= mid) change(p << 1, l, mid, nl, nr, v); if (nr > mid) change(p << 1 | 1, mid + 1, r, nl, nr, v); push_up(p); } int get(int p, int l, int r, int nl, int nr) { if (nl <= l && r <= nr) return (mn[p] == 0) * cnt[p]; int mid = (l + r) >> 1; int res = 0; push_down(p); if (nl <= mid) res += get(p << 1, l, mid, nl, nr); if (nr > mid) res += get(p << 1 | 1, mid + 1, r, nl, nr); return res; } signed main() { t = read(); while (t--) { scanf("%d", &n); ans = 0; for (int i = 1; i <= n; ++i) scanf("%d", &w[i]); build(1, 1, n); ta = tb = 0; for (int i = 1; i <= n; ++i) { while (ta && w[sa[ta]] < w[i]) { add(1, 1, n, sa[ta - 1] + 1, sa[ta], -w[sa[ta]]); ta--; } while (tb && w[sb[tb]] > w[i]) { add(1, 1, n, sb[tb - 1] + 1, sb[tb], w[sb[tb]]); tb--; } sa[++ta] = i; add(1, 1, n, sa[ta - 1] + 1, sa[ta], w[sa[ta]]); sb[++tb] = i; add(1, 1, n, sb[tb - 1] + 1, sb[tb], -w[sb[tb]]); if (i != 1) change(1, 1, n, 1, i - 1, abs(w[i] - w[i - 1])); ans += get(1, 1, n, 1, i); } printf("%lld\n", ans); } return 0; }
B
在 个炮的一行中吃一次方案数为
,因为有方向。
所以吃 次的方案数就是
为了方便
第一种情况对于某个次数 方案数就是
然后在第一行选择 次,第二行选择
次。
考虑后面一部分的组合意义,就是从 个数里,有序地选择
个数。那么可以化简为
所以第一种方案数就是 ,可以直接递推
第二种情况的方案数就是
$$
最后一步是换成了枚举 。然后就是组合数的前缀和问题。
$$
#include <bits/stdc++.h> #define int long long using namespace std; const int p = 1e9 + 9; const int N = 1e7 + 5; int x, y; int fac[N], ifac[N], sum[N]; int inv(int x, int base = p - 2) { int res = 1; while (base) { if (base & 1) res = res * x % p; x = x * x % p; base >>= 1; } return res; } int C(int n, int m) { return n >= m && m >= 0 ? fac[n] * ifac[m] % p * ifac[n - m] % p : 0; } void init(int n = N - 1) { fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % p; ifac[n] = inv(fac[n]); for (int i = n - 1; i >= 0; i--) ifac[i] = ifac[i + 1] * (i + 1) % p; assert(fac[10] * ifac[10] % p == 1); } signed main() { scanf("%lld%lld", &x, &y); x -= 2; y -= 2; init(); int ans1 = 0, ans2 = 0; int p2 = 1; sum[0] = 1; for (int i = 1; i <= x + y; ++i) { sum[i] = sum[i - 1] * 2 % p; sum[i] = ((sum[i] - C(i - 1, x) - C(i - 1, y)) % p + p) % p; } for (int i = 0; i <= x + y; ++i) { int res = p2 * fac[i] % p * C(x + y, i) % p; ans1 ^= res; res = p2 * fac[x] % p * fac[y] % p * ifac[x + y - i] % p * sum[x + y - i] % p; ans2 ^= res; p2 = p2 * 2 % p; } printf("%lld %lld\n", ans1, ans2); return 0; }
C
首先有结论若 则先手必胜,否则后手必胜。证明如下:
若 ,则先手无论选择哪一条边
,后手都以被选过一次的
为起点,走向另一个从来没被连过的点
。这样
次就会覆盖所有
个点。下次一定会连接两个被选过的点,从而形成闭环。否则,先手随便走一步,就变成了
的局面了。
#include <bits/stdc++.h> int main() { int n, m; scanf("%d%d", &n, &m); printf("%s\n", (n * m) & 1 ? "NO" : "YES"); return 0; }
D
直接按照题意模拟即可。也可以把每一个特征赋一个权,最后就可以直接比较,减少分类讨论。
#include <bits/stdc++.h> #define int long long using namespace std; signed main() { int T; int a1, a2, b1, b2; int va = 0, vb = 0; cin >> T; for (int i = 1; i <= T; i++) { cin >> a1 >> a2 >> b1 >> b2; if (a1 > a2) swap(a1, a2); if (b1 > b2) swap(b1, b2); if (a1 == 2 && a2 == 8) va += 10000; if (b1 == 2 && b2 == 8) vb += 10000; if (a1 == a2) va += 1000 * a1; if (b1 == b2) vb += 1000 * b1; if (a1 != a2) va += 100 * ((a1 + a2) % 10) + a2; if (b1 != b2) vb += 100 * ((b1 + b2) % 10) + b2; if (va > vb) cout << "first" << endl; if (va < vb) cout << "second" << endl; if (va == vb) cout << "tie" << endl; va = 0; vb = 0; } return 0; }
F
假设某个人在点 ,另外两个人坐标分别是
,则:
$$
发现和球的方程非常类似。
一般方程:
标准方程:
所以球心坐标为
然后计算两个球相交部分体积即可。
#include <bits/stdc++.h> using namespace std; const double pi = acos(-1.0); const double eps = 1e-10; struct point { double x, y, z; } a, b, c, d; double dis(point p, point q) { return sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y) + (p.z - q.z) * (p.z - q.z)); } int t, k1, k2; int main() { scanf("%d", &t); while (t--) { scanf("%lf%lf%lf", &a.x, &a.y, &a.z); scanf("%lf%lf%lf", &b.x, &b.y, &b.z); scanf("%lf%lf%lf", &c.x, &c.y, &c.z); scanf("%lf%lf%lf", &d.x, &d.y, &d.z); scanf("%d%d", &k1, &k2); double d1 = dis(a, b); double d2 = dis(c, d); double r1 = d1 / (k1 * k1 - 1) * k1, r2 = d2 / (k2 * k2 - 1) * k2; point o1, o2; o1.x = (b.x * k1 * k1 - a.x) / (k1 * k1 - 1); o1.y = (b.y * k1 * k1 - a.y) / (k1 * k1 - 1); o1.z = (b.z * k1 * k1 - a.z) / (k1 * k1 - 1); o2.x = (d.x * k2 * k2 - c.x) / (k2 * k2 - 1); o2.y = (d.y * k2 * k2 - c.y) / (k2 * k2 - 1); o2.z = (d.z * k2 * k2 - c.z) / (k2 * k2 - 1); double d = dis(o1, o2); if (d - r1 - r2 > eps) { puts("0.0000"); continue; } if (r1 < r2) { swap(r1, r2); swap(o1, o2); } if (r1 - r2 - d > eps) printf("%.4f\n", 4 * pi / 3 * r2 * r2 * r2); else { double ca = (r1 * r1 + d * d - r2 * r2) / r1 / d / 2, h1 = r1 * (1.0 - ca); double cb = (r2 * r2 + d * d - r1 * r1) / r2 / d / 2, h2 = r2 * (1.0 - cb); printf("%.4f\n", pi / 3 * ((3 * r1 - h1) * h1 * h1 + (3 * r2 - h2) * h2 * h2)); } } return 0; }
G
如果存在两个区间 满足
,即大区间包含小区间,那么这个大区间相当于没有额外贡献,把他提出来单独成一个区间有可能更优。注意,不可能将两个及以上的“大区间”分成一组,否则留下一个,把剩下的分配到相应的小区间所在组会更优。
剩下来的小区间按照左端点排序,可知右端点也会单调递增(不然就会存在包含关系,而已经把把包含关系中的大区间筛去了)。设 为前
组用了前
个线段的最优答案。那么就能得到一个时间复杂度为
的算法。
观察 转移式子
$$
把 提出来后,就发现是经典的可以用单调队列来解决的转移方程。具体的,只需要从小到大枚举
然后
从大到小避免后效性,对于每一个
都维护一个单调队列即可,维护
的最大值,每次把队尾
的
都弹掉,这样总时间复杂度是
.
最后枚举这些小区间分成多少组,剩下的用大区间来补全统计答案。
#include <bits/stdc++.h> using namespace std; const int N = 5e3 + 9; struct node { int l, r; } a[N], b[N]; int dp[N][N], q[N], c[N]; int n, x, y, z, ans, sum, k; bool cmp(node A, node B) { if (A.l == B.l) return A.r > B.r; return A.l < B.l; } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].l, &a[i].r); sort(a + 1, a + 1 + n, cmp); int aaa = 1000000000, top = 0, m = 0; for (int i = n; i >= 1; i--) { if (a[i].r >= aaa) c[++top] = a[i].r - a[i].l; else { aaa = a[i].r; b[++m] = a[i]; } } memset(dp, -1, sizeof(dp)); sort(c + 1, c + 1 + top, greater<int>()); reverse(b + 1, b + 1 + m); dp[0][0] = 0; for (int i = 1; i <= k; i++) { int sl = 1, sr = 0; for (int j = 1; j <= m; j++) { if (dp[i - 1][j - 1] != -1) { while (sl <= sr && dp[i - 1][q[sr]] + b[q[sr] + 1].r <= dp[i - 1][j - 1] + b[j].r) sr--; q[++sr] = j - 1; } while (sl <= sr && b[q[sl] + 1].r <= b[j].l) sl++; if (sl <= sr) dp[i][j] = dp[i - 1][q[sl]] + b[q[sl] + 1].r - b[j].l; } } for (int i = 0; i <= min(k, n); i++) { sum += c[i]; if (dp[k - i][m] != -1) ans = max(ans, dp[k - i][m] + sum); } cout << ans; return 0; }
I
状态记录两只企鹅的位置状态,保存上一步的位置,直接广搜,然后根据路径构造答案。
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 9; const int M = 30; const int inf = 0x3f3f3f3f; char s1[M][M], s2[M][M], sta[N * 10]; char c[4] = { 'D', 'L', 'R', 'U' }; int top; struct node { int x1, y1, x2, y2; }; int d[M][M][M][M], p[M][M][M][M]; node pre[M][M][M][M]; int dir[4][4] = { 1, 0, 1, 0, 0, -1, 0, 1, 0, 1, 0, -1, -1, 0, -1, 0 }; queue<node> q; void bfs() { memset(d, inf, sizeof(d)); d[20][20][20][1] = 0; q.push(node{ 20, 20, 20, 1 }); while (!q.empty()) { node now = q.front(); q.pop(); int x1 = now.x1, y1 = now.y1, x2 = now.x2, y2 = now.y2; int dis = d[x1][y1][x2][y2]; for (int i = 0; i < 4; i++) { int u1 = x1 + dir[i][0], v1 = y1 + dir[i][1], u2 = x2 + dir[i][2], v2 = y2 + dir[i][3]; if (s1[u1][v1] != '.') u1 -= dir[i][0], v1 -= dir[i][1]; if (s2[u2][v2] != '.') u2 -= dir[i][2], v2 -= dir[i][3]; if (dis + 1 < d[u1][v1][u2][v2]) { d[u1][v1][u2][v2] = dis + 1; pre[u1][v1][u2][v2] = node{ x1, y1, x2, y2 }; p[u1][v1][u2][v2] = i; q.push(node{ u1, v1, u2, v2 }); } } } } void solve(node u) { int x1 = u.x1, y1 = u.y1, x2 = u.x2, y2 = u.y2; s1[x1][y1] = s2[x2][y2] = 'A'; if (x1 == 20 && y1 == 20 && x2 == 20 && y2 == 1) return; solve(pre[x1][y1][x2][y2]); sta[top++] = c[p[x1][y1][x2][y2]]; } signed main() { for (int i = 1; i <= 20; i++) scanf("%s%s", s1[i] + 1, s2[i] + 1); bfs(); solve(node{ 1, 20, 1, 1 }); printf("%d\n", top); printf("%s\n", sta); for (int i = 1; i <= 20; i++) printf("%s %s\n", s1[i] + 1, s2[i] + 1); }
J
枚举 ,然后求有多少个大小为
的子集
为
枚举 的倍数,若为
个,方案数就为
。但是这样会包含
是
的倍数的方案,只需要枚举倍数,减掉他就可以了。设求得的答案为
最终的答案就是
很大,但根据扩展欧拉定理:
$a_d
\phi(p)
\rm int128$ ,或者龟速乘。
#include <bits/stdc++.h> #define int long long using namespace std; const int N = 80000; int vis[80005], pr[80005], len; int T, n, k, a[80005], res, b[35]; int p, ans; int mul(int a, int b) { int s = (long double)a / p * b, t = a * b - s * p; return t < 0 ? t + p : t; } int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } int inv(int x, int base = p - 2) { int res = 1; while (base) { if (base & 1) res = mul(res, x); base >>= 1, x = mul(x, x); } return res; } int calc(int N, int m, int t) { for (int i = 1; i <= m; ++i) b[i] = N - i + 1; for (int i = 2; i <= m; ++i) { int now = i; for (int j = 1, g; now > 1 && j <= m; ++j) { g = gcd(b[j], now); b[j] /= g, now /= g; } } int res = inv(t, b[1]); for (int i = 2; i <= m; ++i) res = inv(res, b[i]); return res; } void init() { for (int i = 2; i <= N; ++i) { if (!vis[i]) pr[++len] = i; for (int j = 1; j <= len && i * pr[j] <= N; ++j) { vis[i * pr[j]] = 1; if (i % pr[j] == 0) break; } } } signed main() { init(); scanf("%d", &T); while (T--) { cin >> n >> k >> p; memset(a, 0, sizeof(a)); for (int i = 1, x; i <= n; ++i) { cin >> x; a[x]++; } ans = 1; for (int i = 1; i <= len; ++i) { for (int now = pr[i]; now <= N; now *= pr[i]) { res = 0; for (int j = now; j <= N; j += now) res += a[j]; if (res < k) continue; ans = mul(ans, calc(res, k, pr[i])); } } cout << ans << endl; } return 0; }
K
我们根据单调栈的操作来连边。具体的,如果 令
弹出,那么
表示
。否则
表示
然后做一遍拓扑排序,按照遍历的顺序赋值一定满足题目要求。如果图不是一张 ,说明有环无解,直接输出
。
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> PII; const int N = 1e6 + 9; int n, k; PII p[N]; int a[N], b[N], to[N], d[N], sta[N], q[N]; bool check() { for (int i = 0; i < k; i++) { int pos = p[i].first, val = p[i].second; int pp = p[i + 1].first, vv = p[i + 1].second; if (pos < val) return true; if (i == k - 1) break; if (val + pp - pos < vv) return true; } return false; } void topsort() { int l = 0, r = -1; for (int i = 1; i <= n; i++) if (!d[i]) q[++r] = i; while (l <= r) { int x = q[l++]; d[to[x]]--; if (d[to[x]] == 0) q[++r] = to[x]; } int x = n; for (int i = 0; i <= r; i++) { int pos = q[i]; a[pos] = x--; } for (int i = 1; i <= n; i++) cout << a[i] << " \n"[i == n]; } void solve() { int top = 0; for (int i = 1; i <= n; i++) { if (b[i]) { bool flag = false; while (top > b[i] - 1) top--, flag = true; if (flag) to[sta[top + 1]] = i; } sta[++top] = i; to[i] = sta[top - 1]; } for (int i = 1; i <= n; i++) d[to[i]]++; topsort(); } int main() { cin >> n >> k; for (int i = 0; i < k; i++) { int pos, val; cin >> pos >> val; p[i] = { pos, val }; b[pos] = val; } if (check()) puts("-1"); else solve(); return 0; }
L
注意到两个信息
- 每次只改一个点权值
- 最大权值小于等于
按照 对每个点的度数进行分治,度数大于
的点称为大点,剩下的称为小点。
- 修改小点的时候,可以暴力遍历与它相邻的点,并维护相应的答案。
- 对于大点,先暴力遍历与他相邻的大点,至多有
个点。但是却不能枚举小点。这时候就要利用权值不超过
的性质了。假设更改前点权为
,更改后为
,那么会改变夺冠情况的只有点权位于
的,是冠军的小点,他们会失去冠军。用
表示与点
相邻的小点,权值是
并且是冠军的集合。所以当一个小点
是冠军的时候,不妨枚举相邻的大点
,把
塞进
。当更改的时候就可以暴力遍历
里面的点并修改状态
注意到总 个数是
级别,所以是没有问题的。时间复杂度
#include <bits/stdc++.h> using namespace std; const int N = 200010; int n, m, q, S; vector<int> e[N], big[N]; int walk[N], mx[N]; int ans[N], chmp[N]; vector<pair<int, int>> had[10005]; int main() { cin >> n >> m >> q; S = 2 * sqrt(m) + 1; for (int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); e[u].push_back(v); e[v].push_back(u); } for (int i = 1; i <= n; i++) for (int v : e[i]) if (e[v].size() >= S) big[i].push_back(v); for (int i = 1; i <= q; i++) { int u, w; scanf("%d%d", &u, &w); walk[u] += w; had[walk[u]].push_back({ u, i }); } vector<int> f(n + 1, q), last(n + 1, q), mn(n, q); for (int w = 10000; w >= 1; w--) { for (int i = 0; i < had[w].size(); i++) { int u = had[w][i].first, t = had[w][i].second; for (auto v : big[u]) mn[v] = min(mn[v], t); last[u] = f[u], f[u] = t; } for (int i = 0; i < had[w].size(); i++) { int u = had[w][i].first, t = had[w][i].second; if (e[u].size() < S) { int r = last[u]; for (auto v : e[u]) r = min(r, f[v]); ans[u] += max(0, r - t); } else ans[u] += max(0, min(last[u], mn[u]) - t); } } for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); return 0; }
全部评论
(0) 回帖