这是一篇用来记录codeforces中一些有意思的中档题的分类总结。
xor
问题是非常常见的一类问题,但是因为xor
类型的问题比较抽象所以看起来比较困难。
关键点1
一个数的因数的个数只有当是完全平方数的时候才能够是奇数。
首先通过前缀和,算各个结点处的异或和,并且统计此前的前缀和的各个数的数目,让后枚举所有可能的完全平方数,如果说存在某个平方数可以链接一个先前的前缀的话,那么就可以可以找到先前的前缀和数目的的 s u b a r r a y subarray subarray,只需要将当前的前缀和异或上一个平方数,将所有的可以连接上的前面的前缀和数目求和即可,如下公式,枚举的过程中就是就是再找有多少个先前的前缀和异或上中间的一段异或和为平方数的数组就可以得到该数组,这里其实用的是动态规划的思想,求的是以当前的为结尾的异或和因数为奇数的 s u b a r r a y s subarrays subarrays。
推导1
a ⨁ b = c ⟺ a ⨁ b ⨁ b = c ⨁ b ⟺ a = c ⨁ b a\bigoplus{b}=c\iff{a\bigoplus{b}\bigoplus{b}=c\bigoplus{b}}\iff{a=c\bigoplus{b}} a⨁b=c⟺a⨁b⨁b=c⨁b⟺a=c⨁b
推导2
总数 s u m = C n 2 + n sum=C^{2}_{n}+n sum=Cn2+n
时间复杂度
O ( n n ) O(n\sqrt{n}) O(nn)
代码实现:
#include
#define ll long long
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
ll n;
cin >> n;
vector<int> a(n), m(2 * n);//a数组用来存储原数组,m数组是用来统计0~2n统计所有异或和的数组
for (int i = 0; i < n; i++) cin >> a[i];
ll cnt = 0;
int curr = 0;//0异或上任何数都是它本身
m[curr]++;//统计异或和,零也是一个!!
for (int j = 0; j < n; j++) {
curr ^= a[j];//计算异或和
for (ll i = 0; i * i < 2 * n; i++) {//枚举可能的奇数的
if ((curr ^ (i * i)) < 2 * n) {//不能越界
cnt += m[curr ^ (i * i)];//核心,找到所有可能的subarray
}
}
m[curr]++;//统计异或和
}
ll ans = ((n * (n + 1)) / 2) - cnt;//总数减去不符合的
cout << ans << '\n';
}
return 0;
}
gcd
问题是非常常见的问题,各种题目都有,数学知识比较抽象为此分类总结。
题目的内容是让我们取去判断一个数组,能否设定一个 x x x使得任何一对数加上这个是的gcd值为1,也就是 ∃ x , ∀ i , j , g c d ( a i + x , a j + x ) = 1 \exist x,\forall i, j, gcd(a_{i} + x, a_{j} + x) = 1 ∃x,∀i,j,gcd(ai+x,aj+x)=1
关键点1
对于这些数对 ( a i , a j ) (a_{i}, a_{j}) (ai,aj),我们可以分成三种情况,第一种是 a i = a j a_{i} = a_{j} ai=aj,由于 x > 0 且 a > 0 ,必然有 g c d ( a i + x , a j + x ) ⩾ 2 x > 0 且 a > 0,必然有 gcd(a_{i} + x,a_{j}+x)\geqslant2 x>0且a>0,必然有gcd(ai+x,aj+x)⩾2,如果 a i ≠ a j a_{i} \neq a_{j} ai=aj,有两种情况,一是 a i m o d p ≠ a j m o d p a_{i}~mod~p\neq a_{j}~mod~p ai mod p=aj mod p那么只需 x x x足够大便可以保证 g c d ( a i + x , a j + x ) = 1 gcd(a_{i} + x, a_{j} + x) = 1 gcd(ai+x,aj+x)=1,而且是这样的 x x x是连续分布的!!!,可以可以通过中国剩余定理求出x的值,二是 a i m o d p = a j m o d p a_{i}~mod~p=a_{j}~mod~p ai mod p=aj mod p对于变化的 x x x来说即使是足够大也不一定行,有可能 ( a i + x ) m o d p = 0 且 ( a j + x ) m o d p = 0 (a_{i}+x)~mod~p=0且(a_{j}+x)~mod~p=0 (ai+x) mod p=0且(aj+x) mod p=0无论多么大都无法连续存在使得 g c d ( a i + x , a j + x ) = 1 gcd(a_{i} + x, a_{j} + x) = 1 gcd(ai+x,aj+x)=1因为必然存在无穷大的 x x x使得两个数刚好余数为 0 0 0即 g c d ( a i + x , a j + x ) = p 且 p ⩾ 2 gcd(a_{i} + x, a_{j} + x) = p且p\geqslant 2 gcd(ai+x,aj+x)=p且p⩾2。
知识学习
1.剩余系:所谓“剩余系”,就是指对于某一个特定的正整数n,一个整数集中的数模n所得的余数域。
2.完全剩余系:设 m ∈ Z + m\in Z^{+} m∈Z+,若 r 0 , r 1 , . . . r m − 1 r_{0},r_{1},...r_{m-1} r0,r1,...rm−1为 m m m个整数,并且两两模 m m m不同余,则 r 0 , r 1 , . . . r m − 1 r_{0},r_{1},...r_{m-1} r0,r1,...rm−1叫作模 m m m的一个完全剩余系。
关键点2
对于某个模 p p p,我们只需考虑第一和第三类情况,前者简单,对于后者,如果说存在这个模的完全剩余系的话就无法找到这样一个数了,因为不论 x x x多大其余数必然是完全剩余系中的某个数,而且,必然使得某对数的余数均为零,被 p p p整除,所以我们只需要检查相同和是否存在完全剩余系即可,数据较小,直接暴力。
实现
#include
#define ll long long
#define inf 0x3f3f3f3f
#define int ll
using namespace std;
const int N = 105;
int a[N], b[N];
main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
int n;
cin >> n;
int flag = 1;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (a[i] == a[j]) flag = 0;
}//第一类情况
}
if (flag) {
for (int i = 2; i <= n; i++) {//其实只需枚举到100即可,因为最多100对
memset(b, 0, sizeof(b));
for (int j = 0; j < n; j++) {
b[a[j] % i]++;//所有余数统计0~i-1
}
int flag1 = 0;
for (int j = 0; j < i; j++) {
if (b[j] <= 1) {//是否有一对!!
flag1 = 1;//只要缺一对即可
break;
}
}
if (!flag1) {//如果是不缺一对话就找不到
flag = 0;
break;
}
}
}
if (flag) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}