2019-08-20 纪中NOIP模拟B组
T1 [JZOJ3490] 旅游(travel)
题目描述
$ztxz16$ 如愿成为码农之后,整天的生活除了写程序还是写程序,十分苦逼。终于有一天,他意识到自己的生活太过平淡,于是决定外出旅游丰富阅历。
$ztxz16$ 生活的城市有 $N \times M$ 个景点,可以描述成一个 $N \times M$ 的矩形,每个景点有一个坐标 $(x, y) \; (1 \leq x \leq N, 1 \leq y \leq M)$ 以及美观度 $A[x][y]$ 和观赏所需的时间 $B[x][y]$,从一个景点 $(x_1, y_1)$ 走到另一个景点 $(x_2, y_2)$ 需要时间为它们之间的曼哈顿距离:$|x_1 - x_2| + |y_1 - y_2|$。
为了防止审美疲劳,$ztxz16$ 希望观赏的景点的的美观度是严格上升的,由于不想太早回家码代码,$ztxz16$ 希望旅游的总时间尽可能长。
数据范围
对于 $30\%$ 的数据,$1 \leq N,M \leq 50$
对于 $60\%$ 的数据,$1 \leq N,M \leq 300$
对于 $100\%$ 的数据,$1 \leq N,M \leq 1000$,$0 \leq A \leq 10^6$,$0 \leq B \leq 10^9$
分析
数据较水,$O(n^2m^2)$ 可以过,暂没写出 $O(nm)$ 做法
T2 [JZOJ3491] 做梦(dream)
题目描述
$ztxz16$ 旅游归来后十分疲倦,很快就进入了梦中。
在梦中 $ztxz16$ 结婚生子了,他不得不照顾小宝宝。但这实在太无聊了,于是 $ztxz16$ 会在散步。梦中 $ztxz16$ 住在一个类似数轴的街上,数轴上的每个整点是一个街区,每个单位时间内 $ztxz16$ 可以选择向左走一个街区或者向右走一个街区,但如果他离开家超过 $m$ 个单位时间小宝宝会有危险,因此 $ztxz16$ 必须在距离上次在家中不超过 $m$ 个单位时间内回到家中。$n$ 个单位时间后 $ztxz16$ 会醒来,他希望此时正好在家中。
$ztxz16$ 想知道散步过程可能有多少种不同的散步过程。两个散步过程被认为不同,当且仅当存在至少一个单位时刻 $ztxz16$ 6选择的走向不同。
数据范围
对于 $30\%$ 的数据,$2 \leq N \leq 100$,$2 \leq M \leq 100$
对于 $100\%$ 的数据,$2 \leq N \leq 10^9$,$2 \leq M \leq 100$
$N$ 和 $M$ 均为偶数
分析
看到数据,很容易想到矩阵乘法
设 $f[i][j]$ 表示不经过起点走 $i$ 步到达 $j$ 位置的不同路径数
于是可以推出初始矩阵为 $$\begin{bmatrix} 0 & 0 & \cdot\cdot\cdot & 0 & f[\frac{m}{2}]\\ 1 & 0 & \cdot\cdot\cdot & 0 & f[\frac{m}{2}-1]\\ 0 & 1 & \cdot\cdot\cdot & 0 & f[\frac{m}{2}-2]\\ \cdot\cdot\cdot & \cdot\cdot\cdot & \cdot\cdot\cdot & \cdot\cdot\cdot & \cdot\cdot\cdot\\ 0 & 0 & \cdot\cdot\cdot & 0 & f[2]\\ 0 & 0 & \cdot\cdot\cdot & 1 & f[1] \end{bmatrix}$$
然后只需要求出矩阵 $\frac{n}{2}$ 次幂的最后一项就可以了
(关于这样转移的原因,手推一下前几个矩阵找出最后一项的形成规律就好了,而且也会明白为什么 $f$ 数组记录的是不经过起点的路径数)
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <vector> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f int n, m, ans; int f[105][105]; int p = 1e9 + 7; struct Mat { int t[55][55]; Mat() {memset(t, 0, sizeof t);} } x; Mat Mul(Mat a, Mat b) { Mat c; for (int i = 1; i <= m; i++) for (int j = 1; j <= m; j++) for (int k = 1; k <= m; k++) c.t[i][j] = (c.t[i][j] + (ll)a.t[i][k] * b.t[k][j] % p) % p; return c; } Mat Pow(Mat a, int b) { Mat c; for (int i = 1; i <= m; i++) c.t[i][i] = 1; while (b) { if (b & 1) c = Mul(c, a); a = Mul(a, a); b >>= 1; } return c; } int main() { scanf("%d%d", &n, &m); n >>= 1; m >>= 1; f[1][m - 1] = f[1][m + 1] = 1; for (int i = 2; i <= (m << 1); i++) for (int j = 0; j <= (m << 1); j++) { if (j - 1 != m) f[i][j] = (f[i][j] + f[i - 1][j - 1]) % p; if (j + 1 != m) f[i][j] = (f[i][j] + f[i - 1][j + 1]) % p; } for (int i = 2; i <= m; i++) x.t[i][i - 1] = 1; for (int i = 1; i <= m; i++) x.t[m + 1 - i][m] = f[i << 1][m]; x = Pow(x, n); printf("%d", x.t[m][m]); return 0; }
T3 [JZOJ3492] 数数(count)
题目描述
$ztxz16$ 从小立志成为码农,因此一直对数的二进制表示很感兴趣。今天的数学课上,$ztxz16$ 学习了等差数列的相关知识。我们知道,一个等差数列可以用三个数 $A,B,N$ 表示成如下形式:
$B + A, B + 2 \times A, B + 3 \times A, ..., B + N \times A$
$ztxz16$ 想知道对于一个给定的等差数列,把其中每一项用二进制表示后,一共有多少位是 $1$,但他的智商太低无法算出此题,因此寻求你的帮助。
数据范围
对于 $30\%$ 的数据,$1 \leq T \leq 20$,$1 \leq A \leq 10^4$,$1 \leq B \leq 10^{16}$,$1 \leq N \leq 10^3$
对于 $60\%$ 的数据,$1 \leq T \leq 20$,$1 \leq A \leq 10^4$,$1 \leq B \leq 10^{16}$,$1 \leq N \leq 10^9$
对于 $100\%$ 的数据,$1 \leq T \leq 20$,$1 \leq A \leq 10^4$,$1 \leq B \leq 10^{16}$,$1 \leq N \leq 10^{12}$
分析
对于一个二进制数 $x$,它第 $k$ 位上的数为 $\lfloor \frac{x}{2^{k-1}} \rfloor - \lfloor \frac{x}{2^k} \rfloor \times 2$
所以在等差数列的 $n$ 个数中,第 $k$ 位的总贡献为 $\sum_{i=1}^n \lfloor \frac{b+ai}{2^{k-1}} \rfloor - \lfloor \frac{b+ai}{2^k} \rfloor \times 2$
最后答案就是每一位上的贡献之和
对于形如 $f(a,b,c,n)=\sum_{i=0}^n \lfloor \frac{ai+b}{c} \rfloor$ 的式子,我们会使用类欧几里得算法来计算
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f ll T, A, B, N, f[65]; ll calc(ll a, ll b, ll c, ll n) { if (!a) return b / c * (n + 1); if (a < c && b < c) { ll m = (a * n + b) / c; if (!m) return 0; return n * m - calc(c, c - b - 1, a, m - 1); } if (n & 1) return calc(a % c, b % c, c, n) + (n + 1) / 2 * n * (a / c) + (n + 1) * (b / c); return calc(a % c, b % c, c, n) + n / 2 * (n + 1) * (a / c) + (n + 1) * (b / c); } int main() { scanf("%lld", &T); while (T--) { ll ans = 0; scanf("%lld%lld%lld", &A, &B, &N); for (ll i = 1, j = 0; (i >> 1) <= B + A * N; i <<= 1, j++) f[j] = calc(A, B + A, i, N - 1); for (ll i = 1, j = 0; i <= B + A * N; i <<= 1, j++) ans += f[j] - (f[j + 1] << 1); printf("%lld\n", ans); } return 0; }