2026 ICPC 贵州 省赛
点击查看题面
A. 贵州历史文化名城
题目大意
给定一个由两个小写英文字符串 Yes;否则(包括属于国家级历史文化名城或完全不属于名城的情况)输出 No。
数据范围:
,字符串仅包含小写英文字母。
思路
本题属于典型的静态集合查找(Membership Testing)问题。
- 集合构建: 根据题意,合法目标集合仅包含 10 座省级历史文化名城。将每个地名的拼音拆解为二元组
,构成静态集合:
注意题意明确指出“国家级历史文化名城不算作省级历史文化名城”,因此遵义(zun yi)和镇远(zhen yuan)不计入集合 std::set 提供的成员检索函数进行判断:
- 若
,输出 Yes; - 若
,输出 No。
由于数据规模极小(
复杂度分析
时间复杂度: 预处理阶段插入 std::set 中耗时为
空间复杂度: 集合仅存储
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
std::set<std::pair<std::string, std::string>> st = {{"fu", "quan"}, {"an", "shun"}, {"zhi", "jin"}, {"si", "nan"},
{"an", "long"}, {"da", "fang"}, {"pu", "ding"}, {"li", "ping"},
{"shi", "qian"}, {"zhen", "feng"}};
void fc() {
std::string a, b;
std::cin >> a >> b;
std::cout << (st.count({a, b}) ? "Yes" : "No");
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// std::cin>>t;
while (t--) fc();
return 0;
}B. 福泉
题目大意
给定
- 整体衰减:所有油灯亮度减
,即 ( ); - 单点添油:给定参数
与 。若 ,则将第 盏油灯的亮度提升至 ;若 ,则不执行任何操作。
要求在每个夜晚操作结束后输出当前亮度大于
数据范围:
, ,且当 时保证
思路
本题的核心矛盾在于全局区间修改(每轮全体减
1. 绝对时间轴与偏移量转化(Offset Technique)
注意到“所有数每轮同步减
- 设当前已经经历了
个夜晚(即经历过 次整体衰减)。 - 若一盏灯在第
个夜晚之后的亮度为 ,则在第 个夜晚( )其实际亮度为:
- 观察可知,
是一个不随时间 改变的绝对熄灭时间阈值。记 为第 盏灯的绝对失效时间点,只要当前时间满足 ,该油灯即处于点亮状态,其实际亮度为 ;一旦 ,该灯熄灭,亮度为 。
2. 动态维护有效集合
利用该性质,维护动态数据结构即可:
熄灭检测:将所有处于点亮状态油灯的
存入有序集合(如 std::multiset)。每天开始时使时间戳,由于集合单调有序,只需不断检查并移除集合中满足 的元素。 单点加油:对于修改操作
: 计算该灯当前的实际亮度
; 新亮度为
。若 ,说明该操作使该灯的熄灭时间延后; 从集合中移除旧的失效时间
(若原先未熄灭),并将新的失效时间 插入集合,同时更新数组 。 数量统计:处于点亮状态的油灯总数即为集合当前的元素个数(
size())。最终状态:所有夜晚结束后,遍历所有油灯,第
盏灯的最终亮度即为 。
复杂度分析
时间复杂度:
- 初始化:将
个初始亮度转换为绝对时间并构建集合,复杂度为 。 - 时间推进与删除:每个油灯在未被加油的情况下至多被移出集合一次;每次有效的单点加油操作最多引入一次删除与一次插入。整个过程中集合操作的总次数不超过
。 - 单次平衡树插入/删除的复杂度为
。 - 因此,总时间复杂度为
,可在限定时间内稳定通过。
空间复杂度:
- 静态数组
arr用于记录每盏灯的失效时间阈值,占用空间。 std::multiset中同时存在的元素数量最多为,占用空间 。 - 总空间复杂度严格为
。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class ST {
private:
std::multiset<int> s;
std::vector<int> arr;
int t;
public:
void init(std::vector<int>& a) {
arr = a;
for (int i : a)
if (i) s.insert(i);
t = 0;
}
void day() {
t++;
while (s.size() && *s.begin() <= t) {
s.erase(*s.begin());
}
}
void add(int x, int val) {
if (val + t > arr[x]) {
auto it = s.find(arr[x]);
if (it != s.end()) s.erase(it);
if (val) s.insert(val + t);
arr[x] = val + t;
}
}
int get(int i) { return std::max(0, arr[i] - t); }
int size() { return s.size(); }
void print() {
for (int x : s) std::cout << x << " ";
std::cout << endl;
}
};
void fc() {
int n, q;
std::cin >> n >> q;
std::vector<int> a(n);
for (int& i : a) std::cin >> i;
ST st;
st.init(a);
while (q--) {
int x, v;
std::cin >> x >> v;
st.day();
if (x) st.add(x - 1, v);
std::cout << st.size() << '\n';
// for (int i = 0; i < n; i++) {
// std::cout << st.get(i) << " ";
// }
// std::cout << "\n";
// st.print();
}
for (int i = 0; i < n; i++) {
std::cout << st.get(i) << " ";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// std::cin>>t;
while (t--) fc();
return 0;
}C. 安顺
题目大意
渠首至渠尾共有
对每个分水口 Yes,否则输出 No。
数据范围:
思路
本题的核心在于分析无限周期巡视过程中,端点覆盖对内部各点施加的刚性几何约束。
1. 往返距离与时间下界约束
若存在一条无限长的巡渠路线,则整条渠道的两端极值点
考虑任意分水口
- 巡管人离开
前往右端点 并最终返回 ,所经历的最短物理路程为往返距离 ; - 巡管人离开
前往左端点 并最终返回 ,所经历的最短物理路程为往返距离 。
因行进速度为
2. 区间等价转化
将上述两不等式变形为以
定义边界有效区间端点:
由于坐标差
因此,使用整数除法 t / 2 进行边界判定与原数学命题严格充要等价。
3. 充分性与判定策略
若每个分水口均满足
因此,只需在线性扫描中检查是否存在点满足
- 若存在任意一点越界,则无解,输出
No; - 若全部点均在合法区间内,则有解,输出
Yes。
复杂度分析
时间复杂度:
- 读取坐标序列耗时
。 - 单次遍历比对各分水口的坐标与允许区间端点,耗时
。 - 总时间复杂度为
。
空间复杂度:
- 使用长度为
的动态数组存储坐标序列,空间复杂度为 。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void fc() {
int n;
std::cin>>n;
// 读取所有分水口的坐标
std::vector<ll>a(n);
for(int i=0;i<n;i++)std::cin>>a[i];
// 左右两端的极值坐标
ll start=a[0],end=a[n-1];
for(int i=0;i<n;i++){
ll t;std::cin>>t;
// 计算当前分水口允许的合法坐标区间 [x, y]
ll x=end-t/2,y=start+t/2;
// 若当前坐标超出有效覆盖范围,则无法维持无限巡渠
if(a[i]<x||a[i]>y){
std::cout<<"No\n";return;
}
}
std::cout<<"Yes\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
//std::cin>>t;
while (t--) fc();
return 0;
}D. 织金
题目大意
设有
数据范围:
组测试数据( ) ,
思路
1. 可行性判定(剪枝)
由于操作本质为区间非严格单调递增变换,单点状态的转移存在极强的代数约束:
- 单调性约束:若存在
,由于数值不可逆减,必定无解。 - 零元不可变性:当
时,由 可知其状态始终不变,因此若 则无解;反之若 ,则不论覆盖多少次均合法。 - 未触顶精度约束:当
且 时,数值从未被上限截断,其演化必须严格通过翻倍实现。因此必须满足 且 (即商为 的整数次幂)。 - 触顶可行性:当
且 时,只要操作次数充足,必定能满足 并被上限截断至 。
2. 操作次数的区间差分模型
设第
3. 动态松弛与贪心决策
分析每个位置操作次数
- 当
且 时, 具有唯一确定性,必须严格等于下界:
- 当
且 时,该位置被操作任意次均不改变其数值,其下界 ; - 当
且 时,只需翻倍至不小于 即可,故 可取任意满足 的非负整数。
对于能够自由取值的位置(
- 若
,第一项为 ,第二项随 增大单调不增; - 若
,第一项以导数 递增,第二项导数为 或 ,总导数恒大于等于 。
因此,令
复杂度分析
时间复杂度:
- 状态合法性校验与向上对数计算均为常数时间操作。
- 差分累加与贪心维护仅需单次遍历数组。
- 每组测试数据耗时
,总时间复杂度为 ,能够极为充裕地通过全部测试点。
空间复杂度:
- 使用固定长度的全局静态数组维护序列
,空间复杂度为 。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n;
ll m;
ll a[N], b[N], c[N];
int clog2(ll x) {
ll k = 1;
int res = 0;
while (k < x) k <<= 1, res++;
return res;
}
inline bool Is2(ll x) { return (x & (x - 1)) == 0; }
bool isOk() {
for (int i = 1; i <= n; i++) {
if (b[i] < a[i]) return false;
if (a[i] == 0) {
if (b[i]) return false;
continue;
}
if (b[i] % a[i] == 0 && Is2(b[i] / a[i])) continue;
if (b[i] == m) continue;
return false;
}
return true;
}
void Qc() {
for (int i = 1; i <= n; i++) {
c[i] = (a[i] == 0 ? 0 : clog2((b[i] + a[i] - 1) / a[i]));
}
}
int Q() {
int ans = 0;
for (int i = 1; i <= n; i++) {
if (b[i] == m || a[i] == 0) {
c[i] = std::max(c[i],c[i-1]);
}
ans += std::max(0LL,c[i] - c[i - 1]);
}
return ans;
}
void fc() {
std::cin >> n >> m;
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = 1; i <= n; i++) std::cin >> b[i];
if (!isOk()) {
std::cout << "-1\n";
return;
}
Qc();
// for (int i = 1; i <= n; i++) std::cout << c[i] << " ";
// std::cout << '\n';
std::cout << Q() << "\n";
// for (int i = 1; i <= n; i++) std::cout << c[i] << " ";
// std::cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}K. 石阡
题目大意
给定一个长度为
数据范围:
- 测试数据组数
满足 。 - 单组字符串长度
满足 。 - 保证所有测试数据的
之和满足 。
思路
本题要求统计同时满足回文性和字符集大小限制(
1. 回文对称性与字符集等价转化
若一个子串
这意味着:回文串
由此,判断一个回文串是否为双色串,等价于判断其从中心向右延伸的半边子串包含的字符种类数是否不超过 2。
2. 双指针维护双色最远右端点
对于给定的起始位置
因此,可以利用滑动窗口(双指针)算法,预处理出数组
3. Manacher 算法统一处理回文中心
使用经典的 Manacher 算法,在字符之间插入分隔符 '#' 将原串长度扩展为
4. 统计合法子串贡献
对于扩展串中的每一个回文中心
根据中心位置的奇偶性,映射出原串中对应的右半边起始下标
,以及该中心最远能向右扩展到的原串下标 : 若
为奇数(对应原串字符间的缝隙,即偶回文):起点 ,最远右端点 ; 若
为偶数(对应原串具体字符,即奇回文):起点 ,最远右端点 。 在以
为中心的所有回文子串中,其右半段在原串的端点区间为 。 结合双色限制,合法右端点的上限为
。 因此,该中心对答案的贡献即为合法右端点的数量:
将所有回文中心的贡献求和即为最终结果。
复杂度分析
时间复杂度:
字符串扩展与 Manacher 算法:复杂度为
; 双指针求解
数组:左右指针均只单调向前移动,复杂度为 ; 遍历扩展中心统计贡献:单次判定为
,总复杂度为 ; 对于多组测试数据,总时间复杂度为
,可以在时限内高效通过。 空间复杂度:
扩展字符串数组
exp_s及回文半径数组p长度为,空间复杂度为 ; 存储最远端点的
max_r数组大小为,空间复杂度为 ; 总额外空间复杂度为
。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N=1e6+5;
int n;
std::string s;
char exp_s[(N<<1)+3];
int p[(N<<1)+3],max_r[N];
int idx=0;
void expand() {
idx=2;
exp_s[0]='$';exp_s[1]='#';
for (int i = 0; i < s.size(); i++) exp_s[idx++]= s[i],exp_s[idx++]= '#';
exp_s[idx++]= '%';
}
void get_Manacher_mx() {
for (int i = 1, r = 0, c = 0; i < idx - 1; i++) {
p[i] = (r > i) ? min(p[2 * c - i], r - i) : 1;
while (exp_s[i - p[i]] == exp_s[i + p[i]]) p[i]++;
if (i + p[i] > r) {
r = i + p[i];
c = i;
}
}
}
void get_max_r(){
int cnt[26]={0};
int d=0;
for(int i=0,j=0;i<n;i++){
while(j<n){
if(cnt[s[j]-'a']==0&&d==2)break;
if(cnt[s[j]-'a']++ == 0)d++;
j++;
}
max_r[i]=j-1;
if(--cnt[s[i]-'a']==0)d--;
}
}
void fc() {
std::cin>>n>>s;
expand();
get_Manacher_mx();
get_max_r();
ll ans=0;
for(int i=2;i<idx-2;i++){
if(i&1){
int l=(i+1)/2-1,r=l+(p[i]-1)/2-1;
ans+=std::min(max_r[l],r)-l+1;
}else{
int l=i/2-1,r=l+p[i]/2-1;
ans+=std::min(max_r[l],r)-l+1;
}
}
std::cout<<ans<<"\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin>>t;
while (t--) fc();
return 0;
}