可以证明,只要首先裁剪最外围的 4 4 4 条边,之后无论怎样裁剪,次数都是最少。对于 n n n 行 m m m 列的二维码,至少需要裁剪 n m + 3 nm + 3 nm+3 次,因此答案为 20 × 22 + 3 = 443 20\times 22+3=443 20×22+3=443 。
证明:只需证明裁掉边界后至少还需裁剪 n m − 1 nm-1 nm−1 次。
将棋盘的格子按行优先的顺序从 1 1 1 到 8 8 8 编号,每一个格子有“已放置棋子”和“未放置棋子”两种状态。因此棋盘的状态可用 8 8 8 个二进制位表示。用程序模拟下棋过程即可,答案:LLLV。
#include
using namespace std;
const int MASK = (1 << 8) - 1;
// 棋盘状态为 status 时下一步操作者是否可以获胜?
bool dfs(int status) {
if (status == MASK) {
return true;
}
for (int i = 0; i < 8; ++i) { // 在一个空位上放置一个棋子
if (status & (1 << i)) continue;
if (!dfs(status | (1 << i))) return true;
}
for (int i = 0; i < 3; ++i) { // 在同一行的连续两个空位上各放置一个棋子
if (!(status & (3 << i))) {
if (!dfs(status | (3 << i))) return true;
}
if (!(status & (3 << i + 4))) {
if (!dfs(status | (3 << i + 4))) return true;
}
}
// for (int i = 0; i < 4; ++i) { // 同一列的连续两个空位上各放置一个棋子,不合题意
// if (status & (17 << i)) continue;
// if (!dfs(status | (17 << i))) return true;
// }
return false;
}
int main() {
printf(dfs(1) ? "L" : "V");
printf(dfs(3) ? "L" : "V");
printf(dfs(2) ? "L" : "V");
printf(dfs(6) ? "L" : "V");
return 0;
}
对于正整数 n = p 1 a 1 p 2 a 2 ⋯ p k a k n=p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k} n=p1a1p2a2⋯pkak ,其中 p i p_i pi 为质数且 p 1 < p 2 < ⋯ < p k p_1 < p_2 < \cdots < p_k p1<p2<⋯<pk .
首先找到最小素因子 p 1 p_1 p1 并剔出所有 p 1 p_1 p1 ;然后找到最小素因子 p 2 p_2 p2 并剔出所有 p 2 p_2 p2 ;依此类推.
时间复杂度 O ( n ) O(\sqrt{n}) O(n) ,空间复杂度 O ( 1 ) O(1) O(1) .
#include
using namespace std;
int main() {
long long n;
cin >> n;
int cnt = 0;
for (int i = 2; i <= n / i; ++i) {
if (n % i == 0) ++cnt;
while (n % i == 0) n /= i;
}
if (n > 1) ++cnt;
cout << cnt;
return 0;
}
区间中存在两个数的异或等于 x
,等价于区间中同时存在 t
和 t^x
.
用 d p r dp_r dpr 记录以 r r r 为右端点的“满足要求的短区间”的左端点。也就是说计算 d p r dp_r dpr 使得区间 [ d p r , r ] [dp_r,r] [dpr,r] 满足要求,但 [ d p r + 1 , r ] [dp_r+1,r] [dpr+1,r] 不满足要求。
对于每一个查询 l i , r i l_i,r_i li,ri ,只需判断 l i ≤ d p r i l_i \le dp_{r_i} li≤dpri 是否成立即可。时间复杂度 O ( n ) O(n) O(n) .
当然,本题还可以采用莫队算法解决(这不是本题的正解),下面仅给出莫队算法的实现。
#include
#include
#include
#include
using namespace std;
struct node {
int l, r, i;
bool operator< (const node &t) {
if (r != t.r) return r < t.r;
return l < t.l;
}
};
int main() {
int n, m, x;
scanf("%d%d%d", &n, &m, &x);
vector<int> a(n);
for (auto &p : a) scanf("%d", &p);
vector<node> q(m);
{
int i = 0;
for (auto &t : q) {
scanf("%d%d", &t.l, &t.r);
t.i = i++;
--t.l;
--t.r;
}
}
sort(q.begin(), q.end());
vector<bool> ans(m);
int step = max(sqrt(n), 1.);
for (int i = 0; i < n; i += step) {
int cnt = 0;
vector<int> st(1 << 20);
int L = 0, R = -1;
for (auto &t : q) {
if (t.l < i || t.l >= i + step) continue;
if (R == -1) L = t.l, R = L - 1;
while (R < t.r) {
int y = a[++R];
++st[y];
if (x == 0 && st[y] == 2) ++cnt;
else if (x && st[y^x] && st[y] == 1) ++cnt;
}
while (L < t.l) {
int y = a[L++];
--st[y];
if (x == 0 && st[y] == 1) --cnt;
else if (x && st[y^x] && st[y] == 0) --cnt;
}
while (L > t.l) {
int y = a[--L];
++st[y];
if (x == 0 && st[y] == 2) ++cnt;
else if (x && st[y^x] && st[y] == 1) ++cnt;
}
ans[t.i] = (cnt > 0);
}
}
for (int i = 0; i < m; ++i) {
if (ans[i]) puts("yes");
else puts("no");
}
return 0;
}
做真题的好处在于,可以让自己意识到在这个位置的题目不应该离谱到需要莫队来解。
因为 g c d ( a + k , b + k ) = g c d ( ∣ a − b ∣ , b + k ) gcd(a+k,b+k)=gcd(\left|a-b\right|,b+k) gcd(a+k,b+k)=gcd(∣a−b∣,b+k) ,因此问题转化为寻找最小的正整数 k k k 使得 b + k b+k b+k 是 ∣ a − b ∣ |a-b| ∣a−b∣ 的倍数。
时空复杂度均为 O ( 1 ) O(1) O(1) .
#include
using namespace std;
int main() {
long long a, b, k;
cin >> a >> b;
a = abs(a - b);
a += b / a * a;
k = a - b;
cout << k;
return 0;
}
设随机变量 X i X_i Xi 代表甲壳虫从树根爬到高度 i i i 需要经过的时间( 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n).
随机变量 X 1 X_1 X1 的期望为
E ( X 1 ) = ∑ k = 0 ∞ ( k + 1 ) ⋅ P i k ( 1 − P i ) = 1 1 − P i , E(X_1)=\sum\limits_{k=0}^{\infty}(k+1)\cdot P_i^k(1-P_i)=\dfrac{1}{1-P_i}, E(X1)=k=0∑∞(k+1)⋅Pik(1−Pi)=1−Pi1,
随机变量 X i X_i Xi ( i > 1 i > 1 i>1)的期望为
E ( X i ) = ∑ k = 0 ∞ ( k + 1 ) ( E ( X i − 1 ) + 1 ) ⋅ P i k ( 1 − P i ) = E ( X i − 1 ) + 1 1 − P i . E(X_i)=\sum\limits_{k=0}^{\infty}(k+1)(E(X_{i-1})+1)\cdot P_i^k(1-P_i)=\dfrac{E(X_{i-1})+1}{1-P_i}. E(Xi)=k=0∑∞(k+1)(E(Xi−1)+1)⋅Pik(1−Pi)=1−PiE(Xi−1)+1.
因为 P = 998244353 P=998244353 P=998244353 是一个质数,由费马小定理知, ∀ a ( gcd ( a , P ) = 1 ) \forall a(\gcd(a,P)=1) ∀a(gcd(a,P)=1) ,可求得 a a a 的乘法逆元为 a p − 2 a^{p-2} ap−2 .
时间复杂度 O ( n log n ) O(n\log n) O(nlogn) ,空间复杂度 O ( 1 ) O(1) O(1) .
#include
using namespace std;
long long qpow(long long a, int b, int mod) {
a %= mod;
long long t = 1 % mod;
while (b) {
if (b & 1) t = t * a % mod;
a = a * a % mod;
b >>= 1;
}
return t;
}
int inv(int a, int p) {
return qpow(a, p - 2, p);
}
int main() {
int n;
scanf("%d", &n);
int ex = 0;
const int p = 998244353;
for (int i = 1; i <= n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
x = y - x;
ex = (ex + 1LL) * y % p * inv(x, p) % p;
}
printf("%d\n", ex);
return 0;
}
值得一提的是,当 y − x = P y-x=P y−x=P 时本题无解,比如这组样例:
1
1 998244354
提交本题代码的时候发现使用
INT_MAX
需要#include
,否则会出现编译错误。
对于一个排列 A = ( a 1 , a 2 , ⋯ , a n ) A=(a_1,a_2,\cdots ,a_n) A=(a1,a2,⋯,an) ,必存在一个与之对应的 B = ( b 1 , b 2 , ⋯ , b n B=(b_1,b_2,\cdots ,b_n B=(b1,b2,⋯,bn) ,其中 b i = n + 1 − a i b_i=n+1-a_i bi=n+1−ai ,使得 c i = ∣ { a j ∣ j < i , a j < a i } ∣ = ∣ { b j ∣ j < i , b j > b i } ∣ c_i=\left|\{a_j|j < i,a_j < a_i\}\right|=\left|\{b_j|j < i,b_j > b_i\}\right| ci=∣{aj∣j<i,aj<ai}∣=∣{bj∣j<i,bj>bi}∣ .
所有排列的价值之和为 1 2 ⋅ n ! ⋅ C n 2 \dfrac 12\cdot n!\cdot \mathrm C_n^2 21⋅n!⋅Cn2
#include
using namespace std;
int main() {
int n;
scanf("%d", &n);
const int p = 998244353;
int ans = 1LL * n * (n - 1) / 2 % p;
for (int i = 3; i <= n; ++i) {
ans = 1LL * ans * i % p;
}
printf("%d", ans);
return 0;
}
由于棒从 y y y 轴正向开始顺时针扫描,因此首先将所有点按照顺时针排序。对于时针同时扫过的点,距原点近的点在前。
将排好序后的数组视为一个循环数组。让棒从初始位置出发,每次向后寻找第一个可以被棒扫到(距原点距离不超过棒长)的点。最多寻找 n n n 次之后算法结束。
算法复杂度取决于每次向后寻找下一个点的时间,如果顺序查找,总时间复杂度 O ( n 2 ) O(n^2) O(n2) . 使用线段树维护区间最小值之后可以二分找到下一个点,可使复杂度降为 O ( n log 2 n ) O(n\log^2 n) O(nlog2n) .
#include
#include
#include
#include
#include
using namespace std;
const long long INF = LLONG_MAX;
int gcd(int a, int b) {
while (b) {
int t = a % b;
a = b;
b = t;
}
return a;
}
struct A {
int x, y;
int dx, dy; // (dx,dy) 代表向量 OA 的方向
int z, i;
long long l2; // L2 范数
double cs; // OA 向量与 y 轴正向夹角的 cos 值
A(int x, int y, int z, int i) : x(x), y(y), l2(1LL * x * x + 1LL * y * y), z(z), i(i) {
if (x == 0 && y == 0) {
dx = 0, dy = 1;
cs = 1;
return;
}
int d = abs(gcd(x, y));
dx = x / d, dy = y / d;
cs = dy / sqrt(1. * dx * dx + 1. * dy * dy);
}
bool operator< (const A &t) const {
if (dx == t.dx && dy == t.dy) return l2 < t.l2;
if (dx >= 0) {
return t.dx < 0 || cs > t.cs;
}
else {
return t.dx < 0 && cs < t.cs;
}
}
};
class SegTree {
int n;
vector<long long> tree;
public:
void update(int p, long long x, int L, int R, int id) {
if (L == R) {
tree[id] = x;
return;
}
int m = L + R >> 1;
if (p <= m) update(p, x, L, m, id << 1);
else update(p, x, m + 1, R, id << 1 | 1);
tree[id] = min(tree[id << 1], tree[id << 1 | 1]);
}
SegTree(int n) : n(n) {
tree.resize(n << 2, INF);
}
// 查询 [x, y] 内的最小值
long long query(int x, int y, int L, int R, int id) {
if (x <= L && R <= y) return tree[id];
int m = L + R >> 1;
long long mi = INF;
if (x <= m) mi = min(mi, query(x, y, L, m, id << 1));
if (y > m) mi = min(mi, query(x, y, m + 1, R, id << 1 | 1));
return mi;
}
// 查询 [x, y] 内第一个小于等于 val 的位置,若不存在,返回 -1
int query(int x, int y, long long val) {
if (x > y || query(x, y, 1, n, 1) > val) return -1;
int L = x, R = y;
while (L < R) {
int m = L + R >> 1;
if (query(x, m, 1, n, 1) <= val) R = m;
else L = m + 1;
}
return L;
}
};
int main()
{
// 输入
int n;
long long L;
scanf("%d%lld", &n, &L);
vector<A> a;
for (int i = 0; i < n; ++i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
a.emplace_back(x, y, z, i);
}
sort(a.begin(), a.end());
// for (auto &t : a) cout << t.x << ' ' << t.y << endl;
SegTree st(n);
vector<int> ans(n, -1);
int last = 0;
int r = 0;
// 线段树初始化
{
int i = 0;
for (auto &t : a) {
st.update(++i, t.l2, 1, n, 1);
}
}
// 检查向量 Ox Oy 是否同向
auto check = [&] (A x, A y) -> bool {
return x.dx == y.dx && x.dy == y.dy;
};
// 将数组 A 看作一个循环数组,每次寻找区间 [last + 1, n] U [1, last - 1] 区间内第一个可以扫到的点(即第一个 l2 范数小于等于 L^2 的点)
while (true) {
int x = st.query(last + 1, n, 1LL * L * L);
if (x == -1) {
x = st.query(1, last - 1, 1LL * L * L);
if (x == -1) break;
}
if (last && check(a[last - 1], a[x - 1])) ans[a[x - 1].i] = ans[a[last - 1].i], ++r;
else ans[a[x - 1].i] = ++r;
L += a[x - 1].z;
if (L > 2e9) L = 2e9;
st.update(x, INF, 1, n, 1);
last = x;
}
for (auto x : ans) printf("%d ", x);
return 0;
}
对于正整数 n = p 1 k 1 p 2 k 2 ⋯ p r k r n=p_1^{k_1}p_2^{k_2}\cdots p_r^{k_r} n=p1k1p2k2⋯prkr ,其中 k i ∈ N + k_i \in \mathbb N^+ ki∈N+ 、 p i p_i pi 为质数 且 p 1 < p 2 < ⋯ < p r p_1 < p_2 < \cdots < p_r p1<p2<⋯<pr .
可以证明, n n n 能表示成 x 1 y 1 ⋅ x 2 y 2 x_1^{y_1}\cdot x_2^{y_2} x1y1⋅x2y2 ( x 1 , x 2 , y 1 , y 2 ∈ N + x_1,x_2,y_1,y_2\in \mathbb N^+ x1,x2,y1,y2∈N+ 且 y 1 , y 2 ≥ 2 y_1,y_2 \ge 2 y1,y2≥2)的形式当且仅当 ∀ i ∈ { 1 , 2 , ⋯ , r } , k i ≥ 2 \forall i\in\{1,2,\cdots ,r\},k_i \ge 2 ∀i∈{1,2,⋯,r},ki≥2 .
必要性显然成立,下面证明充分性。若 ∀ i ∈ { 1 , 2 , ⋯ , r } , k i ≥ 2 \forall i\in\{1,2,\cdots ,r\},k_i \ge 2 ∀i∈{1,2,⋯,r},ki≥2,记 l i = { 1 , k i 为奇数 0 , k i 为偶数 , m i = k i − 3 l i 2 l_i=\begin{cases}1,&k_i \text{为奇数}\\ 0,&k_i为偶数\end{cases},m_i=\dfrac{k_i-3l_i}{2} li={1,0,ki为奇数ki为偶数,mi=2ki−3li . 则 n = ∏ i = 1 r p i k i = ∏ i = 1 r p i 2 m i + 3 l i = ( ∏ i = 1 r p i m i ) 2 ( ∏ i = 1 r p i l i ) 3 n=\prod\limits_{i=1}^rp_i^{k_i}=\prod\limits_{i=1}^rp_i^{2m_i+3l_i}=\left(\prod\limits_{i=1}^rp_i^{m_i}\right)^2 \left(\prod\limits_{i=1}^rp_i^{l_i}\right)^3 n=i=1∏rpiki=i=1∏rpi2mi+3li=(i=1∏rpimi)2(i=1∏rpili)3 . 证毕.
可见,只要所有素因子 p i p_i pi 的个数 k i ≥ 2 k_i \ge 2 ki≥2 , n n n 一定可以表示成 x 1 2 ⋅ x 2 3 x_1^2\cdot x_2^3 x12⋅x23 的形式.
若 n n n 可以表示成 x 1 2 ⋅ x 2 3 x_1^2\cdot x_2^3 x12⋅x23 的形式,由题设知 x 1 2 ⋅ x 2 3 ≤ 1 0 18 x_1^2\cdot x_2^3\le 10^{18} x12⋅x23≤1018 , ⇒ \Rightarrow ⇒ min ( x 1 , x 2 ) ≤ 1 0 18 5 = 1 0 3.6 < 4000 \min(x_1,x_2)\le\sqrt[5]{10^{18}}=10^{3.6}\lt 4000 min(x1,x2)≤51018=103.6<4000 . 不难发现,如果除掉 n n n 中所有 4000 4000 4000 以内的素因子,剩下的部分一定是一个平方数或者立方数。
至此算法思路已然明了:
时间复杂度 O ( ( 550 + ε ) T ) O((550+\varepsilon)T) O((550+ε)T) ,其中 ε \varepsilon ε 是计算平方根和立方根的时间.
#include
#include
#include
using namespace std;
/**
* Get all prime numbers between 1 and n, inclusively.
*/
void getPrimes(std::vector<int> &primes, int n) {
std::vector<bool> vis(n + 1);
for (int i = 2; i <= n; ++i) {
if (!vis[i]) primes.push_back(i);
for (int p : primes) {
if (p > n / i) break;
vis[p * i] = true;
if (i % p == 0) break;
}
}
};
inline bool is_square(long long e) {
long long x = round(sqrt(e));
return x * x == e;
}
inline bool is_cubic(long long e) {
long long x = round(pow(e, 1. / 3));
return x * x * x == e;
}
int main()
{
vector<int> primes;
getPrimes(primes, 4e3);
int T;
scanf("%d", &T);
vector<long long> a(T);
for (auto &p : a) scanf("%lld", &p);
for (auto &e : a) {
bool ok = true;
for (auto &p : primes) {
if (e % p) continue;
e /= p;
if (e % p) {ok = false; break;}
do e /= p; while (e % p == 0);
}
ok &= is_square(e) || is_cubic(e);
puts(ok ? "yes" : "no");
}
return 0;
}
个人认为这是本场思维难度最大的一题,一开始真的毫无头绪。
下方代码实现的 s t e p step step 取 n \sqrt n n ,总的时间复杂度为 O ( n log n + ( m + n ) n ) O(n\log n+(m+n)\sqrt n) O(nlogn+(m+n)n) .
#include
#include
#include
#include
using namespace std;
struct node {
int l, r, k, i;
bool operator< (const node &t) const {
return r < t.r;
}
};
int main() {
int n;
scanf("%d", &n);
vector<int> a(n);
for (auto &p : a) scanf("%d", &p);
int m;
scanf("%d", &m);
vector<node> v(m);
{
int i = 0;
for (auto &t : v) {
scanf("%d%d%d", &t.l, &t.r, &t.k);
--t.l, --t.r;
t.i = i++;
}
}
sort(v.begin(), v.end());
vector<int> cnt(n + 1), num(1e5 + 1), ans(m);
int L = v[0].l, R = L - 1;
int step = sqrt(n) + 1;
bool rev = true;
for (int i = 0; i < n; i += step) {
rev ^= 1;
for (int j = 0; j < m; ++j) {
auto &t = !rev ? v[j] : v[m - 1 - j];
if (t.l < i || t.l >= i + step) continue;
while (R < t.r) {
int x = a[++R];
cnt[num[x]] -= 1;
num[x] += 1;
cnt[num[x]] += 1;
}
while (L > t.l) {
int x = a[--L];
cnt[num[x]] -= 1;
num[x] += 1;
cnt[num[x]] += 1;
}
while (R > t.r) {
int x = a[R--];
cnt[num[x]] -= 1;
num[x] -= 1;
cnt[num[x]] += 1;
}
while (L < t.l) {
int x = a[L++];
cnt[num[x]] -= 1;
num[x] -= 1;
cnt[num[x]] += 1;
}
ans[t.i] = cnt[t.k];
}
}
for (int i = 0; i < m; ++i) printf("%d\n", ans[i]);
return 0;
}
num[x]] -= 1;
num[x] += 1;
cnt[num[x]] += 1;
}
while (R > t.r) {
int x = a[R--];
cnt[num[x]] -= 1;
num[x] -= 1;
cnt[num[x]] += 1;
}
while (L < t.l) {
int x = a[L++];
cnt[num[x]] -= 1;
num[x] -= 1;
cnt[num[x]] += 1;
}
ans[t.i] = cnt[t.k];
}
}
for (int i = 0; i < m; ++i) printf("%d\n", ans[i]);
return 0;
}