2021,8,23 模擬賽
前言:
\(T1\) 逆元。
\(T2\) 沒過大樣例竟然跑過去了 = =
\(T3\) 沒開 \(long ~long\) 含淚掛 0
期望: \(Soce: 20 + 60 + 10\)
實際: \(Soce: 20 + 100 + 0\)
T1 count
一道 sb 數論題。
題目大意:
問有幾個無序二元組 \((x, y)\) 滿足 \(x y ≡ 1 \pmod p\), \(0 ≤ x < P, 0 ≤ y <P\)。
solution
發現 \(xy \equiv 1(mod~p)\)
\(x\) 有逆元當且僅當 \(gcd(x, p) = 1\) 的時候,並且逆元是唯一的。
設 \(1\sim P - 1\) 中與 \(P\) 互質的數有 \(s\) 個,考慮這 \(s\) 個數與它
們的逆元組成的二元組,這些二元組一定符合條件,那麼只要考
慮去重的問題。
可以發現如果 $xy \equiv 1(modp), x\neq y $那麼 \((x,y)\) 和 \((y,x)\)
一定會在上述 s 個二元組中各出現一次,也就是被多算了一次。
當 \(x = y\) 的時候,\((x, x)\) 這個二元組只會出現一次,設個數為 \(t\)
答案就是 \(\frac{s + t}{2}\)。
\(1\sim P - 1\) 中與 \(P\) 互質的數的個數用尤拉函式來求就好。
code
/* work by:Ariel_ Knowledge: Time: */ #include <bits/stdc++.h> #define ll long long #define rg register using namespace std; int p, Ans; int read(){ int x = 0,f = 1; char c = getchar(); while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();} while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();} return x*f; } int phi(int m) { int res = m, tmp = m; for (int i = 2; i * i <= m; i++) { if (tmp % i == 0) { res = res / i * (i - 1); while(tmp % i == 0) tmp /= i; } } if (tmp > 1) res = res / tmp * (tmp - 1); return res; } int main(){ freopen("count.in", "r", stdin); freopen("count.out", "w", stdout); p = read(); Ans += phi(p); for(int i = 1; i <= p; i++) if(i * i % p == 1) Ans++; Ans = Ans / 2; printf("%d\n", Ans); return 0; }
T2 color (CF547D Mike and Fish)
一道 CF 原題
solution
對於 \(x\) 座標相同的點,把這些點兩兩配對,如果剩下點則不管,然後在每一對點之間連一條邊。
對於 \(y\) 座標相等的點,把這些點兩兩配對,如果剩下點則不管,然後在每一對點之間連一條邊。
最後對得到的圖進行黑白染色可以得到我們的方案了。
證明:如果我們紅藍染色成功,那麼對於每一個 \(x\) 或者 \(y\) 座標來講最多剩一個點,這個點的顏色可以任意選擇。
code
/*
work by:Ariel_
Knowledge:
Time:
*/
#include <bits/stdc++.h>
#define rg register
using namespace std;
const int N = 500010;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int n, x, y, col[N];
map<int, int>lx, ly;
int head[N], E;
char c[2];
struct edge{int v, nxt;}e[N << 1];
void add_edge(int u, int v) {
e[++E] = (edge) {v, head[u]};
head[u] = E;
}
void dfs(int u, int f){
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == f) continue;
if(col[v] == -1) {
col[v] = col[u] ^ 1;
dfs(v, u);
}
}
}
int main(){
//freopen("color.in", "r", stdin);
//freopen("color.out", "w", stdout);
n = read();
memset(col, -1, sizeof col);
c[0] = 'r', c[1] = 'b';
for (int i = 1; i <= n; i++) {
x = read(), y = read();
if(lx[x]) {
add_edge(lx[x], i), add_edge(i, lx[x]);
lx[x] = 0;
} else lx[x] = i;
if (ly[y]) {
add_edge(ly[y], i), add_edge(i, ly[y]);
ly[y] = 0;
} else ly[y] = i;
}
for (int i = 1; i <= n; i++)
if(col[i] == -1) col[i] = 1, dfs(i, 0);
for (int i = 1; i <= n; i++) printf("%c", c[col[i]]);
puts("");
return 0;
}
/*
10
1 1
2 1
2 2
3 2
4 3
4 4
3 3
2 3
2 4
1 3
*/
T3 Sequence
很有價值的一道題。
給出一個 \(n\) 個數的序列,一個區間的價值是區間內最小值乘最大值的積。
\(n\leq 10^5\)
考試的時候一直在考慮每個數對哪些區間有貢獻,把貢獻對應到二維平面上,求解。
寫資料結構寫多了吧 = = 沒注意到 \(n\leq 10^5\)
感覺題解解釋的就很詳細了,所以就直接搬來了。(其實是我懶)
subtask1
\(O(n^2)\) 暴力
subtask2
資料是隨機的。
考慮分治。
如果當前是在考慮區間 \([l,r]\) 的子區間的價值和,設區間 \([l, r]\) 內最大值的下標為 \(mid\) 。
考慮左端點在 \([l, mid]\) 內,右端點在 \([mid,r ]\) 內的區間的價值和。它們內部的最大值是 \(a_{mid}\) 。
對 \([l, mid]\) 做字尾 \(\min\) ,對 \([mid, r]\) 做字首 \(\min\) ,不妨把結果記為\(mn[i]\) 。即 \(l\leq i\leq mid\) 時,\(mn[i] = min_{i = l}^{mid }a_i\) ;\(mid < i \leq r\) 時 \(mn[i] = min_{i = mid}^r a_i\) 。
列舉左端點,由於右半部分的 \(mn\) 是單調的,一定存在一個分界點 \(p\) ,使得 \(j \leq p\) 時,\(mn[j] \geq mn[i]\) ,\(j > p\) 時 \(mn[j] < mn[i]\) 。又由於左半部分的 \(mn\) 也是單調的,所以左端點 \(i\) 移動的時,分界點 \(p\) 的移動方向是不變的。這樣就容易對每個左端點 \(i\) 都求出分界點 \(p\) 了。另外再求出 \(mn\) 的字首和,就能 \(O(1)\) 求出左端點在 \(i\) , 右端點在 \([mid, r]\) 內的區間的價值和了。
然後遞迴計算區間 \([l, mid - 1]\) 和 \([mid + 1, r]\)
時間複雜度 \(O(nlogn)\)
Subtask3
只有 10 種數,因此左端點固定時,最大值至多隻有 10 種,
即右端點在某段區間內的最大值相同,而這種區間最多隻有 10 個。
最小值同理。同時考慮最大值和最小值可以知道,左端點固定時,右端點在某段區間內的最大值和最小值都相同,而這種區間最多隻有 20 個。
移動左端點,用單調棧維護這些區間。複雜度 \(O(n \times 20)\)
Subtask5
跟 Subtask2 的做法類似,每次取 $mid = \lfloor \frac{l + r}{2}\rfloor $,對最大值進行和最小值類似的操作(不妨把字首/字尾 \(\max\) 的結果記為 \(mx[i]\) )。我們可以求出最大值的分界點 \(q\)。除了 \(mn\) 的字首和外,再求出 \(mx\) 的字首和以及 \(mn \times mx\) 的字首和。\(p\) 和 \(q\) 把右半部分劃分成三個區間,對三個區間分別計算。利用求出來的字首和可以 \(O(1)\) 算出右端點在一個區間內的價值和。
然後再遞迴計算左右兩半部分。
code
/*
work by:Ariel_
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const ll mod = 998244353;
const int MAXN = 1e6 + 5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int n, a[MAXN], Ans, mn[MAXN], mx[MAXN], s1[MAXN], s2[MAXN], s3[MAXN];
void work(int l, int r) {
if (l == r) {
Ans = (Ans + 1ll * a[l] * a[r] % mod) % mod;
return ;
}
int mid = l + r >> 1;
mn[mid] = a[mid];
for (int i = mid - 1; i >= l; i--) mn[i] = min(mn[i + 1], a[i]);
mx[mid] = a[mid];
for (int i = mid - 1; i >= l; i--) mx[i] = max(mx[i + 1], a[i]);
mn[mid + 1] = a[mid + 1];
for (int i = mid + 2; i <= r; i++) mn[i] = min(mn[i - 1], a[i]);
mx[mid + 1] = a[mid + 1];
for (int i = mid + 2; i <= r; i++) mx[i] = max(mx[i - 1], a[i]);
s1[mid] = 0;
for (int i = mid + 1; i <= r; i++) s1[i] = (s1[i - 1] + mn[i]) % mod;
s2[mid] = 0;
for (int i = mid + 1; i <= r; i++) s2[i] = (s2[i - 1] + mx[i]) % mod;
s3[mid] = 0;
for (int i = mid + 1; i <= r; i++) s3[i] = (s3[i - 1] + 1ll * mx[i] * mn[i] % mod) % mod;
int p1 = mid + 1, p2 = mid + 1;
for (int i = mid; i >= l; i--) {
while (p1 <= r && mn[p1] > mn[i]) p1++;
while (p2 <= r && mx[p2] < mx[i]) p2++;
if (p1 < p2) {
Ans = (Ans + 1ll * mn[i] * mx[i] % mod * (p1 - 1 - mid) % mod) % mod;
Ans = (Ans + 1ll * mx[i] * (s1[p2 - 1] - s1[p1 - 1] + mod) % mod) % mod;
Ans = (Ans + (s3[r] - s3[p2 - 1] + mod) % mod) % mod;
}
else {
Ans = (Ans + 1ll * mn[i] * mx[i] % mod * (p2 - 1 - mid) % mod) % mod;
Ans = (Ans + 1ll * mn[i] * (s2[p1 - 1] - s2[p2 - 1] + mod) % mod) % mod;
Ans = (Ans + (s3[r] - s3[p1 - 1] + mod) % mod) % mod;
}
}
work(l, mid), work(mid + 1, r);
}
int main(){
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
work(1, n);
printf("%d", Ans);
puts("");
return 0;
}