1. 程式人生 > >清北押題班(1)

清北押題班(1)

清北押題衝刺班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;
}