B - GCD Subtraction
题意:
给定两个正整数 A , B A,B A,B,给定一个操作:令 g = g c d ( A , B ) g=gcd(A, B) g=gcd(A,B),令 A = A − g , B = B − g A=A-g, B=B-g A=A−g,B=B−g。问最少经过多少次操作之后其中一个数变为0.
思路:
令
A
′
=
A
/
g
,
B
′
=
B
/
g
A'=A/g,B'=B/g
A′=A/g,B′=B/g,操作一次后等价于求
(
A
′
−
1
)
∗
g
,
(
B
′
−
1
)
∗
g
(A'-1)*g,(B'-1)*g
(A′−1)∗g,(B′−1)∗g的答案。而且不难发现的是,后面的
g
g
g对答案并无影响,所以等价于求
A
′
−
1
,
B
′
−
1
A'-1,B'-1
A′−1,B′−1的答案。
显然,如果
g
>
1
g>1
g>1那么该操作最多进行
l
o
g
log
log次,直接暴力递归求解即可。如果
g
=
1
g=1
g=1,那么我们需要找到一个最小的整数
x
x
x,使得
g
c
d
(
A
′
−
1
−
x
,
B
−
1
−
x
)
≠
1
gcd(A'-1-x,B-1-x)\neq1
gcd(A′−1−x,B−1−x)=1。即满足
A
≡
B
(
m
o
d
m
)
A\equiv B(mod m)
A≡B(modm)。我们令
A
=
k
1
∗
m
+
b
1
,
B
=
k
2
∗
m
+
b
1
A=k_1*m+b_1,B=k_2*m+b_1
A=k1∗m+b1,B=k2∗m+b1,我们要找最小的
b
1
b_1
b1。
A
−
B
A-B
A−B也是
m
m
m的倍数,那么我们暴力枚举
A
−
B
A-B
A−B的因数即可。时间复杂度
n
∗
l
o
g
n
\sqrt{n}*logn
n∗logn
代码:
LL a, b, ans;
void solve(LL x) {
LL t = b;
for(LL i = 2; i * i <= x; i++) {
if(x % i == 0) {
if(a % i == b % i) t = min(t, a % i);
if(a % (x / i) == b % (x / i)) t = min(t, a % (x / i));
}
}
if(a % x == b % x) t = min(t, a % x);
ans += t;
a -= t;
b -= t;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> a >> b;
while(a && b) {
if(a < b) swap(a, b);
LL g = __gcd(a, b);
a /= g, b /= g;
a--, b--;
ans++;
if(a == 0 || b == 0) break;
if(a - 1 == b) {
ans += b;
break;
}
if(a == b) {
ans++;
break;
}
solve(a - b);
}
cout << ans << '\n';
return 0;
}
C - Permutation Addition
题意:
给定一个长为 n , n ≤ 50 n,n\leq 50 n,n≤50的数组 a [ i ] a[i] a[i],每次可以选择一个 n n n的排列 p p p,使得 a [ i ] = a [ i ] + p [ i ] a[i]=a[i]+p[i] a[i]=a[i]+p[i],求能否在操作次数不超过 1 e 4 1e4 1e4的前提下将数组所有元素变得相等。
思路:
对于这种限制操作次数,而且很明显操作次数会很多的构造题,我们应尽量去找存不存在一个元操作使数组有某种变化,然后复制该操作。
我们令
s
u
m
=
∑
a
[
i
]
sum=\sum{a[i]}
sum=∑a[i],显然最后要使
s
u
m
%
n
=
0
sum\%n=0
sum%n=0。
若
n
n
n为奇数,则
n
∗
(
n
+
1
)
2
%
n
=
0
\frac{n*(n+1)}{2}\%n=0
2n∗(n+1)%n=0,
s
u
m
%
n
=
0
sum\%n=0
sum%n=0有解,我们构造两个排列:
n
=
5
n=5
n=5:
p
:
1
2
3
4
,
p
′
:
4
3
2
1
p:1\quad 2 \quad 3 \quad 4, p':4\quad 3\quad 2\quad 1
p:1234,p′:4321。之后相当于
a
[
1
]
+
1
,
a
[
2
]
−
1
a[1]+1,a[2]-1
a[1]+1,a[2]−1。那么我们就找到了一个元操作,使得一个位置增加1,一个位置减少1.
若
n
n
n为偶数,则
n
∗
(
n
+
1
)
2
%
n
=
n
2
\frac{n*(n+1)}{2}\%n=\frac{n}{2}
2n∗(n+1)%n=2n,则
s
u
m
%
n
=
0
,
s
u
m
%
n
=
n
2
sum\%n=0,sum\%n=\frac{n}{2}
sum%n=0,sum%n=2n有解。
对于
s
u
m
%
n
=
n
/
2
sum\%n=n/2
sum%n=n/2,我们先随便找一个排列将其变成
s
u
m
%
n
=
0
sum\%n=0
sum%n=0即可。
代码:
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n + 1);
int sum = 0;
for(int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
if(n % 2 == 0 && sum % n != 0 && sum % n != n / 2) {
cout << "No\n";
return 0;
}
if(n % 2 && sum % n != 0) {
cout << "No\n";
return 0;
}
cout << "Yes\n";
int f = 0;
if(n % 2 == 0 && sum % n == n / 2) {
f = 1;
sum += n * (n + 1) / 2;
for(int i = 1; i <= n; i++) a[i] += i;
}
vector<int> x, y;
int mid = sum / n;
for(int i = 1; i <= n; i++) {
while(a[i] < mid) x.pb(i), a[i]++;
while(a[i] > mid) y.pb(i), a[i]--;
}
cout << SZ(x) * 2 + f << '\n';
if(f) {
for(int i = 1; i <= n; i++) cout << i << " \n"[i == n];
}
for(int i = 0; i < SZ(x); i++) {
int l = 2, r = n - 1;
for(int j = 1; j <= n; j++) {
cout << (j != x[i] && j != y[i] ? ++l : (j == x[i] ? 2 : 1)) << " \n"[j == n];
}
for(int j = 1; j <= n; j++) {
cout << (j != x[i] && j != y[i] ? --r : (j == x[i] ? n : n - 1)) << " \n"[j == n];
}
}
return 0;
}
D - LIS 2
题意:
思路:
一个很重要的性质:若一个区间被选,那么选的一定是其一个后缀。
所以我们定义
d
p
[
i
]
dp[i]
dp[i]:第i个区间必选的最长上升子序列。
转移:
若区间
j
,
r
[
j
]
<
l
[
i
]
j,r[j]<l[i]
j,r[j]<l[i],那么
f
[
i
]
=
m
a
x
(
f
[
j
]
+
r
[
i
]
−
l
[
i
]
+
1
)
f[i]=max(f[j]+r[i]-l[i]+1)
f[i]=max(f[j]+r[i]−l[i]+1)
若区间
j
,
r
[
j
]
≥
l
[
i
]
j,r[j]\geq l[i]
j,r[j]≥l[i],那么
f
[
i
]
=
m
a
x
(
f
[
j
]
+
r
[
i
]
−
r
[
j
]
)
f[i]=max(f[j]+r[i]-r[j])
f[i]=max(f[j]+r[i]−r[j])
那么我们将其离散化后建两颗线段树分别维护
f
[
j
]
,
f
[
j
]
−
r
[
j
]
f[j],f[j]-r[j]
f[j],f[j]−r[j]即可。
代码:
#define int LL
int lsh[N], a[N], n;
int l[N], r[N];
int ll[N], rr[N];
struct Node {
int tr[N * 4];
void pushup(int u) {
tr[u] = max(tr[lson], tr[rson]);
}
void modify(int u, int l, int r, int pos, int val) {
if(l == r && l == pos) {
tr[u] = max(tr[u], val);
return;
}
int mid = l + r >> 1;
if(pos <= mid) modify(lson, l, mid, pos, val);
else modify(rson, mid + 1, r, pos, val);
pushup(u);
}
int query(int u, int l, int r, int L, int R) {
// return 0;
// cout << u << ' ';
if(L <= l && r <= R) {
return tr[u];
}
int mid = l + r >> 1;
int res = -INF;
if(L <= mid) res = max(res, query(lson, l, mid, L, R));
if(R > mid) res = max(res, query(rson, mid + 1, r, L, R));
return res;
}
}seg1, seg2;
int dp[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(seg1.tr, -0x3f, sizeof seg1.tr);
memset(seg2.tr, -0x3f, sizeof seg2.tr);
cin >> n;
int id = 0;
for(int i = 1; i <= n; i++) {
cin >> l[i] >> r[i];
lsh[++id] = l[i],
lsh[++id] = r[i];
}
sort(lsh + 1, lsh + 1 + id);
id = unique(lsh + 1, lsh + 1 + id) - lsh - 1;
for(int i = 1; i <= n; i++) {
ll[i] = lower_bound(lsh + 1, lsh + 1 + id, l[i]) - lsh;
rr[i] = lower_bound(lsh + 1, lsh + 1 + id, r[i]) - lsh;
// DD(ll[i], rr[i])
}
dp[0] = 0;
seg1.modify(1, 0, 2 * n, 0, 0);
seg2.modify(1, 0, 2 * n, 0, 0);
for(int i = 1; i <= n; i++) {
dp[i] = max(dp[i], seg1.query(1, 0, 2 * n, 0, ll[i] - 1) + r[i] - l[i] + 1);
dp[i] = max(dp[i], seg2.query(1, 0, 2 * n, ll[i], rr[i]) + r[i]);
// D(dp[i])
// DD(seg2.tr[5], seg2.tr[6])
seg1.modify(1, 0, n * 2, rr[i], dp[i]);
seg2.modify(1, 0, n * 2, rr[i], dp[i] - r[i]);
}
LL ans = *max_element(dp + 1, dp + 1 + n);
cout << ans;
return 0;
}