Divan and Kostomuksha(性质+dp+优化转移方程)
Divan and Kostomuksha
[Link](Problem - D1 - Codeforces)
题意
给你
n
n
n个数,
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an。你可以将这
n
n
n个数随意摆放,问你
∑
i
=
1
n
g
c
d
(
a
1
,
a
2
,
.
.
.
,
a
i
)
sum_{i=1}^{n}gcd(a_1,a_2,...,a_i)
∑i=1ngcd(a1,a2,...,ai)的最大值是多少?
数据范围:
1
≤
n
≤
1
0
5
1le nle 10^5
1≤n≤105,
1
≤
m
=
m
a
x
(
a
i
)
≤
5
×
1
0
6
1le m=max(a_i) le 5times10^6
1≤m=max(ai)≤5×106,时限
4
s
4s
4s
思路
假设我们将
a
i
=
x
a_i=x
ai=x放到了第一个位置,那么后面一定是先放
x
x
x的倍数最优,这样
(
x
,
a
i
(
x
的
倍
数
)
)
=
=
x
(x,a_i(x的倍数))==x
(x,ai(x的倍数))==x,否则的话
(
x
,
a
i
(
不
是
x
的
倍
数
)
)
=
=
d
(
x
的
因
数
)
(x,a_i(不是x的倍数))==d(x的因数)
(x,ai(不是x的倍数))==d(x的因数),所以先放
x
x
x的倍数贡献最大。放完
x
x
x的倍数以后再放别的就会让
g
c
d
gcd
gcd减少,设接下来放完的
g
c
d
=
=
y
gcd==y
gcd==y,那么
y
∣
x
y|x
y∣x,发现好像有转移关系。
设
f
[
x
]
;
最
开
始
g
c
d
位
x
的
满
足
题
意
的
最
大
值
f[x];最开始gcd位x的满足题意的最大值
f[x];最开始gcd位x的满足题意的最大值,一开始为
x
x
x放完倍数再放就会使
g
c
d
gcd
gcd变成
x
x
x的一个因数
y
y
y,看一下
f
[
x
]
和
f
[
y
]
f[x]和f[y]
f[x]和f[y]是否有递推式。
设
c
n
t
[
i
]
:
有
多
少
个
i
的
倍
数
cnt[i]:有多少个i的倍数
cnt[i]:有多少个i的倍数,因为
y
∣
x
y|x
y∣x所以
c
n
t
[
x
]
∈
c
n
t
[
y
]
cnt[x]in cnt[y]
cnt[x]∈cnt[y],
f
[
y
]
f[y]
f[y]的最优解的形式为
y
+
y
的
倍
数
(
c
n
t
[
y
]
个
,
包
含
x
和
x
的
倍
数
)
+
其
它
→
x
+
x
的
倍
数
(
c
n
t
[
x
]
个
)
+
y
+
y
的
倍
数
(
c
n
t
[
y
]
−
c
n
t
[
x
]
个
)
+
其
他
y+y的倍数(cnt[y]个,包含x和x的倍数)+其它to x+x的倍数(cnt[x]个)+y+y的倍数(cnt[y]-cnt[x]个)+其他
y+y的倍数(cnt[y]个,包含x和x的倍数)+其它→x+x的倍数(cnt[x]个)+y+y的倍数(cnt[y]−cnt[x]个)+其他。因此
f
[
x
]
=
f
[
y
]
+
(
x
−
y
)
×
c
n
t
[
x
]
f[x]=f[y]+(x-y)times cnt[x]
f[x]=f[y]+(x−y)×cnt[x],即原来跟在
y
y
y后面
g
c
d
gcd
gcd为
y
y
y的移到前面
g
c
d
gcd
gcd可以多
y
−
x
y-x
y−x,一共有
c
n
t
[
x
]
cnt[x]
cnt[x]个。
状态表示:
f
[
i
]
:
g
c
d
以
i
开
头
的
最
大
s
u
m
f[i]:gcd以i开头的最大sum
f[i]:gcd以i开头的最大sum
状态转移:
f
[
i
]
=
m
a
x
(
f
[
j
]
+
(
i
−
j
)
×
c
n
t
[
i
]
∣
j
∣
i
)
f[i]=max(f[j]+(i-j)times cnt[i] |j|i)
f[i]=max(f[j]+(i−j)×cnt[i] ∣j∣i)
每个数的倍数可以读入的时候直接调和级数统计大概
O
(
n
l
o
g
m
)
O(nlogm)
O(nlogm),转移可以直接暴力枚举值域调和级数向它倍数转移复杂度
O
(
m
l
o
g
m
)
O(mlogm)
O(mlogm)可以卡过。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <bitset>
#include <unordered_map>
#include <cmath>
#include <stack>
#include <iomanip>
#include <deque>
#include <sstream>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
#define tpyeinput int
inline char nc() {static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
inline void read(tpyeinput &sum) {char ch=nc();sum=0;while(!(ch>='0'&&ch<='9')) ch=nc();while(ch>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch-48),ch=nc();}
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL cnt[N];
LL f[N];
LL res;
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 0; i < n; i ++) {
int x; cin >> x;
cnt[x] ++;
}
for (int i = 1; i <= N; i ++)
for (int j = i + i; j <= N; j += i)
cnt[i] += cnt[j];
f[1] = n;
for (int i = 1; i <= N; i ++) {
res = max(res, f[i]);
for (int j = i + i; j <= N; j += i)
f[j] = max(f[j], f[i] + (j - i) * cnt[j]);
}
cout << res << endl;
return 0;
}
D2
[Link](Problem - D2 - Codeforces)
题意
不变
数据范围:
1
≤
n
≤
1
0
5
1le nle 10^5
1≤n≤105,
1
≤
m
=
m
a
x
(
a
i
)
≤
2
×
1
0
7
1le m=max(a_i) le 2times10^7
1≤m=max(ai)≤2×107,时限
4
s
4s
4s
思路
延续上次思路
O
(
m
l
o
g
m
)
O(mlogm)
O(mlogm),铁
T
T
T。
考虑优化,对于统计每个数的倍数我们可以用试除法
O
(
n
n
)
O(nsqrt n)
O(nn
)来枚举每个数的约数来统计。
对于我们的状态转移,我们的做法是每个数都往他的倍数转移,发现每个数往它的素数倍转移时最优的。
证明
设有
i
i
i且
j
=
x
i
j=xi
j=xi且
x
不
是
素
数
x不是素数
x不是素数,那么
x
必
可
以
拆
成
x
=
k
(
素
数
)
×
x
2
x必可以拆成x=k(素数)times x_2
x必可以拆成x=k(素数)×x2。
i
i
i向
j
j
j有两种转移方式 :
i
→
j
:
f
[
j
]
=
f
[
i
]
+
(
j
−
i
)
×
c
n
t
[
j
]
ito j :f[j]=f[i]+(j-i)times cnt[j]
i→j:f[j]=f[i]+(j−i)×cnt[j]
i
→
k
→
j
:
f
[
j
]
=
f
[
i
]
+
(
k
−
i
)
×
c
n
t
[
k
]
+
(
j
−
k
)
×
c
n
t
[
j
]
ito k to j:f[j]=f[i]+(k-i)times cnt[k]+(j-k)times cnt[j]
i→k→j:f[j]=f[i]+(k−i)×cnt[k]+(j−k)×cnt[j]
=
f
[
i
]
+
j
c
n
t
[
j
]
−
i
c
n
t
[
k
]
+
k
c
n
t
[
k
]
−
k
c
n
t
[
j
]
+
i
c
n
t
[
j
]
−
i
c
n
t
[
j
]
=f[i]+jcnt[j]-icnt[k]+kcnt[k]-kcnt[j]+icnt[j]-icnt[j]
=f[i]+jcnt[j]−icnt[k]+kcnt[k]−kcnt[j]+icnt[j]−icnt[j]
=
f
[
i
]
+
(
j
−
i
)
×
c
n
t
[
j
]
+
(
i
−
k
)
×
(
c
n
t
[
j
]
−
c
n
t
[
k
]
)
=f[i]+(j-i)times cnt[j]+(i-k)times (cnt[j]-cnt[k])
=f[i]+(j−i)×cnt[j]+(i−k)×(cnt[j]−cnt[k])
因为
c
n
t
[
j
]
≥
c
n
t
[
k
]
,
k
≥
i
cnt[j]ge cnt[k],kge i
cnt[j]≥cnt[k],k≥i 所以第一种转移没有用,一次我们只需要枚举每个数的素数和倍即可。
玄学复杂度
O
(
C
l
o
g
l
o
g
c
)
O(Cloglogc)
O(Cloglogc)
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <bitset>
#include <unordered_map>
#include <cmath>
#include <stack>
#include <iomanip>
#include <deque>
#include <sstream>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e7 + 1, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
#define tpyeinput int
inline char nc() {static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
inline void read(tpyeinput &sum) {char ch=nc();sum=0;while(!(ch>='0'&&ch<='9')) ch=nc();while(ch>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch-48),ch=nc();}
int n, m, k;
LL cnt[N];
LL f[N];
int primes[N], idx;
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; i ++) {
if (!st[i]) primes[idx ++] = i;
for (int j = 0; primes[j] <= n / i; j ++) {
st[primes[j]*i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
get_primes(N - 1);
scanf("%d", &n);
for (int i = 0; i < n; i ++) {
int x; scanf("%d", &x);
for (int j = 1; j * j <= x; j ++)
if (x % j == 0) {
cnt[j] ++;
if (j != x / j) cnt[x/j] ++;
}
}
f[1] = n;
LL res = 0;
for (int i = 1; i < N; i ++) {
res = max(res, f[i]);
for (int j = 0; j < idx && primes[j] * i < N; j ++) {
int p = primes[j];
f[i * p] = max(f[i * p], f[i] + (p * i - i) * cnt[p * i]);
}
}
printf("%lldn", res);
return 0;
}