清北押題班(1)
阿新 • • 發佈:2018-11-01
清北押題衝刺班Text1
T1 Count
問有幾個無序二元組 $ (x , y) $ 滿足 $ xy \equiv 1 (mod P ) $ , $ 0 \leq x < P , 0 \leq y <P $
解題思路:
你沒看錯,這是day1的T1,一道赤裸裸的數學題。
Subtask 1:列舉 $ O(P^2) $ 個二元組,選出符合條件的,再去重;
Subtask 2:可以發現模 $ P $ 意義下,一個數 x 有逆元,當且僅當 $ gcd(x, P) = 1 $ 。並且如果 $ x $ 有逆元,那麼 $ x $ 的逆元只有一個。設 $ 1 $ 到 $ P ? 1 $中與 $ P $ 互質的數有 $ s $ 個,考慮這 $ s $ 個數與它們的逆元組成的二元組,這些二 元組一定符合條件,那麼只要考慮去重的問題可以發現如果 $ xy ≡ 1 (mod P), x \neq y $,那麼 $( x, y) $ 和 $ (y, x) $ 一定會 在上述 $ s $ 個二元組中各出現一次,也就是被多算了一次。設滿足 $ x^2 ≡ 1 (mod P) $ 的 $ x $ 有 $ t $ 個,容易算出答案就是 $ \frac{s+t}{2} $ 。暴力列舉算出 $ s $ 和 $ t $,複雜度 $ O(nlogn) $
Subtask 3: 如果你會做第二個子任務,那麼你離AC這道題也就不遠了。實際上,Subtask 2的做法就是偽正解,他的侷限性就在求出 $ s $ 的速度太慢。所以,我們在Subtask 3要解決的問題實際上就是怎麼快速求 $ s $ 。所以我們引入尤拉函式來解決這個問題。然後問題就轉化成了當 $ P \geq 1$ 時 , 求 $ S = \phi (P) $ 的值的問題。求 $ \phi(P) $ 的值可以線上性篩中解決。
CODE:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define N 50000005 #define M 1000005 using namespace std; int n,prime[M]; int num,phi[N]; bool flag[N]; inline void open_judge() { freopen("count.in","r",stdin); freopen("count.out","w",stdout); } int main() { open_judge(); scanf("%d",&n); flag[1] = 1; phi[1] = 1; for(int i = 2 ; i <= n ; i++) { if(!flag[i]) { prime[++num] = i; phi[i] = i - 1; } for(int j = 1 ; j <= num && prime[j] * i <= n ; j++) { flag[prime[j] * i] = 1; if(i % prime[j] == 0) { phi[i * prime[j]] = phi[i] * prime[j]; break; } phi[i * prime[j]] = phi[i] * phi[prime[j]]; } } for(long long i = 1 ; i <= n ; i++) if(i * i % n == 1) phi[n]++; printf("%d\n",phi[n] / 2); return 0; }
T2 Color
給出平面上 n 個點,你要把每個點染成黑色或白色。要求染完之後,任意一條與座標軸平行的直線上,黑白點數量差的絕對值小於等於 1。
解題思路:
Subtask 1:列舉 $ 2^n $ 種染色方案,並進行判斷。
Subtask $ 2->4 $ : 首先把座標離散化。考慮一個二分圖,一個點 (x, y) 視為在左半邊的第 x 個點和右半邊的第 y 個點之間連了一條邊。問題變成了,給每條邊染色,要求與每個點相連的黑邊和白邊的數量差小於等於 1。由於每個點的度數都是偶數,因此一個聯通塊可以由一條歐拉回路覆蓋。把歐拉回路上相鄰的邊染上不同的顏色,可以發現這樣染,與每個點相連的黑邊和白邊的數量一定相等。
CODE:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Data {
int v;
int p;
}data[500005];
struct Edge {
int to;
int next;
}e[1000005];
int n,m,cnt=1,head[1000005],d[1000005];
int x[500005],y[500005],num,ans[500005];
bool flag[500005];
void add_edge(int u,int v) {
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
inline bool cmp(Data a,Data b) {
return a.v < b.v;
}
void lisan() {
for(int i = 1 ; i <= n ; i++) {
data[i].p = i;
data[i].v = x[i];
}
sort(data+1,data+n+1,cmp);
data[0].v=-1;
for(int i = 1 ; i <= n ; i++) {
if(data[i].v != data[i-1].v) num++;
x[data[i].p] = num;
}
for(int i = 1 ; i <= n ; i++) {
data[i].p = i;
data[i].v = y[i];
}
sort(data+1,data+n+1,cmp);
data[0].v = -1;
for(int i = 1 ; i <= n ; i++) {
if(data[i].v != data[i-1].v) num++;
y[data[i].p] = num;
}
}
void dfs(int node,bool last) {
while(1) {
bool ff = 1;
for(int&hd = head[node] ; hd ; hd = e[hd].next) {
if(flag[hd>>1]) continue;
flag[hd>>1] = 1;
d[node]--;
d[e[hd].to]--;
ans[hd>>1] = !last;
node = e[hd].to;
last = !last;
ff = 0;
break;
}
if(ff) break;
}
}
inline int read() {
char c=getchar();
int t=0;
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9') {t=t*10+c-'0';c=getchar();}
return t;
}
inline void open_judge() {
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
}
int main() {
open_judge();
n = read();
for(int i = 1 ; i <= n ; i++) {
x[i] = read();
y[i] = read();
}
lisan();
for(int i = 1 ; i <= n ; i++) {
d[x[i]]++,d[y[i]]++;
add_edge(x[i],y[i]);
add_edge(y[i],x[i]);
}
for(int i = 1 ; i <= num ; i++) {
if(d[i] & 1) {
d[i]++,d[num+1]++;
add_edge(i , num + 1);
add_edge(num + 1 , i);
}
}
num++;
memset(ans,-1,sizeof(ans));
for(int i = 1 ; i <= num ; i++) {
while(d[i]) dfs(i,0);
}
for(int i = 1 ; i <= n ; i++)
printf("%d ",ans[i]);
printf("\n");
return 0;
}
T3 Sequence
給出一個 $ n (n \leq 5 \times 10^5) $ 個數的序列,一個區間的價值是區間內最小值乘最大值的積。求所有區間價值和,答案對 $ 998244353 $ 取模。
解題思路:
Subtask 1: $ O(n^2) $ 暴力。
Subtask 2: 考慮分治。如果當前是在考慮區間 [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^{mid}{i=1} a_i $ 。 $ mid < i \leq r $ 時 , $ mn[i] = min^r{i=mid}a_i $ 。 列舉左端點 $ 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) $ 。
Subtask 3:只有 10 種數,因此左端點固定時,最大值至多隻有 10 種,即右端點在某段區間內的最大值相同,而這種區間最多隻有 10個,最小值同理。同時考慮最大值和最小值可以知道,左端點固定時,右端點在某段區間內的最大值和最小值都相同,而這種區間最多隻有 20 個。移動左端點,用單調棧維護這些區間,複雜度 $ O(n ∗ 20) $ 。
Subtask 4: $ O(nlog^2 n) $ 的寫掛的正解。。。
Subtask 5(標算): 跟 Subtask2 的做法類似,每次取 $ mid = ⌊ \frac{l+r}{2}⌋ $ ,對最大值進行和最小值類似的操作(不妨把字首/字尾 $ max $ 的結果記為 $ mx[i] $ ),我們可以求出最大值的分界點 $ q $ 。除了 $ mn $ 的字首和外,再求出 $ mx $ 的字首和以及 $ mn \times mx $ 的字首和。$ p $ 和 $ q $ 把右半部分劃分成三個區間,對三個區間分別計算。利用求出來的字首和可以 $ O(1) $ 算出右端點在一個區間內的價值和。然後再遞迴計算左右兩半部分。總時間複雜度是 $ O(nlogn) $ 。
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define MOD 998244353
#define N 500005
using namespace std;
ll n,a[N],ans,maxl[N];
ll maxr[N],minl[N],minr[N];
ll sum[N],sum_minl[N],sum_maxl[N];
void work(int l,int r) {
if(l > r)return;
if(l == r) {
ans = (ans + a[l] * a[r]) % MOD;
return;
}
int mid = (l+r) >> 1;
work(l , mid - 1);
work(mid + 1 , r);
ll s = 0;
maxl[0] = maxr[0] = minl[0] = minr[0] = a[mid];
for(int i = mid + 1 ; i <= r ; i++) {
maxr[i - mid] = max(maxr[i - mid - 1] , a[i]);
minr[i - mid] = min(minr[i - mid - 1] , a[i]);
}
for(int i = mid - 1 ; i >= l ; i--) {
maxl[mid - i] = max(maxl[mid - i - 1] , a[i]);
minl[mid - i] = min(minl[mid - i - 1] , a[i]);
}
sum[mid - l + 1] = 0;
for(int i = l ; i <= mid ; i++) {
sum[mid - i] = (sum[mid - i + 1] + minl[mid - i] * maxl[mid - i]) % MOD;
sum_minl[mid - i] = (sum_minl[mid - i + 1] + minl[mid - i]) % MOD;
sum_maxl[mid - i] = (sum_maxl[mid - i + 1] + maxl[mid - i]) % MOD;
}
int now_minl = 0 , now_maxl = 0;
for(int i = 0 ; i <= r - mid ; i++) {
while(now_minl <= mid - l && minl[now_minl] >= minr[i]) now_minl++;
while(now_maxl <= mid - l && maxl[now_maxl] <= maxr[i]) now_maxl++;
ans = (ans + minr[i] * maxr[i] % MOD * min(now_minl , now_maxl)) % MOD;
ans = ans + sum[max(now_minl , now_maxl)];
if(now_minl < now_maxl)
ans = (ans + (sum_minl[now_minl] - sum_minl[now_maxl] + MOD) * maxr[i]) % MOD;
else ans = (ans + (sum_maxl[now_maxl] - sum_maxl[now_minl] + MOD) * minr[i]) % MOD;
}
}
inline void open_judge() {
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
}
int main() {
open_judge();
scanf("%lld",&n);
for(int i = 1 ; i <= n ; i++)
scanf("%lld",&a[i]);
work(1,n);
printf("%lld\n" , ans % MOD);
return 0;
}