清北押题班(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;
}

猜你喜欢

转载自www.cnblogs.com/Repulser/p/9891004.html