T1 [JZOJ1385] 直角三角形
题目描述
二维平面坐标系中有N个位置不同的点。
从N个点选择3个点,问有多少选法使得这3个点形成直角三角形。
数据范围
$3 \leq N \leq 1500$
分析
再一次考场上正解写挂(怎么AC的一堆都是暴力卡常吸氧??)
我们可以先枚举直角顶点,并求出其他点与该点连成的直线的斜率,再按斜率将点排序
众所周知,两条相互垂直的直线斜率之积为 $-1$,所以两条垂直直线斜率一定异号(竖直和水平的直线特殊考虑)
然后把斜率大于 $0$ 的点从大到小加入队列,再从大到小枚举斜率小于等于 $0$ 的点
如果队首斜率与该点斜率之积小于 $-1$,那么它与后面将枚举的点也一定无法形成直角三角形,所以将其出队
如果队首斜率与该点斜率之积大于 $-1$,那么该点与队列后面的点也一定无法形成直角三角形,所以直接判断下一个点
这样时间复杂度就降至 $O(n^2 \, log \, n)$
注意精度,$eps$ 尽量设小一点
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 2000000001.0 #define eps 1e-15 #define N 1505 int n, ans; struct Point { int x, y; double k; } p[N], q[N]; bool cmp(Point a, Point b) { return a.k > b.k; } double getk(Point a, Point b) { if (a.x == b.x) return inf; if (a.y == b.y) return 0; return (double)(b.y - a.y) / (b.x - a.x); } double Abs(double a) { if (a < 0) return -a; return a; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d%d", &p[i].x, &p[i].y); for (int i = 1; i <= n; i++) { for (int j = 1; j < i; j++) q[j] = p[j]; for (int j = i; j < n; j++) q[j] = p[j + 1]; for (int j = 1; j < n; j++) q[j].k = getk(p[i], q[j]); sort(q + 1, q + n, cmp); queue<int> qu; int now = 0, last; while (q[++now].k > 0 && now < n) qu.push(now); for (; now < n; now++) { if (Abs(q[now].k - q[now - 1].k) < eps) { ans += last; continue; } last = 0; while (!qu.empty()) { int temp = qu.front(); if (q[now].k == 0) { if (q[temp].k == inf) qu.pop(), last++; else break; } else { double mul = q[now].k * q[temp].k; if (Abs(mul + 1) < eps) qu.pop(), last++; else if (mul + 1 < 0) qu.pop(); else break; } } ans += last; } } printf("%d", ans); return 0; }
T2 [JZOJ1386] 排序
题目描述
你收到一项对数组进行排序的任务,数组中是1到N个一个排列。你突然想出以下一种特别的排序方法,分为以下N个阶段:
• 阶段1,把数字1通过每次交换相邻两个数移到位置1;
• 阶段2,用同样的方法把N移到位置N;
• 阶段3,把数字2移到位置2处;
• 阶段4,把数字N-1移到位置N-1处;
• 依此类推。换句话说,如果当前阶段为奇数,则把最小的未操作的数移到正确位置上,如果阶段为偶数,则把最大的未操作的数移到正确位置上。
写一个程序,给出初始的排列情况,计算每一阶段交换的次数。
数据范围
对于 $70 \%$ 的数据,$1 \leq N \leq 100$
对于 $100 \%$ 的数据,$1 \leq N \leq 10^5$
分析
刚看到这题,就想到了昨天讲到的拉格朗日计数
如果一个数要向前移,那么移动次数就是前面还没有操作过的数字个数,向后移同理
所以我们可以可以先把所有位置的值都设为 $1$,对于每次操作都只需要查询这个数所在位置前/后的区间和,然后把这个位置的值改为 $0$
用树状数组/线段树维护就可以了
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 100005 int n, p; int pos[N], t[N]; int lowbit(int x) { return x & -x; } void update(int x, int q) { while (x <= n) t[x] += q, x += lowbit(x); } int query(int l, int r) { int ans = 0; l--; while (r) ans += t[r], r -= lowbit(r); while (l) ans -= t[l], l -= lowbit(l); return ans; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &p); pos[p] = i; update(i, 1); } for (int i = 1; i <= n / 2; i++) { printf("%d\n", query(1, pos[i] - 1)); update(pos[i], -1); printf("%d\n", query(pos[n + 1 - i] + 1, n)); update(pos[n + 1 - i], -1); } if (n % 2) printf("0\n"); return 0; }
T3 [JZOJ1388] 自行车赛
题目描述
翠亨村举行一场自行车赛,翠亨村有N个路口(编号1到N),另有M条双向边连接起来。下面有几个定义:
• 路径:由一系列边组成,满足后一条边的起点为前一条边的终点;
• 简单路径:每个路口最多经过一次的路径;
• 环:起点和终点在同一个路口的简单路径。
保证每对路口之间至少有一条路径相连,两个路口之间最多只有一条边直接相连,每条边最多只会出现在一个环中。
你的任务是找出最长的满足以下两个条件的路径:
• 起点可以在任意路口,但终点必须在1号路口;
• 路径可能多次经过同一个路口,但每条边最多只会经过一次。
数据范围
$2 \leq N \leq 10^4$,$1 \leq M \leq 2N-2$
分析
咕咕咕
T4 [JZOJ6275] 小L的数列
题目描述
数据范围
对于 $30 \%$ 的数据,$n \leq 10^4$
对于另外 $20 \%$ 的数据,$b_i=1$,$n \leq 10^6$
对于另外 $20 \%$ 的数据,$f_1...f_{k-1}=1$
对于另外 $20 \%$ 的数据,$k \leq 30$
对于 $100 \%$ 的数据,$1 \leq 200$,$1 \leq n \leq 4 \times 10^7$,$1 \leq b_i,f_i \leq 998244352$
分析
我们以 $k=4$ 为例来说明
因为 $f[1]=f[1]^1f[2]^0f[3]^0f[4]^0$,$f[2]=f[1]^0f[2]^1f[3]^0f[4]^0$,
$f[3]=f[1]^0f[2]^0f[3]^1f[4]^0$,$f[4]=f[1]^0f[2]^0f[3]^0f[4]^1$
可得初始矩阵 $$\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}$$
又因为 $f[5]=f[1]^{b[4]}f[2]^{b[3]}f[3]^{b[2]}f[4]^{b[1]}$
所以转移得到的下一个矩阵为 $$\begin{pmatrix}0&0&0&b[4]\\1&0&0&b[3]\\0&1&0&b[2]\\0&0&1&b[1]\end{pmatrix}$$
由于初始矩阵相当于矩阵中的实数 $1$,所以第二个矩阵就是转移矩阵
然后根据矩阵的结合律,可以对转移矩阵快速幂,最后求出矩阵最后一列 $f$ 的幂之积得到答案
注意在矩阵乘法中,若模数为 $p$($p$ 为质数),则应该对 $p-1$ 取模
这是根据费马小定理 $a^{\varphi(p)}\equiv 1 \, (mod \; p)$
即 $a^{p-1}\equiv 1 \, (mod \; p)$
所以每 $p-1$ 次幂模 $p$ 都为 $1$,对答案不会产生影响
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 205 int n, K; ll b[N], f[N], ans = 1; const int p = 998244353; struct Mat { ll m[N][N]; Mat(){memset(m, 0, sizeof m);} } a; Mat mul(Mat x, Mat y) { Mat z; for (int i = 1; i <= K; i++) for (int j = 1; j <= K; j++) for (int k = 1; k <= K; k++) z.m[i][j] = (z.m[i][j] + x.m[i][k] * y.m[k][j] % (p - 1)) % (p - 1); return z; } Mat mpow(Mat x, int k) { Mat res; for (int i = 1; i <= K; i++) res.m[i][i] = 1; while (k) { if (k & 1) res = mul(res, x); x = mul(x, x); k >>= 1; } return res; } ll qpow(ll x, ll k) { ll res = 1; while (k) { if (k & 1) res = (res * x) % p; x = (x * x) % p; k >>= 1; } return res; } int main() { scanf("%d%d", &n, &K); for (int i = 1; i <= K; i++) scanf("%lld", b + i); for (int i = 1; i <= K; i++) scanf("%lld", f + i); if (n <= K) {printf("%lld\n", f[n]); return 0;} for (int i = 2; i <= K; i++) a.m[i][i - 1] = 1; for (int i = 1; i <= K; i++) a.m[i][K] = b[K + 1 - i]; a = mpow(a, n - K); for (int i = 1; i <= K; i++) ans = ans * qpow(f[i], a.m[i][K]) % p; printf("%lld\n", ans); return 0; }