清北押题班
Posted repulser
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了清北押题班相关的知识,希望对你有一定的参考价值。
清北押题冲刺班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 eq 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
",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("
");
return 0;
}
T3 Sequence
给出一个 $ n (n leq 5 imes 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 imes 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
" , ans % MOD);
return 0;
}
以上是关于清北押题班的主要内容,如果未能解决你的问题,请参考以下文章