2019-08-07 纪中NOIP模拟B组
T1 [JZOJ1385] 直角三角形
题目描述
二维平面坐标系中有 $N$ 个位置不同的点。
从 $N$ 个点选择 $3$ 个点,问有多少选法使得这 $3$ 个点形成直角三角形。
数据范围
$3 \leq N \leq 1500$
分析
再一次考场上正解写挂(怎么AC的一堆都是暴力卡常吸氧??)
我们可以先枚举直角顶点,并求出其他点与该点连成的直线的斜率,再按斜率将点排序
众所周知,两条相互垂直的直线斜率之积为 $-1$,所以两条垂直直线斜率一定异号(竖直和水平的直线特殊考虑)
然后把斜率大于 $0$ 的点从大到小加入队列,再从大到小枚举斜率小于等于 $0$ 的点
如果队首斜率与该点斜率之积小于 $-1$,那么它与后面将枚举的点也一定无法形成直角三角形,所以将其出队
如果队首斜率与该点斜率之积大于 $-1$,那么该点与队列后面的点也一定无法形成直角三角形,所以直接判断下一个点
这样时间复杂度就降至 $O(n^2 \, log \, n)$
注意精度,$eps$ 尽量设小一点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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$
用树状数组/线段树维护就可以了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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$
分析
这大概是这些天来最神仙的题了
虽然说思路理解了以后感觉不算是很难但我肯定是想不到的,但是要码出来并不简单,何况是在考场上
思考后可以发现,这个图是由一些环和桥组成的(相连的环之间一定有桥)
然后我们可以用 $Tarjan$ 判断出每条边是在环上还是在桥上
对于一个环/桥,我们说它悬挂在点 $x$ 上,当且仅当 $x$ 是该环/桥上 $dfn$ 值最小的点
设 $f[x][0]$ 表示点 $x$ 的子图中始于 $x$ 终于 $x$ 的最长路径长度,$f[x][1]$ 表示点 $x$ 的子图中始于任意节点终于 $x$ 的最长路径长度
显然 $f[x][0]$ 就等于悬挂在点 $x$ 上的所有环的长度之和加上环上所有点 $y$ 的 $f[y][0]$ 之和
而对于 $f[x][1]$,有两种情况可能达到最优(在两者中取较大值)
1.先走完悬挂在 $x$ 上的所有环(包括环上所有的点上悬挂的环),即 $f[x][0]$;当有桥悬挂在点 $x$ 上时,就再加上这些桥中走下去得到的路径最长的一条路径长度
2.先走完悬挂在 $x$ 上的所有其他环,然后走到剩下的一个环上的某个结点处进入其子图,由于从 $x$ 在环上走到该结点有顺时针和逆时针两个方向,所以要取两条路中的较长者
这样就可以处理出每个结点上的 $f$ 值,答案就是 $f[1][1]$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <vector> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 10005 int n, m, tot, num, top, cirnum; int to[N << 2], nxt[N << 2], head[N]; int dfn[N], low[N], stack[N], f[N][2]; vector<int> c[N], cir[N], bri[N]; void add(int u, int v) { to[++tot] = v; nxt[tot] = head[u]; head[u] = tot; } void tarjan(int x, int fa) { dfn[x] = low[x] = ++num; stack[++top] = x; for (int i = head[x]; i; i = nxt[i]) { if (to[i] == fa) continue; if (dfn[to[i]]) low[x] = min(low[x], dfn[to[i]]); else { tarjan(to[i], x); if (low[to[i]] < dfn[x]) low[x] = min(low[x], low[to[i]]); else if (low[to[i]] == dfn[x]) { cir[x].push_back(++cirnum); do c[cirnum].push_back(stack[top]); while (to[i] != stack[top--]); } else bri[x].push_back(stack[top--]); } } } int dp(int x, int way) { int &sum = f[x][way]; if (sum >= 0) return sum; sum = 0; int path = 0; for (int i = 0; i < bri[x].size(); i++) path = max(path, dp(bri[x][i], 1) + 1); for (int i = 0; i < cir[x].size(); i++) { vector<int> &now = c[cir[x][i]]; int cirlen = now.size() + 1, all = now.size() - 1; int len1 = 1, len2 = 1, maxlen = 0; for (int j = 0; j < now.size(); j++) { maxlen = max(maxlen, len1 + dp(now[j], 1)); len1 += dp(now[j], 0) + 1; maxlen = max(maxlen, len2 + dp(now[all - j], 1)); len2 += dp(now[all - j], 0) + 1; cirlen += dp(now[j], 0); } sum += cirlen; path = max(path, maxlen - cirlen); } if (way) sum += path; return sum; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); } tarjan(1, 0); memset(f, -1, sizeof f); printf("%d\n", dp(1, 1)); return 0; }
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^{p-1}\equiv 1 \, (mod \; p)$
所以每 $p-1$ 次幂模 $p$ 都为 $1$,对答案不会产生影响
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }