1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法
1102Div2-①数学②构造+数学③前后缀最大值/并查集④分治+贡献法
Dashboard - Codeforces Round 1102 (Div. 2) - Codeforces
A-数学
题目重述
给定一个长度为 的序列 。
对于两个正整数 ,定义欧几里得算法序列:
对于任意 :
例如:
得到:
因为:
现在给定一个序列 ,允许任意重排其元素。
判断是否存在一组 ,使重排后的序列恰好成为某个欧几里得算法序列。
若存在输出任意一组 ,否则输出 。
性质分析
根据余数定义:
且余数满足:
题目保证序列中的元素均为正整数,因此:
于是有:
即:
因此:
任意合法欧几里得序列一定严格递减。
设将数组按降序排列得到:
如果原数组能够重排成欧几里得序列,那么重排后的顺序只能是这个降序排列。
因为欧几里得序列严格递减。
同时根据定义必须满足:
对于所有:
成立。
因此得到判定条件:
- 数组降序排列后必须严格递减;
- 对所有 :
若全部满足,则:
就是一个合法答案。
正确性证明
必要性
若存在一组 使得数组能够重排成欧几里得序列。
由于欧几里得序列严格递减,因此重排后的序列必然等于降序排列:
又根据定义:
因此:
对所有 成立。
必要性成立。
充分性
若降序排列后满足:
对所有 成立。
取:
根据欧几里得序列定义:
依次递推可得:
因此整个序列正好为:
故原数组经过重排后确实能成为欧几里得序列。
充分性成立。
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)#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;}复杂度分析
排序复杂度:
检查复杂度:
总复杂度:
B-构造+数学
题目重述
给定一个正整数 。
需要找到两个非负整数 ,满足:
- 是回文数
- 能被 整除
若存在这样的数对,输出任意一组;否则输出 。
思路分析
由题意可得:
由于 能被 整除,因此:
即:
于是问题转化为:
找一个不超过 的回文数 ,使得 与 在模 意义下同余。
对于模 的每个余数,都可以找到一个很小的回文数作为代表:
| 余数 | 回文数 |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 22 |
| 11 | 11 |
其中:
因此对于任意余数 :
取表中对应的回文数 ,
则一定满足:
于是:
必然能被 整除。
无解情况
唯一特殊情况是:
因为:
对应的最小回文数是:
但:
无法满足非负整数条件。
因此无解。
事实上:
- 当 时输出
- 其余所有 都存在解
正确性证明
设查表得到的数为 。
回文性质
表中的数字:
均为回文数。
因此 一定满足回文条件。
整除性质
由构造可知:
因此:
即:
一定能被 整除。
和为 n
定义:
显然:
成立。
复杂度分析
每组测试仅进行常数次运算:
总复杂度:
空间复杂度:
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)#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。
对于每条边满足:
- 如果
则没有限制;
- 如果
则必须有
对于每个位置 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 的高度也不能超过右侧路径上的最大连通口高度。
因此:
最优构造
为了使总水量最大,
显然每个位置都应该取到允许的最大值。
定义:
前缀最大值
后缀最大值
则位置 k 的最大合法高度为:
因此答案为:
注意:
位置 0 对应空容器,
其高度固定为 0。
正确性证明
设当前固定位置 i 为空。
引理1
位置 k 的高度不能超过左侧路径中的最大连通口高度。
证明:
若
则左侧所有边都会触发
最终传递到空容器。
于是得到
矛盾。
故结论成立。
引理2
位置 k 的高度不能超过右侧路径中的最大连通口高度。
证明同理。
因此:
引理3
令
则一定合法。
因为对于任意边,
两侧高度都不会超过限制值,
若超过则两侧高度相同,
因此满足题目要求。
定理
上述构造得到最大总水量。
因为每个位置都已经达到其理论上界,
任何方案都不可能取得更大的总和。
故算法正确。
复杂度分析
对于每个起点:
- 构造数组:
O(n) - 前缀最大值:
O(n) - 后缀最大值:
O(n) - 统计答案:
O(n)
总复杂度:
Easy Version 中:
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):
如果
那么必须有
否则该状态非法。
对于每个位置 s,要求:
在所有合法方案中求
的最大值。
物理意义
考虑相邻两个容器。
连接口高度为 h_i。
若两边水位都不超过 h_i:
w_i <= h_i$$w_{i+1} <= h_i
则完全合法。
若有一边超过了连接口:
那么根据连通器原理,两边最终必须处于同一水位:
因此对于每条边:
- 要么两边都不超过
h_i - 要么两边高度相等
固定一个空容器
设要求:
由于环被断开,
问题变成了一条链:
因为两条与 s 相邻的边都连接到高度为 0 的容器。
重要结论
设链上某个位置的最终高度为 x。
若想把它提高到超过某条边的限制:
则必须与边另一侧高度相同。
于是会不断向外扩展。
换句话说:
一个高度为 x 的连通块能够存在,
当且仅当它覆盖了所有
的边。
转换
考虑所有边高度。
若固定一个高度值 X。
把满足
的边看成必须连通。
于是这些边把若干顶点合并成若干连通块。
每个连通块中的所有点高度都必须相等。
如果连通块内没有空容器 s,
则整个连通块都可以取高度 X。
如果连通块包含 s,
由于
整个块只能取高度 0。
因此对于固定高度 X:
贡献为
Easy 版:
且
因此可以直接枚举答案对应的高度。
枚举高度
注意最优解中的高度变化只会发生在某个 h_i 处。
因此考虑:
X = 1$$X = h_1+1$$X = h_2+1$$...$$X = h_n+1
按照边权从小到大加入。
使用并查集维护:
的边形成的连通块。
并查集维护
对于每个连通块记录:
sz = 连通块大小has = 是否包含空容器 s若某块不包含 s:
贡献
否则贡献为 0。
设
表示所有不含 s 的点数之和。
则当前方案价值:
取最大即可。
Easy Version 复杂度
对于每个空容器:
- 重新建并查集
- 按边权排序加入
复杂度:
由于
且
可以通过。
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 rxt = 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)#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 的二进制数:
其余位置通过如下规则生成:
若当前已知位置为
则同时进行:
最终得到长度为
的完整序列。
对于每个数:
- 表示其中
1的个数; - 表示其中
0的个数。
要求计算:
思路分析
转化贡献
对于某个位置 :
表示:
- 任选一个值为
1的二进制位; - 再任选一个值为
0的二进制位。
因此:
实际上等价于
统计该位置有多少对二进制位取值不同。
于是整个答案可以看成:
对于任意两位二进制位,统计它们在多少个位置取值不同。
由于异或具有按位独立性,因此我们只需要研究单个二进制位即可。
单个位的演化规律
设某一位在左右端点分别为:
其中:
只有四种情况。
情况1:00
例如:
0 ? ? ? 0第一次填充:
0 ? 0 ? 0继续递推:
0 0 0 0 0最终全为 0。
情况2:01
从小规模开始观察。
k=1
011k=2
01101k=3
011011011可以发现循环节:
011情况3:10
同理:
k=1
101k=2
10110k=3
110110110循环节:
110情况4:11
k=1
110k=2
11011k=3
101101101循环节:
101因此对于任意二进制位,只会出现以下四种模式:
| 类型 | 左端 | 右端 | 周期模式 |
|---|---|---|---|
| c0 | 0 | 0 | 000 |
| c1 | 0 | 1 | 011 |
| c2 | 1 | 0 | 110 |
| c3 | 1 | 1 | 101 |
统计四种类型的位数即可。
统计四种类型
设:
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 的循环。
设:
表示总位置数。
把所有位置按照下标模 3 分类。
模0位置数量
N0 = (m + 2) // 3模1位置数量
N1 = (m + 1) // 3模2位置数量
N2 = m // 3k 的奇偶性
观察上面的构造可以发现:
当递推层数变化时,
10 与 11 两种模式会发生交换。
因此需要分类讨论。
k 为奇数
此时:
00 -> 00001 -> 01110 -> 11011 -> 101于是:
x0 = c2 + c3x1 = c1 + c2x2 = c1 + c3其中:
x0:模0位置上的 1 的个数;x1:模1位置上的 1 的个数;x2:模2位置上的 1 的个数。
k 为偶数
此时:
00 -> 00001 -> 01110 -> 10111 -> 110于是:
x0 = c2 + c3x1 = c1 + c3x2 = c1 + c2计算贡献
对于某一类位置:
若其中有
个二进制位为 1,
则有
个二进制位为 0。
因此该位置贡献:
若该类位置共有
个,
则总贡献:
因此最终答案为:
正确性证明
对于任意位置:
表示该位置上:
- 选择一个值为 1 的位;
- 选择一个值为 0 的位;
的方案数。
因此:
即统计不同位对数量。
由于异或运算按位独立,
每个二进制位最终只可能属于四种周期模式:
000011110101因此只需要统计四类位数:
c0,c1,c2,c3即可确定任意位置上 1 的数量。
对于模 0、模 1、模 2 三类位置,
分别统计:
x0,x1,x2个 1。
则对应贡献为:
乘上该类位置个数即可。
故公式正确。
复杂度分析
统计四种类型:
其余均为常数运算。
总复杂度:
空间复杂度:
满足:
的要求。
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)