CCPC-Wannafly Winter Camp - 数论(Div. 1)- 整除理论习题
感谢 tangjz
的精彩讲解以及有趣的题单!
本文中的题意全部来自原 Slideshow。
1. Codeforces - 664A - Complicated GCD(最大公约数性质的应用)
题目链接:https://codeforces.com/contest/664/problem/A
难度: 900 900 900
I promise you all that this should be the easiest problem for most people.
1.1 题意
给定整数
a
,
b
(
1
≤
a
≤
b
≤
1
0
100
)
a,b(1 \le a \le b \le 10^{100})
a,b(1≤a≤b≤10100),求
gcd
(
a
,
a
+
1
,
⋯
b
)
\gcd(a,a+1,\cdots b)
gcd(a,a+1,⋯b)
的值。
1.2 解题过程
当 a = b a=b a=b 时,答案就是 a a a。
否则,注意到相邻数字的 gcd \gcd gcd 为 1 1 1,所以此时答案为 1 1 1。
注意本题需要高精度读入。
时间复杂度: O ( 100 ) O(100) O(100)。
2. Codeforces - 235A - LCM Challenge(最小公倍数性质)
题目链接:https://codeforces.com/contest/235/problem/A
难度: 1600 1600 1600
2.1 题意
给定整数 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1≤n≤106),选出三个不超过 n n n 的正整数 x , y , z x,y,z x,y,z 使得 lcm ( x , y , z ) \text{lcm}(x,y,z) lcm(x,y,z) 最大。
2.2 解题过程
我们需要使用最小公倍数的两个性质:
- lcm ( n , n − 1 ) = n ( n − 1 ) \text{lcm}(n,n-1)=n(n-1) lcm(n,n−1)=n(n−1);
- 若 n n n 为奇数,则 lcm ( n , n − 2 ) = n ( n − 2 ) \text{lcm}(n,n-2)=n(n-2) lcm(n,n−2)=n(n−2),因为若想让 gcd ( n , n − 2 ) > 1 \gcd(n,n-2) > 1 gcd(n,n−2)>1,至少要有因子 2 2 2。
基于此,我们得到如下结论:
-
若 n n n 为奇数,则答案就是 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n−1)(n−2)。
-
若 n n n 为偶数,则显然 lcm ( n , n − 2 ) < n ( n − 2 ) \text{lcm}(n,n-2)<n(n-2) lcm(n,n−2)<n(n−2),我们不能构造 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n−1)(n−2)。
如果构造 n ( n − 1 ) ( n − 3 ) n(n-1)(n-3) n(n−1)(n−3) 呢?这里面显然 n n n 和 n − 1 n-1 n−1 互质, n − 1 n-1 n−1 和 n − 3 n-3 n−3 互质。而 n n n 和 n − 3 n-3 n−3 不互质的条件为 3 ∣ n 3|n 3∣n,因此又分出两种情况:
(1) 若 3 3 3 不能整除 n n n,则答案为 n ( n − 1 ) ( n − 3 ) n(n-1)(n-3) n(n−1)(n−3);
(2) 若 3 3 3 能够整除 n n n,则答案为 ( n − 1 ) ( n − 2 ) ( n − 3 ) (n-1)(n-2)(n-3) (n−1)(n−2)(n−3)。
时间复杂度: O ( 1 ) O(1) O(1)。
2.3 错误点
1 1 1 和 2 2 2 的情况比较特殊,需要特判。
1 1 1 的答案为 1 1 1, 2 2 2 的答案为 2 2 2。
3. Codeforces - 757B - Bash’s Big Day(因数和倍数)
题目链接:https://codeforces.com/contest/757/problem/B
难度: 1400 1400 1400
3.1 题意
给定 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n\le 10^5) n(1≤n≤105) 个正整数 x 1 , x 2 , ⋯ , x n ( 1 ≤ x i ≤ 1 0 5 ) x_1,x_2,\cdots,x_n(1 \le x_i \le 10^5) x1,x2,⋯,xn(1≤xi≤105),选取尽可能多的数字,使得这些数字的最大公约数大于 1 1 1。
如果无解,输出 1 1 1。
3.2 解题过程
维护每个质因子的倍数个数 c n t [ x ] cnt[x] cnt[x],之后最大的 c n t [ x ] cnt[x] cnt[x] 就是答案。
3.3 代码
int s[maxn];
int cnt[maxn];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &s[i]);
if(n == 1)
{
printf("1\n");
return 0;
}
for(int i = 1; i <= n; i++)
{
for(int j = 2; j * j <= s[i]; j++)
{
if(s[i] % j == 0)
{
cnt[j]++;
while(s[i] % j == 0) s[i] /= j;
}
}
if(s[i] > 1) cnt[s[i]]++;
}
int ans = 1;
for(int i = 2; i <= 100000; i++) ans = max(ans, cnt[i]);
printf("%d\n", ans);
return 0;
}
4. Codeforces - 1034A - Enlarge GCD(因数和倍数)
题目链接:https://codeforces.com/contest/1034/problem/A
难度: 1700 1700 1700
4.1 题意
给定 n ( 1 ≤ n ≤ 3 × 1 0 5 ) n(1\le n \le 3 \times10^5) n(1≤n≤3×105) 个正整数 x 1 , x 2 , ⋯ , x n ( 1 ≤ x i ≤ 1.5 × 1 0 7 ) x_1,x_2,\cdots,x_n(1 \le x_i \le 1.5 \times 10^7) x1,x2,⋯,xn(1≤xi≤1.5×107),删掉尽量少的数字使得所有数字的 gcd \gcd gcd 变大,或者确定解不存在。
如果解存在,输出删掉的数字个数。
4.2 解题过程
设 d = gcd ( x 1 , x 2 , ⋯ , x n ) d=\gcd(x_1,x_2,\cdots,x_n) d=gcd(x1,x2,⋯,xn),则将所有的数字都除以 d d d 之后,问题转化为留下尽可能多的数字使得这些数字的 gcd > 1 \gcd > 1 gcd>1。
之后暴力枚举每个质数的倍数,统计每个质因子 x x x 可以整除的数字个数 c n t [ x ] cnt[x] cnt[x]。
则 n − max { c n t [ x ] } n-\max \left\{ cnt[x]\right\} n−max{cnt[x]} 就是答案。
不过我们要特判除以 d d d 之后全 1 1 1 的情况,这种情况下无解。
时间复杂度: O ( 1.5 ⋅ 1 0 7 log ( 1.5 ⋅ 1 0 7 ) ⋅ log ( 1.5 ⋅ 1 0 7 ) ) O(\frac{1.5 \cdot 10^7}{\log(1.5 \cdot 10^7)} \cdot \log(1.5 \cdot 10^7)) O(log(1.5⋅107)1.5⋅107⋅log(1.5⋅107))
4.3 错误点
没有特判特判除以
d
d
d 之后全
1
1
1 的情况,导致 WA11
。
4.4 代码
int a[maxn];
int cnt[50 * maxn], cnt2[50 * maxn];
bool isnotprime[50 * maxn];
ll prime[50 * maxn];
int tot = 0;
void linearsieve(int range)
{
for(int i = 2; i <= range; i++)
{
if(!isnotprime[i]) prime[++tot] = i;
for(int j = 1; j <= tot; j++)
{
if(i * prime[j] > range) break;
isnotprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
int main()
{
int n;
scanf("%d", &n);
int gcd = 0;
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
gcd = __gcd(a[i], gcd);
}
for(int i = 1; i <= n; i++)
{
a[i] /= gcd;
cnt[a[i]]++;
}
if(cnt[1] == n)
{
printf("-1\n");
return 0;
}
linearsieve(15000000);
for(int i = 1; i <= tot; i++)
{
for(int j = prime[i]; j <= 15000000; j += prime[i])
{
cnt2[prime[i]] += cnt[j];
}
}
int ans = 0;
for(int i = 2; i <= 15000000; i++) ans = max(ans, cnt2[i]);
printf("%d\n", n - ans);
return 0;
}
5. Codeforces - 475D - CGCDSSQ(最大公约数性质 + 暴力)
题目链接:https://codeforces.com/contest/475/problem/D
难度: 2000 2000 2000
5.1 题意
给定长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 的序列 a 1 , a 2 , ⋯ , a n ( 1 ≤ a i ≤ 1 0 9 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 10^9) a1,a2,⋯,an(1≤ai≤109),有 q ( 1 ≤ q ≤ 3 ⋅ 1 0 5 ) q(1 \le q \le 3 \cdot 10^5) q(1≤q≤3⋅105) 个询问,每次询问给出一个 x ( 1 ≤ x ≤ 1 0 9 ) x(1 \le x \le 10^9) x(1≤x≤109),问有多少个区间 [ l , r ] [l,r] [l,r] 满足 gcd ( a l , a l + 1 , ⋯ , a r ) = x \gcd(a_l,a_{l+1},\cdots,a_r)=x gcd(al,al+1,⋯,ar)=x。
5.2 解题过程
首先,我们需要知道,右端点固定的所有区间中 gcd \gcd gcd 的种类数只有 O ( log n ) O(\log n) O(logn) 的量级。
基于这个事实,我们可以从左到右枚举右端点坐标 r r r,之后暴力地将 a r a_r ar 与以 a r − 1 a_{r-1} ar−1 为右端点的所有区间取 gcd \gcd gcd 之后统计答案,不要忘记将 a r a_r ar 自身统计到答案中。
维护的话,可以考虑开三个 unordered_map
,
c
n
t
cnt
cnt 统计以
a
r
−
1
a_{r-1}
ar−1 为右端点的所有
gcd
\gcd
gcd 的个数,
c
n
t
2
cnt_2
cnt2 统计以
a
r
a_{r}
ar 为右端点的所有
gcd
\gcd
gcd 的个数,
a
n
s
ans
ans 统计每一种
gcd
\gcd
gcd 的个数。
时间复杂度: O ( n log 1 0 9 ) O(n \log 10^9) O(nlog109)。
5.3 错误点
本题的答案可以达到 long long
量级,如果开 int
的话就爆了。
5.4 代码
ll a[maxn];
unordered_map<ll, ll> cnt, cnt2;
unordered_map<ll, ll> ans;
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int r = 1; r <= n; r++)
{
for(auto element: cnt)
{
ll gcd = __gcd(element.first, a[r]);
cnt2[gcd] += element.second;
ans[gcd] += element.second;
}
cnt = cnt2;
cnt[a[r]]++;
ans[a[r]]++;
cnt2.clear();
}
int q, x;
scanf("%d", &q);
while(q--)
{
scanf("%d", &x);
if(ans.count(x) == 0) puts("0");
else printf("%lld\n", ans[x]);
}
return 0;
}
6. Luogu - P3499 - [POI2010]NAJ-Divine Divisor( O ( n 1 3 ) O(n^\frac{1}{3}) O(n31) 枚举质因子 + 暴力)
题目链接:https://www.luogu.org/problem/P3499
难度:省选/NOI-
6.1 题意
给定 m ( 1 ≤ m ≤ 600 ) m(1 \le m \le 600) m(1≤m≤600) 个数 a 1 , a 2 , ⋯ , a m ( 2 ≤ a i ≤ 1 0 18 ) a_1,a_2,\cdots,a_m(2 \le a_i \le 10^{18}) a1,a2,⋯,am(2≤ai≤1018),定义 n = ∏ i = 1 m a i n=\prod_{i=1}^m a_i n=∏i=1mai,求最大的整数 k k k 使得存在 d > 1 , d ∣ n , d k ∣ n d > 1, d|n, d^k|n d>1,d∣n,dk∣n,并对这个最大的 k k k 计算有多少可能的正整数 d d d。
6.2 解题过程
一个显然的想法是,统计每一个质因子的次数(利用 unordered_map
),再去计算答案。
如果通过传统方法枚举质因子的话,一定会 TLE
。
如果我们 O ( n 1 3 ) O(n^{\frac{1}{3}}) O(n31) 地枚举质因子,即枚举 1 0 6 10^6 106 范围内的质数,将所有数字除以这些质数到不能除为止,在此过程中统计每一种质因子的次数之和,那么最后剩下的数字只剩下了三种形式:
- p p p;
- p 1 p 2 p_1 p_2 p1p2;
- p 2 p^2 p2。
我们先处理 p 2 p^2 p2,这种形式通过二分就可以判掉。
之后处理
p
p
p,直接 Miller-rabin
就可以。
最后处理
p
1
p
2
p_1p_2
p1p2。如果我们暴力枚举剩下的
a
i
a_i
ai 和
a
j
a_j
aj 的话,会发现如果
gcd
(
a
i
,
a
j
)
<
min
(
a
i
,
a
j
)
\gcd(a_i,a_j) < \min(a_i,a_j)
gcd(ai,aj)<min(ai,aj) 的话,则
a
i
=
p
1
p
2
a_i=p_1p_2
ai=p1p2,
a
j
=
p
2
p
3
a_j=p_2p_3
aj=p2p3,即
gcd
(
a
i
,
a
j
)
=
p
2
\gcd(a_i,a_j)=p_2
gcd(ai,aj)=p2,这时我们便可将
p
2
p_2
p2 的贡献加入,并将
a
i
a_i
ai 和
a
j
a_j
aj 都除以
p
2
p_2
p2。进行完这波操作之后,会发现只剩下两种大于
1
1
1 的形式:一种是
p
p
p,跟刚才一样,直接 Miller-rabin
判断即可;还有一种是
p
1
p
2
p_1 p_2
p1p2,但是跟之前不同的是,
p
1
p_1
p1 或
p
2
p_2
p2 都没有单独出现在某一个数字中,因此可以看成一个整体统计起来。
最后,暴力枚举 unordered_map
,计算最大的
k
k
k,并统计其对应的质因子数量
c
n
t
cnt
cnt。
对于这个 k k k,对应的 d d d 的种类数为 2 c n t − 1 2^{cnt}-1 2cnt−1(考虑每个质因子的 k k k 次方选或不选,减 1 1 1 是因为 d > 1 d > 1 d>1)。
注意到这个答案会远远超过 2 64 − 1 2^{64}-1 264−1,因此需要使用高精度来处理。
6.3 错误点
-
最终的答案没有使用高精度来处理,导致爆
long long
。 -
处理完 p 1 p 2 p_1 p_2 p1p2 之后,不要忘记还有残余的 p p p。
-
O ( m 2 ) O(m^2) O(m2) 枚举 a i a_i ai 和 a j a_j aj 时,需要将 i = j i=j i=j 的情况跳过。
-
for (int i = 1; i <= m; i++) { if (a[i] == 1) continue; if (Miller_Rabin(a[i])) { mp[a[i]]++; a[i] = 1; } else { mp2[a[i]]++; a[i] = 1; } }
不要忘记跳过 a [ i ] = 1 a[i]=1 a[i]=1 的情况。
-
最终统计答案时,必须忽略质因子为 1 1 1 的情况(质因子为 1 1 1 不符合数学定义)。
6.4 代码
const int S=30;//随机算法判定次数,S越大,判错概率越小
ull mult_mod(ull a,ull b,ull c)
{
a%=c;
b%=c;
ull ret=0;
while(b)
{
if(b&1){ret+=a;ret%=c;}
a<<=1;
if(a>=c)a%=c;
b>>=1;
}
return ret;
}
ull pow_mod(ull x,ull n,ull mod)
{
if(n==1)return x%mod;
x%=mod;
ull tmp=x;
ull ret=1;
while(n)
{
if(n&1) ret=mult_mod(ret,tmp,mod);
tmp=mult_mod(tmp,tmp,mod);
n>>=1;
}
return ret;
}
bool check(ull a,ull n,ull x,ull t)
{
ull ret=pow_mod(a,x,n);
ull last=ret;
for(ull i=1;i<=t;i++)
{
ret=mult_mod(ret,ret,n);
if(ret==1&&last!=1&&last!=n-1) return true;//合数
last=ret;
}
if(ret!=1) return true;
return false;
}
bool Miller_Rabin(ull n)
{
if(n<2)return false;
if(n==2)return true;
if((n&1)==0) return false;//偶数
ull x=n-1;
ull t=0;
while((x&1)==0){x>>=1;t++;}
for(int i=0;i<S;i++)
{
ull a=rand()%(n-1)+1;//rand()需要stdlib.h头文件
if(check(a,n,x,t))
return false;//合数
}
return true;
}
ll a[maxn], b[maxn];
bool isnotprime[maxn];
ll prime[maxn];
int cnt = 0;
void linearsieve(ll range)
{
for(ll i = 2; i <= range; i++)
{
if(!isnotprime[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt; j++)
{
if(i * prime[j] > range) break;
isnotprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
ll find_root_2(ll x)
{
ll left = 1, right = ll(sqrt(x)) + 55;
while(left <= right)
{
ll mid = left + (right - left) / 2;
if(mid * mid == x) return mid;
else if(mid * mid < x) left = mid + 1;
else right = mid - 1;
}
return -1;
}
unordered_map<ll, int> mp, mp2;
struct bign
{
int len, s[1005];
bign()
{
memset(s, 0, sizeof(s));
len = 1;
}
bign(int num)
{
*this = num;
}
bign(const char* num)
{
*this = num;
}
bign operator =(int num) //直接以整数赋值
{
char s[maxn];
sprintf(s, "%d", num);
*this = s;
return *this;
}
bign operator =(const char* num) //以字符串赋值
{
len = strlen(num);
for(int i = 0; i < len; i++)
s[i] = num[len - i - 1] - '0';
return *this;
}
string str() const //将bign转化成字符串
{
string res = "";
for(int i = 0; i < len; i++)
res = (char) (s[i] + '0') + res;
if(res == "")
res = "0";
return res;
}
bign operator +(const bign& b) const //重载+号运算
{
bign c;
c.len = 0;
for(int i = 0, g = 0; g || i < max(len, b.len); i++)
{
int x = g;
if(i < len) x += s[i];
if(i < b.len) x += b.s[i];
c.s[c.len++] = x % 10;
g = x / 10;
}
return c;
}
void clean() //去掉前到0
{
while(len > 1 && !s[len - 1])
len--;
}
bign operator *(const bign& b) //重载*号运算
{
bign c;
c.len = len + b.len;
for(int i = 0; i < len; i++)
for(int j = 0; j < b.len; j++)
c.s[i + j] += s[i] * b.s[j];
for(int i = 0; i < c.len - 1; i++)
{
c.s[i + 1] += c.s[i] / 10;
c.s[i] %= 10;
}
c.clean();
return c;
}
bign operator -(const bign& b) //重载-号运算
{
bign c;
c.len = 0;
for(int i = 0, g = 0; i < len; i++)
{
int x = s[i] - g;
if(i < b.len)
x -= b.s[i];
if(x >= 0)
g = 0;
else
{
g = 1;
x += 10;
}
c.s[c.len++] = x;
}
c.clean();
return c;
}
bool operator <(const bign& b) const //重载<号运算
{
if(len != b.len)
return len < b.len;
for(int i = len - 1; i >= 0; i--)
if(s[i] != b.s[i])
return s[i] < b.s[i];
return false;
}
bool operator >(const bign& b) const //重载>号运算
{
return b < *this;
}
bool operator <=(const bign& b) //重载<=号运算
{
return !(b > *this);
}
bool operator ==(const bign& b) //重载>=号运算
{
return !(b < *this) && !(*this < b);
}
bign operator +=(const bign& b) //重载+=号运算
{
*this = *this + b;
return *this;
}
};
istream& operator >>(istream &in, bign& x) //重载输入运算符
{
string s;
in >> s;
x = s.c_str();
return in;
}
ostream& operator <<(ostream &out, const bign& x) //重载输出运算符
{
out << x.str();
return out;
}
int main()
{
int m;
linearsieve(1000000);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%lld", &a[i]);
}
memcpy(b, a, sizeof(a));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= cnt; j++) {
ll p = prime[j];
if (a[i] % p) continue;
while (a[i] % p == 0) {
a[i] /= p;
mp[p]++;
}
}
}
for (int i = 1; i <= m; i++) {
ll root = find_root_2(a[i]);
if (root > 1) {
mp[root] += 2;
a[i] = 1;
}
}
for (int i = 1; i <= m; i++) {
if (a[i] == 1) continue;
if (Miller_Rabin(a[i])) {
mp[a[i]]++;
a[i] = 1;
}
}
for (int i = 1; i <= m; i++) {
if (i == 1) continue;
for (int j = 1; j <= m; j++) {
if (i == j) continue;
ll gcd = __gcd(a[i], b[j]);
if (gcd == 1 || gcd == a[i]) continue;
mp[gcd]++;
a[i] /= gcd;
}
}
for (int i = 1; i <= m; i++) {
if (a[i] == 1) continue;
if (Miller_Rabin(a[i])) {
mp[a[i]]++;
a[i] = 1;
} else {
mp2[a[i]]++;
a[i] = 1;
}
}
ll ans1 = 0, ans2 = 0;
for (auto element: mp) {
if (element.first == 1) continue;
if (element.second > ans1) {
ans1 = element.second;
ans2 = 1;
} else if (element.second == ans1) {
ans2++;
}
}
for (auto element: mp2) {
if (element.first == 1) continue;
if (element.second > ans1) {
ans1 = element.second;
ans2 = 2;
} else if (element.second == ans1) {
ans2 += 2;
}
}
bign ans3 = bign(1);
for (int i = 1; i <= ans2; i++) {
ans3 = ans3 * 2;
}
ans3 = ans3 - 1;
printf("%lld\n", ans1);
cout << ans3 << endl;
return 0;
}
7. Codeforces - 1033D - Divisors(因子形式分类讨论 + 暴力)
题目链接:https://codeforces.com/contest/1033/problem/D
难度: 2000 2000 2000
7.1 题意
给定 n ( 1 ≤ n ≤ 500 ) n(1 \le n \le 500) n(1≤n≤500) 个数 a 1 , a 2 , ⋯ , a n ( 1 ≤ a i ≤ 2 ⋅ 1 0 18 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 2 \cdot 10^{18}) a1,a2,⋯,an(1≤ai≤2⋅1018),定义 a = ∏ i = 1 n a i a=\prod_{i=1}^n a_i a=∏i=1nai,求 a a a 的约数个数。
题目保证 a i a_i ai 的约数个数在 3 3 3 到 5 5 5 之间。
7.2 解题过程
我们可以通过 unordered_map
存储每个质因子的幂次。
注意到,根据因子数,每个数的分解只有下面几种情况:
- 因子数为 3 3 3 时,可能是 p 2 p^2 p2 的形式。
- 因子数为 4 4 4 时,可能是 p 3 p^3 p3 或者 p 1 p 2 p_1p_2 p1p2 的形式。
- 因子数为 5 5 5 时,可能是 p 4 p^4 p4 的形式。
因此,我们可以先将
p
2
,
p
3
,
p
4
p^2,p^3,p^4
p2,p3,p4 形式的数字全部变为
1
1
1,在这个过程中将去掉的质因子和次数都存入到 unordered_map
中。
判断幂次可以通过二分实现。
之后,就只剩下了 p 1 p 2 p_1 p_2 p1p2 形式的数字。
首先进行 O ( n 2 ) O(n^2) O(n2) 的遍历, i i i 遍历之前遍历一遍的数字(设该序列为 b b b), j j j 遍历题目给定的所有数字,存在下面几种情况:
- 若 b i = 1 b_i = 1 bi=1,则表明这个数字已经被处理完成,跳过即可。
- 若 gcd ( b i , a j ) = 1 \gcd(b_i,a_j)=1 gcd(bi,aj)=1,说明两个数字没有任何关系,跳过。
- 若 gcd ( b i , a j ) = b i = a j \gcd(b_i,a_j)=b_i=a_j gcd(bi,aj)=bi=aj(注意两个等号若成立则一定同时成立),暂时跳过。
- 其他情况, gcd \gcd gcd 和 b i gcd \frac{b_i}{\gcd} gcdbi 就是我们需要统计的质因子,将它们的次数分别加 2 2 2 即可,之后将 b i b_i bi 置为 1 1 1。
这时剩下的数字,可以把 p 1 p 2 p_1 p_2 p1p2 当成一个整体去统计答案,因为已经不存在存在单独的 p 1 p_1 p1 或者单独的 p 2 p_2 p2 因子的数字了。
我们可以再开一个 unordered_map
统计这种整体的幂次。
对剩下的那些数字,进行一轮与上面类似的遍历,当且仅当
gcd
(
b
i
,
a
j
)
=
b
i
=
a
j
\gcd(b_i,a_j)=b_i=a_j
gcd(bi,aj)=bi=aj 时,在新的 unordered_map
中将
gcd
\gcd
gcd 对应的次数加
2
2
2。
这时还会剩下一些数字,这些数字就是两两互质的了,把他们的次数各自加 1 1 1 即可。
最后遍历两个 unordered_map
,统计答案即可,注意处理第二个 unordered_map
的时候需要按两个因子统计答案(即统计两次答案)。
8. Gym - 100956C - Fraction Factory( O ( n 1 3 ) O(n^\frac{1}{3}) O(n31) 枚举质因子)
题目链接:https://codeforces.com/gym/100956
题目来源:2015-2016 Petrozavodsk Winter Training Camp, SPb SU + SPb AU Contest
8.1 题意
给定 n ( 1 ≤ n ≤ 5000 ) n(1 \le n \le 5000) n(1≤n≤5000) 个整数 a 1 , a 2 , ⋯ , a n ( 1 ≤ a i ≤ 1 0 18 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 10^{18}) a1,a2,⋯,an(1≤ai≤1018) 和 n ( 1 ≤ m ≤ 5000 ) n(1 \le m \le 5000) n(1≤m≤5000) 个整数 b 1 , b 2 , ⋯ , b n ( 1 ≤ b j ≤ 1 0 18 ) b_1,b_2,\cdots,b_n(1 \le b_j \le 10^{18}) b1,b2,⋯,bn(1≤bj≤1018),定义 Q = a 1 ⋅ a 2 ⋯ a n b 1 ⋅ b 2 ⋯ b m = A B Q = \frac{a_1 \cdot a_2 \cdots a_n}{b_1 \cdot b_2 \cdots b_m} = \frac{A}{B} Q=b1⋅b2⋯bma1⋅a2⋯an=BA,其中 A A A 和 B B B 互质。
现在有 k ( 1 ≤ k ≤ 50 ) k(1 \le k \le 50) k(1≤k≤50) 次查询,每次查询给出一个 M ( 2 ≤ M ≤ 1 0 18 ) M(2 \le M \le 10^{18}) M(2≤M≤1018),求一个整数 C ( 0 ≤ C < M ) C(0 \le C < M) C(0≤C<M) 满足 A B ≡ C ( mod M ) \frac{A}{B} \equiv C(\text{mod }M) BA≡C(mod M),或者确定解不存在。
8.2 解题过程
设 A = a 1 ⋅ a 2 ⋯ a n A = a_1 \cdot a_2 \cdots a_n A=a1⋅a2⋯an, B = b 1 ⋅ b 2 ⋯ b m B = b_1 \cdot b_2 \cdots b_m B=b1⋅b2⋯bm。
如果 A A A 和 M M M 互质,并且 B B B 和 M M M 互质,那我们可以直接求解。
但事实是互质不一定成立,因此我们需要考虑将 M M M 分解质因数,之后将 A A A 和 B B B 中 M M M 的因子单独提出来,剩余的部分逆元计算之后,再将这些因子的幂次乘回去。
首先要解决的问题是:处理出 M M M 的全部因子。
根据数据范围,
M
M
M 最大可以达到
1
0
18
10^{18}
1018,所以不可以直接分解(无视 Pollard-rho
)。
考虑先枚举 1 0 6 10^6 106 内的质因子,之后会发现 M M M 只有四种形式:(1) 1 1 1 (2) p p p (3) p 2 p^2 p2 (4) p 1 p 2 p_1 p_2 p1p2。
利用与 1033D 类似的思路处理,之后我们就得到了 M M M 的全部质因子。
然后,对
A
A
A 和
B
B
B 进行试除,将所有
M
M
M 的质因子幂次用一个 unordered_map
统计起来。
对于剩下的 A ′ A' A′ 和 B ′ B' B′,计算 a n s = A ′ ⋅ i n v ( B ′ ) ans = A' \cdot inv(B') ans=A′⋅inv(B′)。
计算逆元时,因为 M M M 不一定为质数,所以需要使用扩展欧几里得。
之后我们需要将刚才处理出的幂次加回来,这部分用快速幂算就可以,如果发现了负的幂次,证明无解(因为逆元不存在)。
需要注意的是,本题中的乘法必须使用
O
(
1
)
O(1)
O(1) 快速乘来进行计算,否则会爆 long long
。
8.3 错误点
进行乘法运算时,没有使用
O
(
1
)
O(1)
O(1) 快速乘,导致了爆 long long
。
8.4 代码
ll a[maxn], b[maxn];
bool isnotprime[maxn];
ll prime[maxn];
int cnt = 0;
unordered_map<ll, int> mp;
vector<ll> ve;
void linearsieve(int range)
{
for(int i = 2; i <= range; i++)
{
if(!isnotprime[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt; j++)
{
if(i * prime[j] > range) break;
isnotprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
inline ll multi(ll x, ll y, ll mod)
{
return (x*y-(ll)(((long double)x*y)/mod)*mod + mod)%mod;
}
long long ex_gcd(long long a, long long b, long long &x, long long &y, ll mod)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
else
{
long long r = ex_gcd(b, a % b, y, x, mod);
y -= x * (a / b);
return r;
}
}
ll solve(ll a, ll b, ll c) //ax = b mod c
{
ll x, y;
ex_gcd(a, c, x, y, c);
ll d = __gcd(a, c);
//x = mod(mod(x, c) + c, c);
x = (x % c + c) % c;
return multi(x, (b / d), c / d);
}
ll find_root_2(ll x)
{
ll left = 1, right = ll(sqrt(x)) + 55;
while(left <= right)
{
ll mid = left + (right - left) / 2;
if(mid * mid == x) return mid;
else if(mid * mid < x) left = mid + 1;
else right = mid - 1;
}
return -1;
}
ll pow_mod(ll a, ll b, ll mod)
{
ll res = 1;
a = a % mod;
while(b)
{
if(b & 1) res = multi(res, a, mod);
b = b >> 1;
a = multi(a, a, mod);
}
return res;
}
int main()
{
int n, m, q;
linearsieve(1000000);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for (int i = 1; i <= m; i++) scanf("%lld", &b[i]);
scanf("%d", &q);
while (q--) {
ll mod;
scanf("%lld", &mod);
mp.clear();
ve.clear();
ll num = mod;
for (int i = 1; i <= cnt; i++) {
ll p = prime[i];
if (num % p) continue;
ve.pb(p);
while (num % p == 0) num /= p;
}
ll root = find_root_2(num);
if (root > 1) {
num = 1;
ve.pb(root);
} else if (num > 1) {
bool isok = false;
for (int i = 1; i <= n; i++) {
ll gcd = __gcd(num, a[i]);
if (gcd == 1) continue;
if (gcd < num) {
ve.pb(gcd);
ve.pb(num / gcd);
num = 1;
isok = true;
break;
}
}
if (!isok) {
for (int i = 1; i <= m; i++) {
ll gcd = __gcd(num, b[i]);
if (gcd == 1) continue;
if (gcd < num) {
ve.pb(gcd);
ve.pb(num / gcd);
num = 1;
isok = true;
break;
}
}
}
}
if (num > 1) ve.pb(num);
sort(ve.begin(), ve.end());
unique(ve.begin(), ve.end());
ll A = 1, B = 1;
for (int i = 1; i <= n; i++) {
num = a[i];
for (auto p: ve) {
if (num % p) continue;
while (num % p == 0) {
num /= p;
mp[p]++;
}
}
A = multi(A, num, mod);
}
for (int i = 1; i <= m; i++) {
num = b[i];
for (auto p: ve) {
if (num % p) continue;
while (num % p == 0) {
num /= p;
mp[p]--;
}
}
B = multi(B, num, mod);
}
ll ans = multi(A, solve(B, 1LL, mod), mod);
bool isok = true;
for (auto element: mp) {
if (element.second < 0) {
isok = false;
break;
}
ans = multi(ans, pow_mod(element.first, element.second, mod), mod);
}
if (isok) printf("%lld\n", ans);
else puts("DIVISION BY ZERO");
}
return 0;
}
9. HDU - 5447 - Good Numbers( O ( n 1 4 ) O(n^\frac{1}{4}) O(n41) 枚举质因子 + 分类讨论)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5447
9.1 题意
对于任意正整数 u u u,定义 f ( u ) f(u) f(u) 是 u u u 的所有质因子组成的集合,如果正整数 u u u 和 v v v 满足 u ∣ v u|v u∣v 且 f ( u ) = f ( v ) f(u)=f(v) f(u)=f(v),那么认为 u u u 对 v v v 来说是友好的。
给定两个正整数 k 1 k_1 k1 和 k 2 k_2 k2 ( 1 ≤ k 1 , k 2 ≤ 1 0 24 1 \le k_1, k_2 \le 10^{24} 1≤k1,k2≤1024),保证 k 1 k_1 k1 和 k 2 k_2 k2 拥有相同的最大质因子, k 1 k_1 k1 的次大质因子不是 k 2 k_2 k2 的质因子, k 2 k_2 k2 的次大质因子不是 k 1 k_1 k1 的质因子。对于 k 1 k_1 k1 和 k 2 k_2 k2,分别求有多少个数对它们来说是友好的。
9.2 解题过程
对于一个数 n = p 1 k 1 p 2 k 2 ⋯ p m k m n=p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m} n=p1k1p2k2⋯pmkm,其友好数的个数为 ∏ i = 1 m k i \prod_{i=1}^m k_i ∏i=1mki。
因为这里的数字是 1 0 24 10^{24} 1024 量级的,所以不能直接分解质因数。
考虑枚举 1 0 6 10^6 106 以内的质因子,对 k 1 k_1 k1 和 k 2 k_2 k2 进行试除,并将质因子的幂次贡献到答案中。
处理完之后,只剩下了以下几种形式(设 p 1 > p 2 > p 3 p_1 > p_2 >p_3 p1>p2>p3):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
1 1 1 | p 1 p_1 p1 | p 1 2 p_1^2 p12 | p 1 3 p_1^3 p13 | p 1 p 2 p_1p_2 p1p2 | p 1 2 p 2 p_1^2 p_2 p12p2 | p 1 p 2 2 p_1 p_2^2 p1p22 | p 1 p 2 p 3 p_1 p_2 p_3 p1p2p3 |
我们只需考虑下面几种形式:
1 | 2 | 3 | 4 |
---|---|---|---|
p 1 2 p_1^2 p12 | p 1 3 p_1^3 p13 | p 1 2 p 2 p_1^2 p_2 p12p2 | p 1 p 2 2 p_1 p_2^2 p1p22 |
保证 k 1 k_1 k1 和 k 2 k_2 k2 拥有相同的最大质因子, k 1 k_1 k1 的次大质因子不是 k 2 k_2 k2 的质因子, k 2 k_2 k2 的次大质因子不是 k 1 k_1 k1 的质因子。
根据这段假设,我们会发现 gcd ( k 1 , k 2 ) \gcd(k_1,k_2) gcd(k1,k2) 仅与 p 1 p_1 p1 有关。
所以我们对 gcd ( k 1 , k 2 ) = d \gcd(k_1,k_2)=d gcd(k1,k2)=d 进行分类讨论:
- d = p 1 3 d=p_1^3 d=p13 时,直接将答案乘上 3 3 3,并将 k 1 k_1 k1 和 k 2 k_2 k2 置为 1 1 1。
- d = p 1 2 d=p_1^2 d=p12 或 p 1 p_1 p1 时,对 k 1 k_1 k1 和 k 2 k_2 k2 不断除以 d d d,如果 d d d 的幂次大于等于 2 2 2,则乘到答案中。
最终残余的数字中,只剩下 p 2 2 p_2^2 p22 这一种情况会对答案产生贡献,枚举之后判断即可。
判断幂次时,需要使用二分。
9.3 错误点
- 不可以
O
(
n
1
3
)
O(n^{\frac{1}{3}})
O(n31) 地枚举质因子,这样会导致
TLE
。 - 二分时边界要设置的相对精确一些,防止爆
__int128
。
9.4 代码
bool isnotprime[maxn];
int prime[maxn];
int cnt = 0;
template <class T>
void read(T &x) {
static char ch;static bool neg;
for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
x=neg?-x:x;
}
void linearsieve(int range)
{
for(int i = 2; i <= range; i++)
{
if(!isnotprime[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt; j++)
{
if(i * prime[j] > range) break;
isnotprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
template <class T>
T sqrt2(T x)
{
T root = sqrt(x);
T left = std::max((T)0, root - 5), right = root + 5;
while(left <= right)
{
T mid = (left + right) / (T)2;
if (mid * mid == x) return mid;
else if(mid * mid < x) left = mid + 1;
else right = mid - 1;
}
return -1;
}
template <class T>
T sqrt3(T x)
{
T root = sqrt(x);
T left = 0, right = root + 5;
while(left <= right)
{
T mid = (left + right) / (T)2;
if (mid * mid * mid == x) return mid;
else if(mid * mid * mid < x) left = mid + 1;
else right = mid - 1;
}
return -1;
}
int main()
{
__int128 k1, k2;
int t;
linearsieve(1000000);
read(t);
while (t--) {
read(k1); read(k2);
ll ans1 = 1, ans2 = 1;
for (int i = 1; i <= cnt; i++) {
__int128 p = prime[i];
if (k1 % p == 0) {
ll tot = 0;
while (k1 % p == 0) {
k1 /= p;
tot++;
}
ans1 *= tot;
}
if (k2 % p == 0) {
ll tot = 0;
while (k2 % p == 0) {
k2 /= p;
tot++;
}
ans2 *= tot;
}
}
__int128 gcd = std::__gcd(k1, k2);
__int128 root2 = sqrt2(gcd);
__int128 root3 = sqrt3(gcd);
if (gcd > 1) {
if (root3 > 1) {
ans1 *= 3;
ans2 *= 3;
while (k1 % root3 == 0) k1 /= root3;
while (k2 % root3 == 0) k2 /= root3;
} else if (root2 > 1) {
ll tot = 0;
while (k1 % root2 == 0) {
k1 /= root2;
tot++;
}
if (tot > 1) ans1 *= tot;
tot = 0;
while (k2 % root2 == 0) {
k2 /= root2;
tot++;
}
if (tot > 1) ans2 *= tot;
} else {
ll tot = 0;
while (k1 % gcd == 0) {
k1 /= gcd;
tot++;
}
if (tot > 1) ans1 *= tot;
tot = 0;
while (k2 % gcd == 0) {
k2 /= gcd;
tot++;
}
if (tot > 1) ans2 *= tot;
}
}
__int128 root2_1 = sqrt2(k1);
__int128 root2_2 = sqrt2(k2);
if (root2_1 > 1) {
ans1 *= 2;
}
if (root2_2 > 1) {
ans2 *= 2;
}
printf("%lld %lld\n", ans1, ans2);
}
return 0;
}
10. UVaLive - 5791 - Candy’s Candy(乱搞)
题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3802
10.1 题意
有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 种糖果,第 i i i 种糖果的数量为 C i ( 1 ≤ C i ≤ 1 0 9 ) C_i(1 \le C_i \le 10^9) Ci(1≤Ci≤109),现在要用两种方式来包装它们:
- 第一种方式要求每包只包含一种糖果;
- 第二种方式要求每包必须含所有种类糖果且每种数量均等。
此外,要求每种包装方式至少使用一次,包装后每包糖果数量相等,严格大于 1 1 1,统计合法方案数。
10.2 解题过程
设包装的尺寸为 s i z e size size,根据第二种包装方式的限制,我们知道 n ∣ s i z e n|size n∣size,因此 n ∣ ∑ s i z e n | \sum size n∣∑size,而 ∑ s i z e = ∑ i C i \sum size = \sum_i C_i ∑size=∑iCi,因此有 n ∣ ∑ i C i n | \sum_i C_i n∣∑iCi。因为每包糖果数量相等,因此必须满足 s i z e ∣ ∑ i C i size|\sum_i C_i size∣∑iCi。一个直观的做法是直接枚举 ∑ i C i ≤ 1 0 14 \sum_i C_i \le 10^{14} ∑iCi≤1014 的因子,但是这样会很慢。
其实根据刚才的分析,会发现 s i z e size size 一定是 n n n 的倍数,如果我们枚举 ∑ i C i n ≤ 1 0 9 \frac{\sum_i C_i}{n} \le 10^9 n∑iCi≤109 的因子,就会快很多。
对于枚举出来的因子 d d d,可计算出当前枚举的 s i z e = d ⋅ n size=d \cdot n size=d⋅n。
之后对于每一种 s i z e size size,计算出方案数后加到总答案中即可。
我们可以将所有的 C i C_i Ci 画成一个柱状图,这时我们会发现, C i C_i Ci 和 C i + 1 C_{i + 1} Ci+1 之间的差值必须使用第一种方式来消除,因此必须满足 s i z e ∣ ∣ C i − C i + 1 ∣ size | |C_i - C_{i + 1}| size∣∣Ci−Ci+1∣。如果考虑所有的 C i C_i Ci,会发现条件等价于 s i z e ∣ gcd ( ∣ C 1 − C 2 ∣ , ∣ C 2 − C 3 ∣ , ⋯ , ∣ C n − 1 − C n ∣ ) size | \gcd(|C_1 - C_{2}|, |C_2 - C_{3}|, \cdots, |C_{n - 1} - C_{n}|) size∣gcd(∣C1−C2∣,∣C2−C3∣,⋯,∣Cn−1−Cn∣),如果不满足,当前答案为 0 0 0。
之后,我们又发现,当我们消除上面的差值之后,所有的 C i C_i Ci 都变得一样,而第二种方式会用一种类似消消乐的方式,即在柱状图的底部以 s i z e n \frac{size}{n} nsize 行为单位消除。如果 k n \frac{k}{n} nk 不可以整除 m i n v a l u e minvalue minvalue,证明无法完全消除下面的公共部分,此时对答案的贡献为 0 0 0。
对于剩余的部分,我们可以在范围内任意地选择按照第一种方式包装的个数 c n t 1 ( c n t 1 > 0 ) cnt_1(cnt_1 > 0) cnt1(cnt1>0)。所以最终最答案的贡献就是 c n t 1 cnt_1 cnt1 的方案数。计算方案数时,显然我们只需考虑 m i n v a l u e = min { C i } minvalue = \min \left\{ C_i \right\} minvalue=min{Ci}。
根据题目要求,每种包装方式至少使用一次,因此我们需要分两种情况讨论,其计算原则都是至少保留一个第二种包装(第一种包装数量为 0 0 0 的情况很显然不会被统计到答案中):
- 若 k ∣ m i n v a l u e k | minvalue k∣minvalue,则方案数为 m i n c v a l u e k − 1 \frac{mincvalue}{k} - 1 kmincvalue−1;
- 否则方案数为 ⌊ m i n c v a l u e k ⌋ \lfloor \frac{mincvalue}{k} \rfloor ⌊kmincvalue⌋。
最终的答案为 ∑ \sum ∑ 方案数。
10.3 代码
int n;
ll gcd;
ll c[maxn];
ll get(ll k) {
ll minvalue = 0x3f3f3f3f3f3f3f3f;
if (k % n) return 0;
for (int i = 1; i <= n; i++) {
//if (c[i] % (k / n)) return 0;
minvalue = min(minvalue, c[i]);
}
if (minvalue % (k / n)) return 0;
if (gcd % k) return 0;
if (minvalue % k == 0) return minvalue / k - 1;
else return minvalue / k;
}
int main()
{
while (~scanf("%d", &n) && n) {
ll sum = 0;
gcd = 0;
for (int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
sum += c[i];
if (i > 1) gcd = __gcd(gcd, abs(c[i] - c[i - 1]));
}
if (sum % n)
{
puts("0");
continue;
}
sum /= n;
ll ans = 0;
for (ll i = 1; i * i <= sum; i++) {
if (sum % i) continue;
if (i * 1LL * n >= 2) ans += get(i * 1LL * n);
if (i * i != sum && sum / i * 1LL * n >= 2) ans += get(sum / i * 1LL * n);
}
printf("%lld\n", ans);
}
return 0;
}
11. Codeforces - 963C - Cutting Rectangle(乱搞)
题目链接:https://codeforces.com/contest/963/problem/C
难度: 2600 2600 2600
11.1 题意
对于长度为 A A A,宽度为 B B B 的矩形,在其上沿着平行矩形边界的直线切若干刀(要切就切到底),可以形成一系列小的矩形,给出 n ( 1 l e n ≤ 2 ⋅ 1 0 5 ) n(1\ le n \le 2 \cdot 10^5) n(1 len≤2⋅105) 种小矩形的长 w w w、宽 h h h 和数量 c c c ( 1 ≤ w , h , c ≤ 1 0 12 1 \le w,h,c \le 10^{12} 1≤w,h,c≤1012),求有多少种 A , B A,B A,B 能够切出这些小矩形。
11.2 解题过程
设 d = gcd ( c 1 , c 2 , ⋯ , c n ) d=\gcd(c_1,c_2,\cdots,c_n) d=gcd(c1,c2,⋯,cn),则我们发现,问题可以转化为先构造一个最小的矩形,再按行按列进行复制。在这个最小的矩形中,每种小矩形的数量为 c i d \frac{c_i}{d} dci。如果这个最小的矩形能够成功构造的话,答案就是 d d d 的因子数,否则答案为 0 0 0。
现在只剩下了如何 check 这个最小的矩形能否构造成功。
如果我们从行(长)优先的角度考虑,会发现只有
w
w
w 相同的矩形才能够放在一行,而在每一行,每一种
h
h
h 都必须出现过且比例相当。这便成为了判定标准,实际实现时,可以采用 unordered_map
套 vector
进行处理,具体处理过程可以参考代码。
时间复杂度: O ( n log 1 0 12 + 1 0 12 ) O(n \log 10^{12} + \sqrt {10^{12}}) O(nlog1012+1012)。
unordered_map<ll, vector<pll>> mp;
ll w[maxn], h[maxn], c[maxn];
vector<pll> standard;
bool check() {
for (auto &element: mp) {
if (element.second.size() != standard.size()) return false;
int n = standard.size();
ll d = 0;
for (int i = 0; i < n; i++) {
d = __gcd(d, element.second[i].second);
}
for (int i = 0; i < n; i++) {
element.second[i].second /= d;
if (element.second[i] != standard[i]) return false;
}
}
return true;
}
int main()
{
int n;
scanf("%d", &n);
ll gcd = 0;
for (int i = 1; i <= n; i++) {
scanf("%lld%lld%lld", &w[i], &h[i], &c[i]);
mp[w[i]].push_back(pll(h[i], c[i]));
gcd = __gcd(gcd, c[i]);
}
for (auto &element: mp) {
for (auto &x: element.second) {
x.second /= gcd;
}
sort(element.second.begin(), element.second.end());
}
standard = mp.begin()->second;
ll d = 0;
for (auto &x: standard) {
d = __gcd(d, x.second);
}
for (auto &x: standard) {
x.second /= d;
}
if (!check()) return 0 * puts("0");
ll ans = 0;
for (ll i = 1; i * i <= gcd; i++) {
if (gcd % i) continue;
ans++;
if (i * i != gcd) ans++;
}
printf("%lld\n", ans);
return 0;
}
12. Codeforces - 512B - Fox And Jumping(状压 dp)
题目链接:https://codeforces.com/contest/512/problem/B
难度: 2100 2100 2100
12.1 题意
有 n ( 1 ≤ n ≤ 300 ) n(1 \le n\le 300) n(1≤n≤300) 种卡片,第 i i i 种面值为 c i ( 1 ≤ c i ≤ 1 0 5 ) c_i(1 \le c_i \le 10^5) ci(1≤ci≤105),属性为 l i ( 1 ≤ l i ≤ 1 0 9 ) l_i(1 \le l_i \le 10^9) li(1≤li≤109),买进卡片 i i i 后可以在数轴上任意向左右移动 l i l_i li 步任意次,花费最小的代价使得从 x = 0 x=0 x=0 的位置可以到达所有 x ∈ Z x \in \textbf{Z} x∈Z 的位置,或者确定解不存在。
12.2 解题过程
根据斐蜀定理,从 x = 0 x=0 x=0 的位置可以到达所有 x ∈ Z x \in \textbf{Z} x∈Z 的位置等价于 gcd ( l i ) = 1 \gcd(l_i)=1 gcd(li)=1。
所以问题转化为选取总花费最小的卡片,使得这些卡片属性的最大公约数为 1 1 1。
如果所有卡片属性的最大公约数大于 1 1 1 的话,表明问题无解。
注意到 2 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ 11 ⋅ 13 ⋅ 17 ⋅ 19 ⋅ 23 ≤ 1 0 9 2 \cdot 3 \cdot 5 \cdot 7 \cdot 11 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \le 10^9 2⋅3⋅5⋅7⋅11⋅13⋅17⋅19⋅23≤109, 2 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ 11 ⋅ 13 ⋅ 17 ⋅ 19 ⋅ 23 ⋅ 29 > 1 0 9 2 \cdot 3 \cdot 5 \cdot 7 \cdot 11 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \cdot 29 > 10^9 2⋅3⋅5⋅7⋅11⋅13⋅17⋅19⋅23⋅29>109。
因此 1 0 9 10^9 109 以内数字的质因子数量最多只有 9 9 9 个。
计算以每一张卡片为起点(即这张卡片为必选的条件下),可以达到的最小花费。
因此枚举起始卡牌,之后计算其质因子集合。定义质因子状态 S S S 为一串二进制数,当前位 i i i 为 1 1 1 表明存在第 i i i 个质因子。
然后进行状压 dp,定义 d p [ S ] dp[S] dp[S] 为实现质因子状态为 S S S 的最小花费,则起始状态为 d p [ 11 ⋯ 1 ] dp[11\cdots 1] dp[11⋯1],目标状态为 d p [ 00 ⋯ 0 ] dp[00\cdots 0] dp[00⋯0]。在当前起点下,预处理出每个数字的质因子情况(只考虑起点的质因子),用二进制的形式存储。之后进行状压 dp,并更新答案的最小值。
时间复杂度: O ( 2 9 ⋅ n 2 ) O(2 ^ 9 \cdot n^2) O(29⋅n2)。
12.3 代码
ll l[maxn], c[maxn];
ll dp[1 << 10];
int mask[maxn];
vector<ll> ve;
int main()
{
int n;
scanf("%d", &n);
ll gcd = 0;
for (int i = 1; i <= n; i++) {
scanf("%lld", &l[i]);
gcd = __gcd(gcd, l[i]);
}
for (int i = 1; i <= n; i++) scanf("%lld", &c[i]);
if (gcd > 1) {
puts("-1");
return 0;
}
ll ans = 0x3f3f3f3f3f3f3f3f;
for (int i = 1; i <= n; i++) {
ve.clear();
memset(dp, 0x3f, sizeof(dp));
memset(mask, 0, sizeof(mask));
ll num = l[i];
for (int j = 2; j * j <= num; j++) {
if (num % j) continue;
ve.pb(j);
while (num % j == 0) num /= j;
}
if (num > 1) ve.pb(num);
int cnt = ve.size();
dp[(1 << cnt) - 1] = c[i];
for (int j = 1; j <= n; j++) {
int bitmask = 0;
for (int k = 0; k < cnt; k++) {
if (l[j] % ve[k] == 0) bitmask |= (1 << k);
}
mask[j] = bitmask;
}
for (int j = (1 << cnt) - 1; j >= 0; j--) {
for (int k = 1; k <= n; k++) {
if (i == k) continue;
dp[j & mask[k]] = min(dp[j & mask[k]], dp[j] + c[k]);
}
}
ans = min(ans, dp[0]);
}
printf("%lld\n", ans);
return 0;
}
13. Codeforces - 225E - Unsolvable(推导结论)
题目链接:https://codeforces.com/contest/225/problem/E
难度: 2100 2100 2100
13.1 题意
找出最小的 n ( 1 ≤ n ≤ 40 ) n(1 \le n \le 40) n(1≤n≤40) 个正整数 z z z,使得 z = ⌊ x 2 ⌋ + y + x y z = \lfloor \frac{x}{2} \rfloor + y + xy z=⌊2x⌋+y+xy 不存在 x x x 和 y y y 均为正整数的解,按照 z z z 升序输出每个 ( z mod ( 1 0 9 + 7 ) ) (z \text{ mod } (10^9 + 7)) (z mod (109+7))。
13.2 解题过程
看到式子 z = ⌊ x 2 ⌋ + y + x y z = \lfloor \frac{x}{2} \rfloor + y + xy z=⌊2x⌋+y+xy,要想到根据 x x x 的奇偶进行分类讨论。
-
x = 2 k x=2k x=2k 时,
z = k + y + k y = k + y + 2 k y 2 z + 1 = 2 k + 2 y + 4 k y + 1 = ( 2 k + 1 ) ( 2 y + 1 ) \begin{aligned} z&=k+y+ky\\ &=k+y+2ky\\ 2z+1&=2k+2y+4ky+1\\ &=(2k+1)(2y+1) \end{aligned} z2z+1=k+y+ky=k+y+2ky=2k+2y+4ky+1=(2k+1)(2y+1) -
x = 2 k + 1 x=2k+1 x=2k+1 时,
z = k + y + x y = k + y + ( 2 k + 1 ) y = k + 2 ( k + 1 ) y z + 1 = k + 1 + 2 ( k + 1 ) y = ( k + 1 ) ( 2 y + 1 ) \begin{aligned} z&=k+y+xy\\ &=k+y+(2k+1)y\\ &=k+2(k+1)y\\ z+1&=k+1+2(k+1)y\\ &=(k+1)(2y+1) \end{aligned} zz+1=k+y+xy=k+y+(2k+1)y=k+2(k+1)y=k+1+2(k+1)y=(k+1)(2y+1)
观察 z + 1 z+1 z+1 的表达式,可以发现为保证 y y y 没有正整数解,必须保证 y = 0 y=0 y=0,也就是必须保证 z + 1 z+1 z+1 没有奇质因子,即 z + 1 z+1 z+1 只能含有 2 2 2 这一质因子。因此 z + 1 = 2 t z+1=2^t z+1=2t,即 z = 2 t − 1 z=2^t-1 z=2t−1。
将 z z z 代入到 2 z + 1 2z+1 2z+1 中,得 2 z + 1 = 2 t + 1 − 2 + 1 = 2 t + 1 − 1 2z+1 = 2^{t+1}-2+1=2^{t+1}-1 2z+1=2t+1−2+1=2t+1−1。
观察到 2 z + 1 2z+1 2z+1 为两个奇数的乘积,为了保证其一无正整数解,必须保证 2 z + 1 2z+1 2z+1 为质数。
因此会发现 2 z + 1 2z+1 2z+1 其实是梅森素数。
设 f ( n ) f(n) f(n) 为第 n n n 个答案,则有
f
(
n
)
=
z
n
=
2
m
e
r
s
e
n
n
e
[
n
−
1
]
−
1
−
1
f(n)=z_n=2^{mersenne[n - 1] - 1}-1
f(n)=zn=2mersenne[n−1]−1−1
m
e
r
s
e
n
n
e
mersenne
mersenne 数组可以通过 OEIS 得到:
ll mersenne[] = {2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609};
13.3 代码
ll mersenne[] = {2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609};
ll pow_mod(ll a, ll b)
{
ll res = 1;
a = a % Mod;
while(b)
{
if(b & 1) res = (res * a) % Mod;
b = b >> 1;
a = (a * a) % Mod;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
printf("%lld\n", (pow_mod(2LL, mersenne[n - 1] - 1) - 1 + Mod) % Mod);
return 0;
}
14. HDU - 5943 - Kingdom of Obsession(质数密度的应用 + 二分图匹配)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5943
题目来源:2016 年中国大学生程序设计竞赛(杭州)
14.1 题意
给出整数 n ( 1 ≤ n ≤ 1 0 9 ) n(1 \le n\le 10^9) n(1≤n≤109) 和 s ( 0 ≤ s ≤ 1 0 9 ) s(0 \le s \le 10^9) s(0≤s≤109),将所有 x ∈ { s + 1 , s + 2 , ⋯ , s + n } x \in \left\{ s+1,s+2,\cdots,s+n \right\} x∈{s+1,s+2,⋯,s+n} 和 y ∈ { 1 , 2 , ⋯ , n } y \in \left\{ 1,2,\cdots,n \right\} y∈{1,2,⋯,n} 两两配对,只有满足 y ∣ x y|x y∣x 的才能配对,问是否存在可行解。
14.2 解题过程
先处理 { s + 1 , s + 2 , ⋯ , s + n } \left\{ s+1,s+2,\cdots,s+n \right\} {s+1,s+2,⋯,s+n} 和 { 1 , 2 , ⋯ , n } \left\{ 1,2,\cdots,n \right\} {1,2,⋯,n} 的交集,这部分可以直接配对,直接去掉即可。
之后,会发现 1 1 1 只能和 1 1 1(这种情况就是交集,已经处理过) 或质数进行配对。
因此,如果 { s + 1 , s + 2 , ⋯ , s + n } \left\{ s+1,s+2,\cdots,s+n \right\} {s+1,s+2,⋯,s+n} 出现了超过 1 1 1 个质数,便是无解。
根据质数分布的规律,如果处理完交集之后的 $n > $ 最大质数间隔,则一定无解。
这个间隔设置到 300 300 300 就够了。
若 n < 300 n < 300 n<300,直接跑二分图匹配即可。
14.3 代码
int n, cnt;
int head[maxn];
bool vis[605];
int match[maxn];
struct edge
{
int v, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= 2 * n; i++)
{
head[i] = -1;
vis[i] = false;
match[i] = 0;
}
cnt = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
bool dfs(int id)
{
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(!vis[v])
{
vis[v] = true;
if(!match[v] || dfs(match[v]))
{
match[v] = id;
return true;
}
}
}
return false;
}
int solve()
{
int res = 0;
for(int i = 1; i <= n; i++)
{
memset(vis, false, sizeof(vis));
if(dfs(i)) res++;
}
return res;
}
int main()
{
int t, s;
int kase = 0;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &s);
int ins = max(0, n - s);
n -= ins;
s += ins;
if(n > 300)
{
printf("Case #%d: No\n", ++kase);
continue;
}
init();
for(int i = 1; i <= n; i++)
{
for(int j = 1 + s; j <= n + s; j++)
{
if(j % i == 0)
{
addedge(i, j - s + n);
addedge(j - s + n, i);
}
}
}
int res = solve();
if(res == n) printf("Case #%d: Yes\n", ++kase);
else printf("Case #%d: No\n", ++kase);
}
return 0;
}
15. HDU - 5684 - zxa and lcm(未知)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5684
太难了,实在不会做了~