1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法

4089 字
20 分钟
1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法

1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法#

Dashboard - Codeforces Round 1102 (Div. 2) - Codeforces

更好的阅读体验

A-数学#

题目重述#

给定一个长度为 nn 的序列 bb

对于两个正整数 xyx \ge y,定义欧几里得算法序列:

a1=x,a2=ya_1=x,\quad a_2=y

对于任意 1ik21\le i\le k-2

ai+2=aimodai+1a_{i+2}=a_i \bmod a_{i+1}

例如:

x=13, y=8x=13,\ y=8

得到:

[13,8,5,3][13,8,5,3]

因为:

13mod8=513\bmod 8=58mod5=38\bmod 5=3

现在给定一个序列 bb,允许任意重排其元素。

判断是否存在一组 (x,y)(x,y),使重排后的序列恰好成为某个欧几里得算法序列。

若存在输出任意一组 (x,y)(x,y),否则输出 1-1


性质分析#

根据余数定义:

ai+2=aimodai+1a_{i+2}=a_i\bmod a_{i+1}

且余数满足:

0ai+2<ai+10\le a_{i+2}<a_{i+1}

题目保证序列中的元素均为正整数,因此:

ai+2>0a_{i+2}>0

于是有:

ai+2<ai+1a_{i+2}<a_{i+1}

即:

a1>a2>a3>>aka_1>a_2>a_3>\cdots>a_k

因此:

任意合法欧几里得序列一定严格递减。


设将数组按降序排列得到:

c1>c2>>cnc_1>c_2>\cdots>c_n

如果原数组能够重排成欧几里得序列,那么重排后的顺序只能是这个降序排列。

因为欧几里得序列严格递减。

同时根据定义必须满足:

cimodci+1=ci+2c_i \bmod c_{i+1}=c_{i+2}

对于所有:

1in21\le i\le n-2

成立。

因此得到判定条件:

  • 数组降序排列后必须严格递减;
  • 对所有 ii
cimodci+1=ci+2c_i\bmod c_{i+1}=c_{i+2}

若全部满足,则:

x=c1,y=c2x=c_1,\quad y=c_2

就是一个合法答案。


正确性证明#

必要性

若存在一组 (x,y)(x,y) 使得数组能够重排成欧几里得序列。

由于欧几里得序列严格递减,因此重排后的序列必然等于降序排列:

c1>c2>>cnc_1>c_2>\cdots>c_n

又根据定义:

ai+2=aimodai+1a_{i+2}=a_i\bmod a_{i+1}

因此:

cimodci+1=ci+2c_i\bmod c_{i+1}=c_{i+2}

对所有 ii 成立。

必要性成立。


充分性

若降序排列后满足:

cimodci+1=ci+2c_i\bmod c_{i+1}=c_{i+2}

对所有 ii 成立。

取:

x=c1,y=c2x=c_1,\quad y=c_2

根据欧几里得序列定义:

a3=xmody=c3a_3=x\bmod y=c_3a4=c2modc3=c4a_4=c_2\bmod c_3=c_4

依次递推可得:

ai=cia_i=c_i

因此整个序列正好为:

c1,c2,,cnc_1,c_2,\cdots,c_n

故原数组经过重排后确实能成为欧几里得序列。

充分性成立。


py
t = int(input())
for _ in range(t):
n = int(input())
b = list(map(int, input().split()))
if n == 2:
print(max(b), min(b))
continue
c = sorted(b, reverse=True)
ok = True
# 必须严格递减
for i in range(n - 1):
if c[i] <= c[i + 1]:
ok = False
break
# 检查取模关系
if ok:
for i in range(n - 2):
if c[i] % c[i + 1] != c[i + 2]:
ok = False
break
if ok:
print(c[0], c[1])
else:
print(-1)

c++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<long long> b(n);
for (int i = 0; i < n; i++) {
cin >> b[i];
}
if (n == 2) {
cout << max(b[0], b[1]) << " "
<< min(b[0], b[1]) << '\n';
continue;
}
sort(b.begin(), b.end(), greater<long long>());
bool ok = true;
for (int i = 0; i < n - 1; i++) {
if (b[i] <= b[i + 1]) {
ok = false;
break;
}
}
if (ok) {
for (int i = 0; i < n - 2; i++) {
if (b[i] % b[i + 1] != b[i + 2]) {
ok = false;
break;
}
}
}
if (ok)
cout << b[0] << " " << b[1] << '\n';
else
cout << -1 << '\n';
}
return 0;
}

复杂度分析#

排序复杂度:

O(nlogn)O(n\log n)

检查复杂度:

O(n)O(n)

总复杂度:

O(nlogn)O(n\log n)

B-构造+数学#

题目重述#

给定一个正整数 nn

需要找到两个非负整数 a,ba,b,满足:

  1. a+b=na+b=n
  2. aa 是回文数
  3. bb 能被 1212 整除

若存在这样的数对,输出任意一组;否则输出 1-1


思路分析#

由题意可得:

b=nab=n-a

由于 bb 能被 1212 整除,因此:

na0(mod12)n-a\equiv0\pmod{12}

即:

an(mod12)a\equiv n\pmod{12}

于是问题转化为:

找一个不超过 nn 的回文数 aa,使得 aann 在模 1212 意义下同余。


对于模 1212 的每个余数,都可以找到一个很小的回文数作为代表:

余数回文数
00
11
22
33
44
55
66
77
88
99
1022
1111

其中:

2210(mod12)22 \equiv 10 \pmod{12}1111(mod12)11 \equiv 11 \pmod{12}

因此对于任意余数 r=nmod12r=n\bmod12

取表中对应的回文数 aa

则一定满足:

an(mod12)a\equiv n\pmod{12}

于是:

b=nab=n-a

必然能被 1212 整除。


无解情况#

唯一特殊情况是:

n=10n=10

因为:

10mod12=1010\bmod12=10

对应的最小回文数是:

a=22a=22

但:

22>1022>10

无法满足非负整数条件。

因此无解。

事实上:

  • n=10n=10 时输出 1-1
  • 其余所有 nn 都存在解

正确性证明#

设查表得到的数为 aa

回文性质#

表中的数字:

0,1,2,,9,11,220,1,2,\cdots,9,11,22

均为回文数。

因此 aa 一定满足回文条件。


整除性质#

由构造可知:

an(mod12)a\equiv n\pmod{12}

因此:

na0(mod12)n-a\equiv0\pmod{12}

即:

b=nab=n-a

一定能被 1212 整除。


和为 n#

定义:

b=nab=n-a

显然:

a+b=na+b=n

成立。


复杂度分析#

每组测试仅进行常数次运算:

O(1)O(1)

总复杂度:

O(t)O(t)

空间复杂度:

O(1)O(1)
py
t = int(input())
pal = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 22, 11]
for _ in range(t):
n = int(input())
a = pal[n % 12]
if a > n:
print(-1)
else:
print(a, n - a)

c++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
long long pal[12] = {
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 22, 11
};
while (T--) {
long long n;
cin >> n;
long long a = pal[n % 12];
if (a > n) {
cout << -1 << '\n';
} else {
cout << a << ' ' << n - a << '\n';
}
}
return 0;
}

C-前后缀最大值#

题意简述#

n 个首尾相连的容器。

i 个容器与第 (i mod n)+1 个容器之间有一个高度为 h_i 的连通口。

设最终水位为 w_i

对于每条边满足:

  • 如果
max(wi,wi+1)hi\max(w_i,w_{i+1}) \le h_i

则没有限制;

  • 如果
max(wi,wi+1)>hi\max(w_i,w_{i+1}) > h_i

则必须有

wi=wi+1w_i=w_{i+1}

对于每个位置 i,要求:

wi=0w_i=0

求所有合法方案中:

wi\sum w_i

的最大值。


关键性质#

固定某个位置 i 为空。

把环从该位置断开。

设新的边序列为:

e[0],e[1],...,e[n-1]

其中:

e[j] = h[(i+j)%n]

此时问题变成了一条链。


水位能够达到多少?#

考虑链上的某个位置 k

它左边经过的所有连通口高度为:

e[0],e[1],...,e[k-1]

右边经过的所有连通口高度为:

e[k],e[k+1],...,e[n-1]

若位置 k 的水位超过左边某个连通口高度,则根据题目条件,该连通口两侧必须保持相同高度。

继续向左传播,最终会传到空容器。

由于空容器高度为 0,因此不可能。

所以位置 k 的高度不能超过左侧路径上的最大连通口高度。

同理:

位置 k 的高度也不能超过右侧路径上的最大连通口高度。

因此:

wkmin(max0t<ket,maxkt<net)w_k \le \min \left( \max_{0\le t<k} e_t, \max_{k\le t<n} e_t \right)

最优构造#

为了使总水量最大,

显然每个位置都应该取到允许的最大值。

定义:

前缀最大值#

pre[k]=max(e0,e1,,ek)pre[k] = \max(e_0,e_1,\dots,e_k)

后缀最大值#

suf[k]=max(ek,ek+1,,en1)suf[k] = \max(e_k,e_{k+1},\dots,e_{n-1})

则位置 k 的最大合法高度为:

min(pre[k1],suf[k])\min(pre[k-1],suf[k])

因此答案为:

k=1n1min(pre[k1],suf[k])\sum_{k=1}^{n-1} \min(pre[k-1],suf[k])

注意:

位置 0 对应空容器,

其高度固定为 0


正确性证明#

设当前固定位置 i 为空。

引理1#

位置 k 的高度不能超过左侧路径中的最大连通口高度。

证明:

wk>max(e0,,ek1)w_k > \max(e_0,\dots,e_{k-1})

则左侧所有边都会触发

wu=wvw_u=w_v

最终传递到空容器。

于是得到

wk=0w_k=0

矛盾。

故结论成立。


引理2#

位置 k 的高度不能超过右侧路径中的最大连通口高度。

证明同理。

因此:

wkmin(pre[k1],suf[k])w_k \le \min(pre[k-1],suf[k])

引理3#

wk=min(pre[k1],suf[k])w_k= \min(pre[k-1],suf[k])

则一定合法。

因为对于任意边,

两侧高度都不会超过限制值,

若超过则两侧高度相同,

因此满足题目要求。


定理#

上述构造得到最大总水量。

因为每个位置都已经达到其理论上界,

任何方案都不可能取得更大的总和。

故算法正确。


复杂度分析#

对于每个起点:

  • 构造数组:O(n)
  • 前缀最大值:O(n)
  • 后缀最大值:O(n)
  • 统计答案:O(n)

总复杂度:

O(n2)O(n^2)

Easy Version 中:

n3000\sum n \le 3000
py
for _ in range(int(input())):
n = int(input())
h = list(map(int, input().split()))
ans = [0] * n
for i in range(n):
e = [h[(i + j) % n] for j in range(n)]
pre = [0] * n
suf = [0] * n
pre[0] = e[0]
for j in range(1, n):
pre[j] = max(pre[j - 1], e[j])
suf[n - 1] = e[n - 1]
for j in range(n - 2, -1, -1):
suf[j] = max(suf[j + 1], e[j])
cur = 0
for k in range(1, n):
cur += min(pre[k - 1], suf[k])
ans[i] = cur
print(*ans)

C-并查集#

题意重述#

n 个无限高的连通容器围成一个环。

i 个容器与第 (i mod n)+1 个容器之间有一条高度为 h[i] 的连通口。

设各容器水位为 w[i]

对于每条边 (i,i+1)

如果

max(wi,wi+1)>hi\max(w_i,w_{i+1}) > h_i

那么必须有

wi=wi+1w_i=w_{i+1}

否则该状态非法。

对于每个位置 s,要求:

ws=0w_s=0

在所有合法方案中求

i=1nwi\sum_{i=1}^{n} w_i

的最大值。


物理意义#

考虑相邻两个容器。

连接口高度为 h_i

若两边水位都不超过 h_i

w_i <= h_i$$w_{i+1} <= h_i

则完全合法。

若有一边超过了连接口:

max(wi,wi+1)>himax(w_i,w_{i+1}) > h_i

那么根据连通器原理,两边最终必须处于同一水位:

wi=wi+1w_i=w_{i+1}

因此对于每条边:

  • 要么两边都不超过 h_i
  • 要么两边高度相等

固定一个空容器#

设要求:

ws=0w_s=0

由于环被断开,

问题变成了一条链:

s+1,s+2,...,s1s+1,s+2,...,s-1

因为两条与 s 相邻的边都连接到高度为 0 的容器。


重要结论#

设链上某个位置的最终高度为 x

若想把它提高到超过某条边的限制:

x>hix > h_i

则必须与边另一侧高度相同。

于是会不断向外扩展。

换句话说:

一个高度为 x 的连通块能够存在,

当且仅当它覆盖了所有

hi<xh_i < x

的边。


转换#

考虑所有边高度。

若固定一个高度值 X

把满足

hi<Xh_i < X

的边看成必须连通。

于是这些边把若干顶点合并成若干连通块。

每个连通块中的所有点高度都必须相等。

如果连通块内没有空容器 s

则整个连通块都可以取高度 X

如果连通块包含 s

由于

ws=0w_s=0

整个块只能取高度 0

因此对于固定高度 X

贡献为

X×(不包含 s 的点数)X \times (\text{不包含 } s \text{ 的点数})

Easy 版:

n3000n\le 3000

n3000\sum n \le 3000

因此可以直接枚举答案对应的高度。


枚举高度#

注意最优解中的高度变化只会发生在某个 h_i 处。

因此考虑:

X = 1$$X = h_1+1$$X = h_2+1$$...$$X = h_n+1

按照边权从小到大加入。

使用并查集维护:

hi<Xh_i < X

的边形成的连通块。


并查集维护#

对于每个连通块记录:

sz = 连通块大小
has = 是否包含空容器 s

若某块不包含 s

贡献

sz×Xsz \times X

否则贡献为 0。

alive(X)alive(X)

表示所有不含 s 的点数之和。

则当前方案价值:

alive(X)×Xalive(X)\times X

取最大即可。


Easy Version 复杂度#

对于每个空容器:

  • 重新建并查集
  • 按边权排序加入

复杂度:

O(n2logn)O(n^2 \log n)

由于

n3000n\le3000

n3000\sum n\le3000

可以通过。


py
def find(p, x):
if p[x] != x:
p[x] = find(p, p[x])
return p[x]
def union(p, rank, x, y):
rx, ry = find(p, x), find(p, y)
if rx == ry:
return rx
if rank[rx] < rank[ry]:
p[rx] = ry
return ry
elif rank[rx] > rank[ry]:
p[ry] = rx
return rx
else:
p[ry] = rx
rank[rx] += 1
return rx
t = int(input())
for _ in range(t):
n = int(input())
h = list(map(int, input().split()))
edges = [(h[i], i) for i in range(n)]
edges.sort()
ans = [0] * n
for s in range(n):
p = list(range(n))
rank = [0] * n
sz = [1] * n
has = [0] * n
has[s] = 1
alive = n - 1
best = alive
ptr = 0
while ptr < n:
H = edges[ptr][0]
while ptr < n and edges[ptr][0] == H:
idx = edges[ptr][1]
u = idx
v = (idx + 1) % n
fu = find(p, u)
fv = find(p, v)
if fu != fv:
before = 0
if has[fu] == 0:
before += sz[fu]
if has[fv] == 0:
before += sz[fv]
root = union(p, rank, fu, fv)
if root == fu:
other = fv
else:
other = fu
sz[root] += sz[other]
has[root] |= has[other]
after = 0
if has[root] == 0:
after += sz[root]
alive += after - before
ptr += 1
best = max(best, alive * (H + 1))
ans[s] = best
print(*ans)
c++
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> fa, sz, has;
DSU(int n,int s):fa(n),sz(n,1),has(n,0){
iota(fa.begin(),fa.end(),0);
has[s]=1;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int a,int b){
a=find(a);
b=find(b);
if(a==b) return;
if(sz[a]<sz[b]) swap(a,b);
fa[b]=a;
sz[a]+=sz[b];
has[a]|=has[b];
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
vector<long long> h(n);
for(int i=0;i<n;i++) cin>>h[i];
vector<long long> ans(n);
vector<pair<long long,int>> edges;
for(int i=0;i<n;i++)
edges.push_back({h[i],i});
sort(edges.begin(),edges.end());
for(int s=0;s<n;s++){
DSU dsu(n,s);
long long curAlive=n-1;
long long best=curAlive;
int ptr=0;
while(ptr<n){
long long H=edges[ptr].first;
while(ptr<n && edges[ptr].first==H){
int id=edges[ptr].second;
int u=id;
int v=(id+1)%n;
int fu=dsu.find(u);
int fv=dsu.find(v);
if(fu!=fv){
long long before=0;
if(!dsu.has[fu]) before+=dsu.sz[fu];
if(!dsu.has[fv]) before+=dsu.sz[fv];
dsu.unite(fu,fv);
int f=dsu.find(fu);
long long after=0;
if(!dsu.has[f])
after+=dsu.sz[f];
curAlive+=after-before;
}
ptr++;
}
best=max(best,curAlive*(H+1));
}
ans[s]=best;
}
for(auto x:ans)
cout<<x<<" ";
cout<<"\n";
}
return 0;
}

D-分治+贡献法#

题目大意#

给定两个长度为 n 的二进制数:

a1=s,a2k+1=za_1=s,\qquad a_{2^k+1}=z

其余位置通过如下规则生成:

若当前已知位置为

p1<p2<<pmp_1<p_2<\cdots<p_m

则同时进行:

api+pi+12=apiapi+1(1i<m)a_{\frac{p_i+p_{i+1}}2} = a_{p_i}\oplus a_{p_{i+1}} \qquad (1\le i<m)

最终得到长度为

2k+12^k+1

的完整序列。

对于每个数:

  • xix_i 表示其中 1 的个数;
  • yiy_i 表示其中 0 的个数。

要求计算:

i=12k+1xiyi\sum_{i=1}^{2^k+1}x_i y_i

思路分析#

转化贡献

对于某个位置 ii

xiyix_i y_i

表示:

  • 任选一个值为 1 的二进制位;
  • 再任选一个值为 0 的二进制位。

因此:

xiyix_i y_i

实际上等价于

统计该位置有多少对二进制位取值不同。

于是整个答案可以看成:

对于任意两位二进制位,统计它们在多少个位置取值不同。

由于异或具有按位独立性,因此我们只需要研究单个二进制位即可。


单个位的演化规律#

设某一位在左右端点分别为:

L,RL,R

其中:

L,R0,1L,R\in{0,1}

只有四种情况。


情况1:00#

例如:

0 ? ? ? 0

第一次填充:

0 ? 0 ? 0

继续递推:

0 0 0 0 0

最终全为 0。


情况2:01#

从小规模开始观察。

k=1

011

k=2

01101

k=3

011011011

可以发现循环节:

011

情况3:10#

同理:

k=1

101

k=2

10110

k=3

110110110

循环节:

110

情况4:11#

k=1

110

k=2

11011

k=3

101101101

循环节:

101

因此对于任意二进制位,只会出现以下四种模式:

类型左端右端周期模式
c000000
c101011
c210110
c311101

统计四种类型的位数即可。


统计四种类型#

设:

c0

表示:

s[i]=0,z[i]=0

的位数。


设:

c1

表示:

s[i]=0,z[i]=1

的位数。


设:

c2

表示:

s[i]=1,z[i]=0

的位数。


设:

c3

表示:

s[i]=1,z[i]=1

的位数。


按模 3 分类#

因为最终序列的模式都是长度为 3 的循环。

设:

m=2k+1m=2^k+1

表示总位置数。

把所有位置按照下标模 3 分类。


模0位置数量#

N0 = (m + 2) // 3

模1位置数量#

N1 = (m + 1) // 3

模2位置数量#

N2 = m // 3

k 的奇偶性#

观察上面的构造可以发现:

当递推层数变化时,

1011 两种模式会发生交换。

因此需要分类讨论。


k 为奇数#

此时:

00 -> 000
01 -> 011
10 -> 110
11 -> 101

于是:

x0 = c2 + c3
x1 = c1 + c2
x2 = c1 + c3

其中:

  • x0:模0位置上的 1 的个数;
  • x1:模1位置上的 1 的个数;
  • x2:模2位置上的 1 的个数。

k 为偶数#

此时:

00 -> 000
01 -> 011
10 -> 101
11 -> 110

于是:

x0 = c2 + c3
x1 = c1 + c3
x2 = c1 + c2

计算贡献#

对于某一类位置:

若其中有

xx

个二进制位为 1,

则有

nxn-x

个二进制位为 0。

因此该位置贡献:

x(nx)x(n-x)

若该类位置共有

cntcnt

个,

则总贡献:

cntx(nx)cnt\cdot x(n-x)

因此最终答案为:

N0x0(nx0)+N1x1(nx1)+N2x2(nx2)N_0x_0(n-x_0) + N_1x_1(n-x_1) + N_2x_2(n-x_2)

正确性证明#

对于任意位置:

xiyix_i y_i

表示该位置上:

  • 选择一个值为 1 的位;
  • 选择一个值为 0 的位;

的方案数。

因此:

xiyi=(u,v)bitubitvx_i y_i = {(u,v)\mid bit_u\ne bit_v}

即统计不同位对数量。


由于异或运算按位独立,

每个二进制位最终只可能属于四种周期模式:

000
011
110
101

因此只需要统计四类位数:

c0,c1,c2,c3

即可确定任意位置上 1 的数量。


对于模 0、模 1、模 2 三类位置,

分别统计:

x0,x1,x2

个 1。

则对应贡献为:

xj(nxj)x_j(n-x_j)

乘上该类位置个数即可。

故公式正确。


复杂度分析#

统计四种类型:

O(n)O(n)

其余均为常数运算。

总复杂度:

O(n)O(n)

空间复杂度:

O(1)O(1)

满足:

n105\sum n\le10^5

的要求。


分治+贡献法
for _ in range(int(input())):
n, k = map(int, input().split())
s = input().strip()
z = input().strip()
c0 = c1 = c2 = c3 = 0
for a, b in zip(s, z):
if a == '0' and b == '0':
c0 += 1
elif a == '0' and b == '1':
c1 += 1
elif a == '1' and b == '0':
c2 += 1
else:
c3 += 1
m = (1 << k) + 1
N0 = (m + 2) // 3
N1 = (m + 1) // 3
N2 = m // 3
if k & 1:
x0 = c2 + c3
x1 = c1 + c2
x2 = c1 + c3
else:
x0 = c2 + c3
x1 = c1 + c3
x2 = c1 + c2
ans = (
N0 * x0 * (n - x0)
+ N1 * x1 * (n - x1)
+ N2 * x2 * (n - x2)
)
print(ans)
1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法
http://blog.7a7a68.xyz/posts/1102div2-数学构造数学前后缀最大值-并查集分治贡献法/
作者
Waning
发布于
2026-06-08
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Waning
愿你明日如绚丽之花.
公告
还活着.
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
56
分类
8
标签
20
总字数
125,693
运行时长
0
最后活动
0 天前

目录