Logo lxn 的博客

博客

标签
暂无

链表 -deepseek写学案

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-08 20:37:47

链表

一、链表基础知识

1. 链表特性

  • 动态数据结构,节点通过指针连接
  • 支持O(1)时间的插入/删除操作
  • 不支持随机访问(访问效率O(n))
  • 两种常见实现方式:数组模拟(静态链表)、STL list(双向链表)

2. 链表类型对比

实现方式 内存管理 访问效率 插入效率 竞赛适用性
数组模拟链表 预分配 O(1) O(1) 推荐
STL list 动态分配 O(n) O(1) 特殊场景

二、数组模拟链表

1. 单向链表模板

const int MAXN = 1e6+5;
struct Node {
    int val;
    int next;
} node[MAXN];
int idx; \/\/ 当前可用节点索引

\/\/ 初始化
void init() {
    node[0].next = -1; \/\/ 头节点
    idx = 1;
}

\/\/ 在pos节点后插入新节点
void insert(int pos, int x) {
    node[idx].val = x;
    node[idx].next = node[pos].next;
    node[pos].next = idx++;
}

\/\/ 删除pos节点的后继节点
void remove(int pos) {
    node[pos].next = node[node[pos].next].next;
}

2. 双向链表模板

const int MAXN = 1e6+5;
struct Node {
    int val;
    int prev, next;
} node[MAXN];
int idx;

void init() {
    node[0].next = 1;  \/\/ 头节点
    node[1].prev = 0;  \/\/ 尾节点
    idx = 2;
}

\/\/ 在pos节点右侧插入
void insert(int pos, int x) {
    node[idx] = {x, pos, node[pos].next};
    node[node[pos].next].prev = idx;
    node[pos].next = idx++;
}

\/\/ 删除pos节点
void remove(int pos) {
    node[node[pos].prev].next = node[pos].next;
    node[node[pos].next].prev = node[pos].prev;
}

三、STL list应用

1. 基本操作

#include <list>
using namespace std;

list<int> lst;
list<int>::iterator it;

lst.push_back(x);    \/\/ 尾部插入
lst.push_front(x);   \/\/ 头部插入
lst.pop_back();      \/\/ 删除尾部
lst.pop_front();     \/\/ 删除头部
it = lst.begin();    \/\/ 获取起始迭代器
advance(it, k);      \/\/ 迭代器移动k步
lst.insert(it, x);   \/\/ 在迭代器位置插入
lst.erase(it);       \/\/ 删除迭代器指向元素

四、基础例题实现

例题1:洛谷P1160 队列安排

题目描述:实现双向链表操作,支持:

  1. 将i号同学插入到k号同学左边/右边
  2. 删除指定同学

数组模拟实现

#include <iostream>
using namespace std;

const int MAXN = 1e5+5;
struct Node { int prev, next; } node[MAXN];
bool exist[MAXN];

void init() {
    node[0].next = 1;
    node[1].prev = 0;
}

void insert(int k, int x, int pos) { \/\/ pos:0左 1右
    if(pos == 0) {
        node[x].prev = node[k].prev;
        node[x].next = k;
        node[node[k].prev].next = x;
        node[k].prev = x;
    } else {
        node[x].prev = k;
        node[x].next = node[k].next;
        node[node[k].next].prev = x;
        node[k].next = x;
    }
    exist[x] = true;
}

void remove(int x) {
    if(!exist[x]) return;
    node[node[x].prev].next = node[x].next;
    node[node[x].next].prev = node[x].prev;
    exist[x] = false;
}

STL list实现

#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;

unordered_map<int, list<int>::iterator> mp;
list<int> lst;

int main() {
    lst.push_back(1);
    mp[1] = lst.begin();
    
    int n, k, p, m, x;
    cin >> n;
    for(int i=2; i<=n; ++i) {
        cin >> k >> p;
        auto it = mp[k];
        if(p == 1) ++it;
        mp[i] = lst.insert(it, i);
    }
    \/\/ ...后续删除操作类似...
    return 0;
}

例题2:洛谷P1996 约瑟夫问题

题目描述:n个人围成环,从第1个开始报数,数到m的人出列

数组模拟实现

int cur = 0;
for(int i=0; i<n; ++i){
    for(int j=1; j<m; ++j)
        cur = node[cur].next;
    cout << node[cur].next << " ";
    remove(cur);
    cur = node[cur].next;
}

STL list实现

list<int> lst;
for(int i=1; i<=n; ++i) lst.push_back(i);

auto it = lst.begin();
while(!lst.empty()){
    for(int i=1; i<m; ++i){
        ++it;
        if(it == lst.end()) it = lst.begin();
    }
    cout << *it << " ";
    it = lst.erase(it);
    if(it == lst.end() && !lst.empty()) 
        it = lst.begin();
}

五、进阶练习题

  1. 洛谷P2058 海港(STL list维护时间窗口)
  2. Codeforces 706D - Vasiliy's Multiset(双向链表维护有序集合)
  3. AtCoder ABC237E - Skiing(链表优化BFS)
  4. 洛谷P4001 [ICPC-Beijing 2006]狼抓兔子(双向链表维护平面图)
  5. Codeforces 1154E - Two Teams(双向链表快速删除)

六、使用技巧

  1. 数组模拟常用优化:

    • 使用「索引0」作为头节点
    • 预先分配足够空间(1e6级别)
    • 用exist数组标记有效节点
  2. STL list注意事项:

    • 迭代器失效问题
    • 结合unordered_map实现快速查找
    • 谨慎处理空链表和边界情况
  3. 性能对比:

    • 数组模拟:访问O(1),适合频繁修改
    • STL list:代码简洁,适合简单操作

七、常见错误

  1. 数组模拟时忘记更新相邻节点的指针
  2. STL list在循环中错误处理迭代器
  3. 没有正确处理空链表的情况
  4. 数组模拟时未初始化头尾节点
  5. 时间复杂度误判(多次遍历链表)

set\/multiset运用-deepseek写学案

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-08 20:53:26

set/multiset运用

一、容器特性对比

特性 set multiset
元素唯一性 唯一 允许重复
底层实现 红黑树 红黑树
插入复杂度 O(log n) O(log n)
查找复杂度 O(log n) O(log n)
迭代器类型 双向迭代器 双向迭代器
常用场景 维护唯一有序集合 允许重复的有序集合

二、核心操作详解

1. 基础操作

#include <set>
using namespace std;

set<int> s;
multiset<int> ms;

\/\/ 插入元素
s.insert(5);       \/\/ set返回pair<iterator, bool>
ms.insert(5);      \/\/ multiset返回iterator

\/\/ 删除元素
s.erase(5);        \/\/ 删除所有值为5的元素
ms.erase(ms.find(5)); \/\/ 仅删除第一个找到的5

\/\/ 查找操作
auto it = s.find(3);   \/\/ 找不到返回s.end()
int cnt = ms.count(5); \/\/ 统计元素出现次数

\/\/ 范围查找
auto [low, high] = s.equal_range(4); \/\/ 获取等于4的元素区间

\/\/ 特殊访问
s.begin();        \/\/ 最小元素
s.rbegin();       \/\/ 最大元素(反向迭代器)

2. 高级操作

\/\/ 合并集合(C++17)
set<int> s1, s2;
s1.merge(s2);  \/\/ 将s2元素移动到s1,重复元素保留在s2

\/\/ 提取节点(C++17)
auto nh = s.extract(3); \/\/ 获取节点但不复制
s.insert(move(nh));     \/\/ 重新插入

\/\/ 自定义排序规则
struct cmp {
    bool operator()(const string& a, const string& b) const {
        return a.length() < b.length();
    }
};
set<string, cmp> lengthSet;

三、经典例题解析

例题1:洛谷P1177 【模板】排序(去重排序)

题目要求:对n个数字进行升序排序并去重

set实现

#include <iostream>
#include <set>
using namespace std;

int main() {
    int n, x;
    set<int> s;
    cin >> n;
    while(n--) {
        cin >> x;
        s.insert(x);
    }
    cout << s.size() << endl;
    for(auto v : s) cout << v << " ";
    return 0;
}

例题2:AtCoder ABC212C - Min Difference

题目要求:在两个数组中找到最小差值

multiset优化解法

#include <bits\/stdc++.h>
using namespace std;

int main() {
    int n, m, x;
    multiset<int> s;
    
    cin >> n >> m;
    for(int i=0; i<n; ++i) {
        cin >> x;
        s.insert(x);
    }
    
    int ans = INT_MAX;
    while(m--) {
        cin >> x;
        auto it = s.lower_bound(x);
        if(it != s.end()) ans = min(ans, *it - x);
        if(it != s.begin()) ans = min(ans, x - *prev(it));
    }
    cout << ans << endl;
    return 0;
}

例题3:洛谷P3369 【模板】普通平衡树

功能需求

  1. 插入x
  2. 删除x(若有多个相同元素,只删除一个)
  3. 查询x的排名
  4. 查询排名为x的数
  5. 求x的前驱
  6. 求x的后继

multiset实现

#include <iostream>
#include <set>
#include <algorithm>
using namespace std;

multiset<int> ms;

int get_rank(int x) {
    return distance(ms.begin(), ms.lower_bound(x)) + 1;
}

int get_num(int k) {
    auto it = ms.begin();
    advance(it, k-1);
    return *it;
}

int main() {
    int q, op, x;
    cin >> q;
    while(q--) {
        cin >> op >> x;
        switch(op) {
        case 1: ms.insert(x); break;
        case 2: ms.erase(ms.find(x)); break;
        case 3: cout << get_rank(x) << endl; break;
        case 4: cout << get_num(x) << endl; break;
        case 5: 
            auto it = ms.lower_bound(x);
            cout << *(--it) << endl;
            break;
        case 6:
            cout << *ms.upper_bound(x) << endl;
            break;
        }
    }
    return 0;
}

四、进阶应用技巧

1. 维护动态中位数

https://www.luogu.com.cn/problem/U207461

题目要求:依次读入一个整数序列,每当已经读入的整数个数为奇数时,输出已读入的整数构成的序列的中位数。

multiset<int> left, right; \/\/ 左半部大顶堆,右半部小顶堆

void insert(int x) {
    if(left.empty() || x <= *left.rbegin()) 
        left.insert(x);
    else 
        right.insert(x);
    
    \/\/ 平衡两个集合
    if(left.size() > right.size()+1) {
        right.insert(*left.rbegin());
        left.erase(prev(left.end()));
    } else if(right.size() > left.size()) {
        left.insert(*right.begin());
        right.erase(right.begin());
    }
}

\/\/ 获取中位数
int get_median() {
    return *left.rbegin();
}

2. 离散化处理

\/\/ 使用set进行离散化
vector<int> arr = {5, 3, 9, 3, 7};
set<int> s(arr.begin(), arr.end());
unordered_map<int, int> mapping;
int idx = 1;
for(int v : s) mapping[v] = idx++;

五、练习题推荐

  1. 洛谷P1059 明明的随机数(set去重排序)
  2. 洛谷P2073 送花(multiset维护双关键字)
  3. AtCoder ABC245D - Polynomial division(multiset维护多项式系数)
  4. 洛谷P2286 [HNOI2004]宠物收养所(平衡树应用)
  5. 洛谷U207461 动态维护中位数(用set实现对顶堆)
  6. Codeforces 706D - Vasiliy's Multiset(异或操作+multiset维护)

六、常见错误与优化

典型错误案例

\/\/ 错误1:迭代器失效
set<int> s = {1,2,3};
for(auto it=s.begin(); it!=s.end(); ++it) {
    if(*it == 2) s.erase(it); \/\/ erase后it失效
}

\/\/ 正确写法
auto it = s.find(2);
if(it != s.end()) s.erase(it);

\/\/ 错误2:错误删除所有元素
multiset<int> ms = {1,1,2};
ms.erase(1); \/\/ 删除所有1,留下size=1

\/\/ 正确删除单个元素
ms.erase(ms.find(1));

性能优化技巧

  1. 使用emplace_hint优化插入:
auto hint = s.end();
for(int x : data) {
    hint = s.emplace_hint(hint, x);
}
  1. 批量操作优化:
vector<int> temp{5,2,8,3};
s.insert(temp.begin(), temp.end()); \/\/ 优于循环插入
  1. 利用有序性进行范围查询:
\/\/ 统计[L, R]区间内的元素数量
int count = distance(s.lower_bound(L), s.upper_bound(R));

map\/multimap-deepseek 学案

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-08 21:12:30

map/multimap运用

一、容器特性对比

特性 map multimap
键唯一性 唯一 允许重复键
底层实现 红黑树 红黑树
插入复杂度 O(log n) O(log n)
查找复杂度 O(log n) O(log n)
迭代器类型 双向迭代器 双向迭代器
典型应用场景 字典、哈希表替代品 一对多关系存储

二、核心操作详解

1. 基本操作

#include <map>
using namespace std;

map<string, int> mp;
multimap<int, string> mmp;

\/\/ 插入元素
mp["Alice"] = 95;                   \/\/ 直接赋值
mp.insert({"Bob", 88});             \/\/ 插入pair对象
mmp.emplace(90, "Charlie");         \/\/ 原地构造

\/\/ 删除元素
mp.erase("Alice");                  \/\/ 按key删除
mmp.erase(mmp.find(90));            \/\/ 按迭代器删除

\/\/ 查找操作
auto it = mp.find("Bob");           \/\/ 返回迭代器
int cnt = mmp.count(90);            \/\/ 统计key出现次数

\/\/ 范围迭代
auto [start, end] = mmp.equal_range(85); \/\/ 获取键值范围

2. 高级操作

\/\/ 自定义排序规则
struct cmp {
    bool operator()(const string& a, const string& b) const {
        return a.length() < b.length();
    }
};
map<string, int, cmp> lenMap;

\/\/ 合并map(C++17)
map<int, int> m1, m2;
m1.merge(m2);  \/\/ 合并后m2保留冲突键值

\/\/ 节点操作(C++17)
auto nh = mp.extract("Alice");      \/\/ 提取节点
mp.insert(move(nh));                \/\/ 重新插入

三、经典例题解析

例题1:POJ2503 Babelfish(字典查询)

题目要求:实现双语词典的单词翻译功能

map实现

#include <iostream>
#include <map>
#include <sstream>
using namespace std;

map<string, string> dict;

int main() {
    string line, en, foreign;
    while(getline(cin, line) && !line.empty()) {
        istringstream iss(line);
        iss >> en >> foreign;
        dict[foreign] = en;
    }
    
    while(cin >> foreign) {
        auto it = dict.find(foreign);
        if(it != dict.end()) 
            cout << it->second << endl;
        else 
            cout << "eh" << endl;
    }
    return 0;
}

例题2:洛谷P3374 【模板】树状数组 1(map模拟解法)

题目要求:实现单点修改、前缀求和操作

map优化解法

#include <iostream>
#include <map>
using namespace std;

map<int, int> tree;  \/\/ 离散化存储非零位置

void update(int pos, int val) {
    while(pos <= n) {
        tree[pos] += val;
        pos += pos & -pos;
    }
}

int query(int pos) {
    int sum = 0;
    while(pos > 0) {
        sum += tree[pos];
        pos -= pos & -pos;
    }
    return sum;
}

例题3:AtCoder ABC245D - Polynomial division(多项式除法)

题目要求:给定多项式A(x)*B(x)=C(x),已知A和C求B

map存储系数解法

#include <bits\/stdc++.h>
using namespace std;

map<int, int> read_poly(int n) {
    map<int, int> res;
    for(int i=0; i<=n; ++i) {
        int c; cin >> c;
        if(c != 0) res[i] = c;
    }
    return res;
}

int main() {
    int n, m;
    cin >> n >> m;
    auto A = read_poly(n);
    auto C = read_poly(n+m);
    
    map<int, int> B;
    for(int k=m; k>=0; --k) {
        int sum = 0;
        for(auto [i,a] : A) {
            if(B.find(k-i) != B.end())
                sum += a * B[k-i];
        }
        B[k] = (C[k] - sum) \/ A.begin()->second;
    }
    \/\/ ...输出B的系数...
    return 0;
}

四、进阶应用技巧

1. 离散化坐标压缩

vector<int> coords = {100, 5, 200, 5, 100};
map<int, int> mapping;
int idx = 1;
for(auto x : coords)
    if(!mapping.count(x))
        mapping[x] = idx++;

2. 时间线管理(扫描线算法)

map<int, int> timeline;  \/\/ 时间戳 -> 事件数量

void add_event(int t) {
    timeline[t]++;
}

void process_events() {
    auto it = timeline.begin();
    while(it != timeline.end()) {
        int t = it->first;
        int cnt = it->second;
        \/\/ 处理t时刻的cnt个事件
        it = timeline.erase(it);
    }
}

五、练习题推荐

  1. 洛谷P1276 校门外的树(增强版)(区间状态维护)
  2. POJ3481 Double Queue(双优先队列模拟)
  3. AtCoder ABC217D - Cutting Woods(坐标离散化)
  4. 洛谷P4056 [JSOI2009] 火星藏宝图(动态规划状态优化)
  5. Codeforces 4C - Registration System(用户名计数)

六、常见错误与优化

典型错误案例

\/\/ 错误1:直接访问不存在的键
map<string, int> mp;
cout << mp["unknown"];  \/\/ 自动插入默认值0

\/\/ 正确做法
if(mp.count("unknown")) 
    cout << mp["unknown"];

\/\/ 错误2:错误删除范围
multimap<int, int> mmp;
mmp.erase(5);  \/\/ 删除所有key=5的元素

\/\/ 正确删除单个元素
auto it = mmp.find(5);
if(it != mmp.end()) mmp.erase(it);

性能优化技巧

  1. 预分配内存(适用于已知规模):
map<int, int> mp;
mp.reserve(1e5);  \/\/ C++23+支持
  1. 批量插入优化:
vector<pair<int, int>> data{{1,2},{3,4}};
mp.insert(data.begin(), data.end());
  1. 利用有序性进行范围统计:
\/\/ 统计[key1, key2)区间的value总和
auto it1 = mp.lower_bound(key1);
auto it2 = mp.lower_bound(key2);
int sum = accumulate(it1, it2, 0, 
    [](int s, auto& p){ return s + p.second; });
  1. 使用emplace替代insert:
mp.emplace(5, 10);  \/\/ 避免临时对象构造

vector、list 和 deque对比-deepseek学案

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-09 08:02:08

在 C++ STL 中,vectorlistdeque 是三种常用的顺序容器,它们的相同点与不同点对比如下:


相同点

  1. 动态容器
    均支持动态扩容/缩容,无需手动管理内存,容量随元素增减自动变化。
  2. 顺序存储
    元素按插入顺序存储(逻辑连续),支持顺序遍历。
  3. 迭代器支持
    提供随机访问迭代器(vectordeque)或双向迭代器(list),支持 begin()/end() 遍历。
  4. 通用操作
    支持 push_back()pop_back()insert()erase()size()empty() 等基本操作。

不同点

特性 vector list deque
底层结构 动态数组 双向链表 分块数组(多个连续块)
随机访问 ✅ O(1) ❌ O(n) ✅ O(1)(接近 vector)
中间插入/删除 ❌ O(n)(需移动元素) ✅ O(1)(已知迭代器位置) ❌ O(n)(需移动元素)
头尾插入/删除 ✅ 尾端 O(1),头端 O(n) ✅ 头尾 O(1) ✅ 头尾 O(1)
内存连续性 ✅ 完全连续 ❌ 非连续(节点分散) ❌ 逻辑连续,物理分块
内存分配策略 扩容时重新分配整个数组 按需分配单个节点 按需分配内存块(无需全拷贝)
迭代器失效场景 扩容后全部失效 删除时仅当前元素迭代器失效 中间插入/删除可能导致失效
内存占用 最低(仅元素内存) 较高(含前后指针) 中等(含块指针表)

核心差异总结

  1. 访问模式

    • vector:适合频繁随机访问(如 v[i])和尾部操作。
    • deque:适合频繁头尾插入/删除,随机访问性能接近 vector
    • list:适合频繁中间插入/删除,但无法快速随机访问。
  2. 内存效率

    • vector:内存紧凑,缓存友好,但扩容成本高。
    • list:内存碎片化,但无扩容开销。
    • deque:折中设计,兼顾头尾操作和随机访问。
  3. 迭代器稳定性

    • list 的迭代器最稳定(除非元素被删除)。
    • vector 扩容后所有迭代器失效。
    • deque 在中间修改时可能局部失效。

典型应用场景

  • vector:需要快速随机访问(如数值计算、动态数组)。
  • list:需要频繁中间插入/删除(如 LRU 缓存链表)。
  • deque:需要高效头尾操作(如任务队列、滑动窗口)。

根据具体需求选择容器,可显著优化程序性能和内存效率。

P2239 [NOIP2014 普及组] 螺旋矩阵 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-15 14:39:52

转载:https://www.luogu.com.cn/article/ur0hlq2a

P2239 [NOIP2014 普及组] 螺旋矩阵 讲解

题目 数学题。

$50$ 分做法:

先暴力做出这个 $n\times n$ 的矩阵,最后直接输出。 时间复杂度为 $O(n^2)$,$n\in[1,30000]$,超时,考虑优化。

$O(n)$ 做法:

我们找出了所有的 $a_{i,j}$,但只输出了一个,浪费了大量时间。 参考以下蛇形方阵: $$\begin{bmatrix}1&2&3&4&5\&17&18&19&6\ &24&25&20&7\ &23&22&21&8\ &12&11&10&9\end{bmatrix}$$ 直接推式子有些难推,我们分类讨论:

  • $i=1,j\in(1,n):a_{i,j}=j$
  • $i\in(1,n),j=n:a_{i,j}=n+i-1$
  • $i=n,j\in(1,n):a_{i,j}=3n-j-1$
  • $i\in(1,n),j=1:a_{i,j}=4n-i-2$

不是以上情况怎么办呢? 我们给矩阵缩小一下: $$\begin{bmatrix}1&2&3\\8&9&4\&6&5\end{bmatrix}$$ 可以看到,中间的九个元素都减少了 $16$,也就是 $4(n-1)$。 那么设这个函数为 $f(n,i,j)$,最后一种情况为:

  • $i\in(1,n),j\in(1,n):f(n-2,i-1,j-1)+4(n-1)$ 总体复杂度为 $O(n)$,可以通过。 代码:
#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 0x2a2a2a2a
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n,i,j;
inl int find(int n,int i,int j){
	if(i==1) return j;
	if(j==n) return n+i-1;
	if(i==n) return (n<<1)+n-j-1;
	if(j==1) return (n<<2)-i-2;
	return find(n-2,i-1,j-1)+((n-1)<<2);
}
signed main(){
	FAST;
	cin>>n>>i>>j;
	cout<<find(n,i,j);
	return 0;
}

$O(1)$ 做法:

虽说已经可以通过此题,但如果 $n$ 的规模再大一些,达到 $n\in[1,10^9]$,那就必须 $O(1)$ 解决了。 我们找一个更大的矩阵: $$\begin{bmatrix}1&2&3&4&5&6&7&8\8&29&30&31&32&33&34&9\&48&49&50&51&52&35&10\&47&60&61&62&53&36&11\&46&59&64&63&54&37&12\&45&58&57&56&55&38&13\&44&43&42&41&40&39&14\&21&20&19&18&17&16&15\end{bmatrix}$$ 一轮缩小,减少了 $4\times(8-1)=28$。 二轮缩小,减少了 $4\times(6-1)=20$。 三轮缩小,减少了 $4\times(4-1)=12$。 $b=\{28,20,12\}$,翻转一下变成 $b'=\{12,20,28\}$,可以看出这是一个等差数列。 很明显会缩小 $x=min(i,j,n-i+1,n-j+1)$ 次。 $b'$ 的末项是 $4(n-1)$,公差是 $8$,首项就是 $4(n-1)-8(x-2)$,得 $b_{sum}$: $$b_{sum}=\frac{(4(n-1)-8(x-2)+4(n-1))\times(x-1)}{2}$$ 总体复杂度为 $O(1)$。 代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 0x2a2a2a2a
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n,i,j,x,y;
signed main(){
	FAST;
	cin>>n>>i>>j;
	x=min(min(i,n-i+1),min(j,n-j+1));
	y=((((n-((x-2)<<1)-1)<<2)+((n-1)<<2))*(x-1))>>1;
	i-=x-1;
	j-=x-1;
	n-=((x-1)<<1);
	if(i==1) cout<<y+j;
	else if(n-j+1==1) cout<<y+n+i-1;
	else if(n-i+1==1) cout<<y+(n<<1)+n-j-1;
	else cout<<y+(n<<2)-i-2;
	return 0;
}

区间覆盖问题的贪心模型详解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-10 08:22:33

1. 区间覆盖问题的基本模型

模型一:选择不相交区间(最大不相交区间数)

问题描述:给定n个区间,选择尽可能多的区间,使得这些区间互不相交。

贪心策略:按区间的右端点从小到大排序

算法步骤

1. 将所有区间按右端点从小到大排序
2. 初始化:count = 0, last_end = -∞
3. 遍历每个区间[l, r]:
   - 如果 l >= last_end:
     - count++
     - last_end = r
4. 返回count

证明思路

  • 选择右端点最小的区间可以为后续留下更多空间
  • 任何最优解都可以通过替换变成贪心解

习题

P1803 凌乱的yyy / 线段覆盖

模型二:区间完全覆盖

问题描述:用最少的区间覆盖指定的线段[L, R]

贪心策略:按左端点排序,每次选择能覆盖当前起点且右端点最大的区间

算法步骤

1. 按左端点从小到大排序
2. 初始化:count = 0, current = L, i = 0
3. while current < R:
   - 在左端点<=current的区间中,选择右端点最大的
   - 如果找不到这样的区间:返回-1(无法覆盖)
   - count++, current = max_right
   - 如果current >= R: 返回count

习题

  • [POJ 2376] Cleaning Shifts UVa 10382 /LOJ10002 https://loj.ac/p/10002

模型三:区间点覆盖(用最少的点覆盖所有区间)

问题描述:用最少的点,使得每个区间至少包含一个点

贪心策略:按右端点排序,在右端点处放点

算法步骤

1. 按右端点从小到大排序
2. 初始化:count = 0, point = -∞
3. 遍历每个区间[l, r]:
   - 如果 l > point:
     - count++
     - point = r
4. 返回count

证明思路

  • 在右端点放点可以覆盖尽可能多的后续区间

习题

  • [LeetCode 452] 用最少数量的箭引爆气球
  • [USACO] 区间点覆盖

例题代码

\/\/ LeetCode 452. 用最少数量的箭引爆气球
class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.empty()) return 0;
        
        sort(points.begin(), points.end(), 
             [](const vector<int>& a, const vector<int>& b) {
                 return a[1] < b[1];
             });
        
        int arrows = 1;
        int arrow_pos = points[0][1];
        
        for (int i = 1; i < points.size(); i++) {
            if (points[i][0] > arrow_pos) {
                arrows++;
                arrow_pos = points[i][1];
            }
        }
        
        return arrows;
    }
};

模型四:区间分组(最小分组数)

问题描述:将区间分成若干组,每组内区间互不相交,求最少组数

贪心策略:按左端点排序,用小根堆维护每组的最大右端点

算法步骤

1. 按左端点从小到大排序
2. 初始化小根堆(存储每组的最大右端点)
3. 遍历每个区间[l, r]:
   - 如果堆为空或堆顶 > l:
     - 新开一组,将r加入堆
   - 否则:
     - 更新堆顶为r(将该区间加入堆顶对应的组)
4. 返回堆的大小

习题: P2859 [USACO06FEB] Stall Reservations S

  • P7913 [CSP-S 2021] 廊桥分配
  • [LeetCode 253] 会议室 II

2. 经典竞赛真题

真题1:[POJ 1328] Radar Installation

题目:在x轴上放置雷达,每个雷达覆盖半径为d,求覆盖所有岛屿的最少雷达数

转换:将岛屿位置转换为x轴上的覆盖区间

对于岛屿(x,y),雷达能覆盖的x轴区间为:
[x - sqrt(d*d - y*y), x + sqrt(d*d - y*y)]

解法:区间点覆盖模型

真题2:P2859 [USACO06FEB] Stall Reservations S

题目:n头奶牛,每头有挤奶时间区间,求最少牛栏数

解法:区间分组模型

3. 进阶变形问题

变形1:带权区间调度

问题:每个区间有权值,选择不相交区间使总权值最大

解法:DP + 二分查找,不是纯贪心

  • P2439 [SDOI2005] 阶梯教室设备利用

变形2:区间合并

问题:合并所有重叠区间 解法:按左端点排序后合并

  • P2082 区间覆盖(加强版)

4. 练习题分类

基础练习

  1. 选择不相交区间

    • [LeetCode 435] 无重叠区间
    • [HDU 2037] 今年暑假不AC
  2. 区间完全覆盖

    • [POJ 2376] Cleaning Shifts
    • [LeetCode 1326] 灌溉花园的最少水龙头数目
  3. 区间点覆盖

    • [LeetCode 452] 用最少数量的箭引爆气球
    • [POJ 1328] Radar Installation
  4. 区间分组

    • [LeetCode 253] 会议室 II
    • [POJ 3190] Stall Reservations
    • AT_abc377_dMany Segments 2

进阶练习

  1. [Codeforces 1029C] Maximal Intersection

    • 删除一个区间使剩余区间交集最大
  2. [Atcoder ABC 103D] Robot Arms

    • 区间点覆盖的变形
  3. [USACO 2014 Jan] Ski Course Rating

    • 并查集+贪心思想
  4. [NOIP 2005] 校门外的树

    • 区间合并应用

5. 解题技巧总结

排序选择技巧

  • 选择不相交区间:按右端点排序
  • 区间完全覆盖:按左端点排序
  • 区间点覆盖:按右端点排序
  • 区间分组:按左端点排序

证明方法

  1. 交换论证:证明贪心选择不会比最优解差
  2. 归纳法:证明前k步都是最优的
  3. 反证法:假设存在更优解,推导矛盾

调试技巧

  • 画图理解区间关系
  • 构造边界测试用例
  • 与暴力解法对拍

通过系统练习这些模型,能够快速识别区间覆盖问题的类型并选择正确的贪心策略。记住核心思想:排序是贪心的前提,正确的排序方式决定了贪心策略的有效性

P1535 [USACO08MAR] Cow Travelling S -搜索或dp

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-05-17 10:23:06

题目大意:在一个迷宫上从 $a$ 点到 $b$ 恰好走 $t$ 步的方案数有多少? 数据范围: $n,m\leq100,t\leq15$

方法一,普通搜索

从终点出发,前起点方向走,如果能到起点就是一中方案。统计所有方案。得分 $48$, 超时。

#include <bits\/stdc++.h>
using namespace std;
int n, m, t, r1[2], r2[2], cnt = 0;
int Fx[4][2] = {{-1,0},{0,-1},{0,1},{1,0}};
bool grid[512][512];
void dfs(int x, int y, int tk) {
	if(tk == 0)	{ if(x == r1[0] && y == r1[1]) cnt++; return; }
	for(auto &fx : Fx)
	{
		int x_n = x + fx[0], y_n = y + fx[1];
		if(x_n < 1 || y_n < 1 || x_n > n || y_n > m || grid[x_n][y_n])
			continue;
		dfs(x_n, y_n, tk-1);
	}
}
int main() {
	cin >> n >> m >> t;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
		{
			char tmp; cin >> tmp;
			grid[i][j] = tmp == '*';
		}
	cin >> r1[0] >> r1[1] >> r2[0] >> r2[1];
	dfs(r2[0], r2[1], t);
	cout << cnt << endl;
	return 0;
}

方法二,考虑记忆化,步数总是从小到大的,如果前期已经处理过,直接拿来用即可。

设置记录步数的数组st,初始化为-1.设计起点的初值为1. 当一个点的四个方向处理完成后,这个点就没有必要再重复访问。 为什么初始设置为-1呢?这相当于访问标记。有的点路径数是0,如果初值为0,那么这些无效点可能会重复计算。

#include <bits\/stdc++.h>
using namespace std;
int n, m, t, r1[2], r2[2], cnt = 0;
int Fx[4][2] = {{-1,0},{0,-1},{0,1},{1,0}};
bool grid[512][512];
int st[112][112][20];
int dfs(int x, int y, int tk) {
	if(st[x][y][tk] !=-1)return st[x][y][tk];
	if(tk == -1)	{  return 0; }
	if(abs(r1[0]-x)+abs(r1[1]-y)>tk)return 0;\/\/可行性剪枝。 
	int tmp=0;
	for(auto &fx : Fx)
	{
		int x_n = x + fx[0], y_n = y + fx[1];
		if(x_n < 1 || y_n < 1 || x_n > n || y_n > m || grid[x_n][y_n])
			continue;		
		tmp+=dfs(x_n, y_n, tk-1);		
	}
	return st[x][y][tk]=tmp;
}
int main() {
	cin >> n >> m >> t;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
		{
			char tmp; cin >> tmp;
			grid[i][j] = tmp == '*';
		}
	cin >> r1[0] >> r1[1] >> r2[0] >> r2[1];
	memset(st,-1,sizeof(st));
	st[r1[0]][r1[1]][0]=1;
	dfs(r2[0], r2[1], t);
	cout << st[r2[0]][r2[1]][t] << endl;
	return 0;
}

方法4 DFS能写,bfs也一样可以写。

在处理入队的过程中,一个点可能从多个方向处理几次,但只需要入队一次,可以通过访问标记或者st的数值来标记入队情况,避免重复入队。

bfs总是中按照0,1,2这样的顺利来处理的。

#include <bits\/stdc++.h>
using namespace std;
int n, m, k, r1[2], r2[2], cnt = 0;
int Fx[4][2] = {{-1,0},{0,-1},{0,1},{1,0}};
bool grid[112][112],vis[112][112][20];
int st[112][112][20];
struct node{
	int x,y,st;
};
queue<node>q;
void bfs(){
	q.push({r1[0],r1[1],0});	
	st[r1[0]][r1[1]][0]=1;
	vis[r1[0]][r1[1]][0]=1;
	while(q.size()){
		node t=q.front();		
		q.pop();
		if(t.st==k) continue;
		for(int i=0;i<4;i++){
			int tx=t.x+Fx[i][0],ty=t.y+Fx[i][1],ts=t.st+1;
			if(tx>n||ty>m||tx<1||ty<1||grid[tx][ty])continue;			
			st[tx][ty][ts]+=st[t.x][t.y][t.st];
			if(!vis[tx][ty][ts])q.push({tx,ty,ts}),vis[tx][ty][ts]=1;	
		}		
	}	
}
int main() {
	cin >> n >> m >> k;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
		{
			char tmp; cin >> tmp;
			grid[i][j] = tmp == '*';
		}
	cin >> r1[0] >> r1[1] >> r2[0] >> r2[1];
	bfs();
	cout << st[r2[0]][r2[1]][k] << endl;
	return 0;
}

方法四 动态规划

能用记忆化搜索来完成的题目基本可以动态规划。动态规划的特点是无后效性,本题只要通过步数来划分阶段,虽然结点间相互可达,但也有了先后关系。

设计状态为 $st[i][j][l]$ 表示达到$(i,j)$用了 $l$ 步的方案数。以步数为阶段扩展即可。

\/\/DP写法 
#include <bits\/stdc++.h>
using namespace std;
int n, m, t, r1[2], r2[2], cnt = 0;
int Fx[4][2] = {{-1,0},{0,-1},{0,1},{1,0}};
bool grid[512][512];
int st[112][112][20];

int main() {
	cin >> n >> m >> t;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++) {
			char tmp;
			cin >> tmp;
			grid[i][j] = tmp == '*';
		}
	cin >> r1[0] >> r1[1] >> r2[0] >> r2[1];
\/\/	memset(st,-1,sizeof(st));
	st[r1[0]][r1[1]][0]=1;
	for(int l=1; l<=t; l++)
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++) {
				for(auto &fx : Fx) {
					int x_n = i + fx[0], y_n = j + fx[1];
					if(x_n < 1 || y_n < 1 || x_n > n || y_n > m || grid[x_n][y_n]||!st[x_n][y_n][l-1])
						continue;
					st[i][j][l]+=st[x_n][y_n][l-1];
				}

			}

	cout << st[r2[0]][r2[1]][t] << endl;
	return 0;
}
\/*
10 10 7
**........
..**......
*.........
.*.*....*.
..........
..*.**....
..........
.**..*....
......**..
...***....
5 4 7 3
*\/

p1432-倒水问题

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-05-23 14:44:08

题解里面写的都太啰嗦了,要利用函数来处理重复问题

#include<bits\/stdc++.h>  
using namespace std;
const int N=1000009;
char qz[N];
int qy[N],qx[N],qf[N],step1[N],head=0,tail=0,pas[N];
bool v[1009][1009];
 
int x,y,z,flag=0;
void inser(int tx,int ty,int step2,char cz,int fa) {
    if(tx<0||ty<0||tx>x||ty>y)return;
    if(v[tx][ty])return;
    tail++;
    qx[tail]=tx,qy[tail]=ty,qz[tail]=cz,qf[tail]=fa;
    step1[tail]=step2;
    v[tx][ty]=1;
}
void bfs(){
    memset(v,0,sizeof(v));
    cin>>x>>y>>z;
    int cx,cy,step3;
    head=0;
    tail=0;flag=0;
    while(head<=tail) {
        cx=qx[head];
        cy=qy[head];
        step3=step1[head];
        
        v[cx][cy]=1;
        if(cy==z) {
            cout<<step3<<" ";
            flag=head;
            for(int j=step3;j>0;j--)
                pas[j]=flag,flag=qf[flag];
            for(int j=1;j<=step3;j++)
                printf("%c ",qz[pas[j]]);
            cout<<endl;
            break;
        }
        inser(x,cy,step3+1,'1',head);
        inser(0,cy,step3+1,'3',head);
        inser(cx,y,step3+1,'2',head);
        inser(cx,0,step3+1,'4',head);
        inser(0,cy+cx,step3+1,'6',head);
        inser(cx-y+cy,y,step3+1,'6',head);
        inser(cx+cy,0,step3+1,'5',head);
        inser(x,cy-x+cx,step3+1,'5',head);
        head++;
    }
    
}
int main() 
{
    int t;
    cin>>t;
    while(t--)
        bfs();
    
    return 0;
}

【78】-周测10

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-05-27 08:23:59

A: The Same Calendar

题面翻译

给你一个年份$ N$,给出一个大于$ N$的年份$Y$,有如下两个要求

1.天数与份$ N$相同
2.该年的第一天的星期与份$ N$年相同

闰年有$366$天。整除$4$且不整除$100$的是闰年,整除$400$的也是闰年

样例 #1

样例输入 #1

2016

样例输出 #1

2044

样例 #2

样例输入 #2

2000

样例输出 #2

2028

样例 #3

样例输入 #3

50501

样例输出 #3

50507

数据范围

$100\%$的数据范围:$Y\leq 100,000$

提示

Today is Monday, the $ 13 $ th of June, $ 2016 $ .

B:Make Bipartite

题面翻译

给定一个 $N$ 个点,$M$ 条边的无向图,求问有多少对还未经连接的点对满足在连接它们后,该图为一个二分图.

注意这里点对 $(u,v)$ 和点对 $(v,u)$ 是同一对点对。

数据保证没有自环与重边。

输入格式

第一行两个数$N,M$,接下来$M$行,每行两个数 $(u,v)$,表示一条无向边。

输出格式

一个整数,表示选择方案总数。

样例 #1

样例输入 #1

5 4
4 2
3 1
5 2
3 2

样例输出 #1

2

样例 #2

样例输入 #2

4 3
3 1
3 2
1 2

样例输出 #2

0

样例 #3

样例输入 #3

9 11
4 9
9 1
8 2
8 3
9 2
8 4
6 7
4 6
7 5
4 5
7 8

样例输出 #3

9

数据范围

$100\%$的数据范围:$ 2\ \leq\ N\ \leq\ 2\ \times\ 10^5 $,$ 0\ \leq\ M\ \leq\ \min\ \lbrace\ 2\ \times\ 10^5,\ N(N-1)/2\ \rbrace $, $ 1\ \leq\ u_i,\ v_i\ \leq\ N $,图为简单图。

C:Joty and Chocolate

题面翻译

Joty 有 $n$ 块瓷砖,编号为 $1,2,3,\cdots ,n$ ,Joty 可以把编号为 $a$ 的倍数的瓷砖刷成红色,同时她会得到 $p$ 块巧克力;她也可以把编号为 $b$ 的倍数的瓷砖刷成蓝色,同时她会得到 $q$ 块巧克力;如果这个瓷砖是 $a,b$ 的公倍 数,则她可以任选一个颜色并得到对应的巧克力报酬,也只能选择一种,并得到 对应的$p$或$q$ 块巧克力。

输入一行5个数据,$n,a,b,p,q$ 。

输出一个数据:表示 Joty 能得到的巧克力最多有多少?

输入格式

The only line contains five integers $ n $ , $ a $ , $ b $ , $ p $ and $ q $ ( ).

输出格式

Print the only integer $ s $ — the maximum number of chocolates Joty can get.

Note that the answer can be too large, so you should use $ 64 $ -bit integer type to store it. In C++ you can use the long long integer type and in Java you can use long integer type.

样例 #1

样例输入 #1

5 2 3 12 15

样例输出 #1

39

样例 #2

样例输入 #2

20 2 3 3 5

样例输出 #2

51

数据范围

$100\%$的数据范围:$Y\leq 100,000$ 1<=n,a,b,p,q<=10^{9} $

D:Iterated Linear Function

题面翻译

定义函数$g$:

①$g^0(x)=x$

②$g^n(x)=Ag^{n-1}(x)+B(n≠0)$

请求出$g^{n}(x)$的值。由于答案可能过大,请将其对$10^9+7$取模

输入格式

一行,给定的两个整数s $ A $ , $ B $.

输出格式

一个整数$ s $ ,表示 $ g^{(n)}(x) $ modulo $ 10^{9}+7 $的结果 .

样例 #1

样例输入 #1

3 4 1 1

样例输出 #1

7

样例 #2

样例输入 #2

3 4 2 1

样例输出 #2

25

样例 #3

样例输入 #3

3 4 3 1

样例输出 #3

79

数据范围

$ n $ and $ x $ ( $ 1<=A,B,x<=10^{9},1<=n<=10^{18} $ )

E: Another Sith Tournament

题面翻译

你是一位骑士,与其他n-1个骑士同时爱上了LKJ,所以你们不得不通过决斗的方式来选出谁能最终得到LKJ。

幸运的是你知道任意骑士i击败骑士j的概率,你还被推选为组织委员。决斗一开始你需要任意选择两名骑士(包括自己)进行决斗,胜利方继续和你另外选择的一名骑士决斗,直到仅剩一人,最终的胜利者将得到LKJ。

你非常渴望取得胜利,想知道自己得到LKJ的最大概率是多少,注意你是1号。


第一行一个正整数$n$,表示一共有$n$名骑士。

接下来是一个$n * n$的实数矩阵,$A_{ij}$ 表示$i$战胜$j$的概率。

保证$ A_{ii} $为 0,且 $A_{ij}+A_{ji}=1$


输出一个数,表示你得到LKJ的概率,误差在$10^{-6}$以内。

题目描述

The rules of Sith Tournament are well known to everyone. $ n $ Sith take part in the Tournament. The Tournament starts with the random choice of two Sith who will fight in the first battle. As one of them loses, his place is taken by the next randomly chosen Sith who didn't fight before. Does it need to be said that each battle in the Sith Tournament ends with a death of one of opponents? The Tournament ends when the only Sith remains alive.

Jedi Ivan accidentally appeared in the list of the participants in the Sith Tournament. However, his skills in the Light Side of the Force are so strong so he can influence the choice of participants either who start the Tournament or who take the loser's place after each battle. Of course, he won't miss his chance to take advantage of it. Help him to calculate the probability of his victory.

输入格式

The first line contains a single integer $ n $ ( $ 1<=n<=18 $ ) — the number of participants of the Sith Tournament.

Each of the next $ n $ lines contains $ n $ real numbers, which form a matrix $ p_{ij} $ ( $ 0<=p_{ij}<=1 $ ). Each its element $ p_{ij} $ is the probability that the $ i $ -th participant defeats the $ j $ -th in a duel.

The elements on the main diagonal $ p_{ii} $ are equal to zero. For all different $ i $ , $ j $ the equality $ p_{ij}+p_{ji}=1 $ holds. All probabilities are given with no more than six decimal places.

Jedi Ivan is the number $ 1 $ in the list of the participants.

输出格式

Output a real number — the probability that Jedi Ivan will stay alive after the Tournament. Absolute or relative error of the answer must not exceed $ 10^{-6} $ .

样例 #1

样例输入 #1

3
0.0 0.5 0.8
0.5 0.0 0.4
0.2 0.6 0.0

样例输出 #1

0.680000000000000

【2024暑假】-普及2

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-05 18:35:05

A:Bronze Count

时间限制 3.00s 内存限制 512.00MB

题目描述

一次比赛结束后,您可能迫切地想知道有多少参赛者获得了铜牌?

金牌会授予所有获得最高分的参赛者。银牌会授予获得第二高分的所有参赛者。铜牌会授予获得第三高分的所有参赛者。

给定一份参赛选手的成绩列表,请求出获得铜牌所需的分数以及有多少人正好得到这一分数。

输入格式

输入的第一行包含一个正整数 $N$ 表示参赛者的数量。

接下来 $N$ 行包含一个整数表示一个参赛者的分数。

保证每个分数都在 $0$ 到 $75$ 之间(包含)并且至少存在三个不同的分数。

输出格式

输出一个非负整数 $S$ 和一个正整数 $P$ 用空格隔开。其中 $S$ 是得到铜牌所需要的分数,$P$ 是正好得到这个分数的参赛者数量。

样例 #1

样例输入 #1

4
70
62
58
73

样例输出 #1

62 1

样例 #2

样例输入 #2

8
75
70
60
70
70
60
75
70

样例输出 #2

60 2

提示

【样例 1 解释】

得到铜牌需要 $62$ 分并且有一个参赛者正好得到这一分数。

【样例 2 解释】

得到铜牌需要 $60$ 分并且有两个参赛者正好得到这一分数。

【数据范围】

本题采用捆绑测试。

对于所有数据,保证 $1\leq N\leq 2.5\times 10^5$,分数 $s$ 满足 $0\leq s\leq 75$,至少存在三个不同的分数。

下面的表格显示了 $15$ 分的分配方案:

分值 描述 范围
$6$ 分数互不相同并且参赛者数量很少。 $N \leq 50$
$7$ 分数可能存在相同并且参赛者数量很少。 $N \leq 50$
$2$ 分数可能存在相同并且参赛者数量可以很大。 $N \leq 2.5 \times 10^5$

B: Troublesome Keys

时间限制 3.00s 内存限制 512.00MB

题目描述

Alex 的键盘很奇怪。有两个字母按键出现了问题:

  • 键盘上有一个按键,每次按下它的时候总是显示另一个错误的字母。Alex 把这个按键称为愚蠢的按键。奇怪的是,Alex 从来没有尝试过输入这个错误的字母。
  • 键盘上还有一个按键,按下它的时候则不会显示任何内容。Alex 把这个按键称为安静的按键。

Alex 至少按下了一次愚蠢的按键,但不一定按下了安静的按键。

你需要确定出现问题的按键和按下它时显示的错误的字母。幸运的是,这是可能的,因为 Alex 从来没有在按下愚蠢的按键之后立即按下安静的按键,也没有再按下安静的按键之后立即按下愚蠢的按键。

输入格式

输入共两行。输入的第一行包含 Alex 按下的 $N$ 个按键。第二行包含屏幕上显示的字母。

输出格式

输出共两行。

第一行输出用空格分开的两个字母表示愚蠢的按键和按下时显示的错误字母。

第二行输出一个字符,如果安静的按键被按下,输出安静的按键,否则输出一个短横线(-)。

样例 #1

样例输入 #1

forloops
fxrlxxps

样例输出 #1

o x
-

样例 #2

样例输入 #2

forloops
fxrlxxp

样例输出 #2

o x
s

样例 #3

样例输入 #3

forloops
frlpz

样例输出 #3

s z
o

提示

【样例 1 解释】

与愚蠢的按键对应的字母是 o,每次按下会显示错误的字母 x。安静的按键没有被按下过。

【样例 2 解释】

与愚蠢的按键对应的字母是 o,每次按下会显示错误的字母 x。没有显示的安静的按键对应的字母是 s

【样例 3 解释】

与愚蠢的按键对应的字母是 s,每次按下会显示错误的字母 z。没有显示的安静的按键对应的字母是 o

【数据范围】

本题采用捆绑测试。

对于所有数据,保证输入中每行都只包含小写字母,$1\leq N\leq 5\times 10^5$。

下面的表格显示了 $15$ 分的分配方案:

分值 描述 范围
$3$ 安静的按键没有被按下过,按键次数很少。 $N \leq 50$
$3$ 按下的第一个有问题的按键是愚蠢的按键,按键次数很少。 $N \leq 50$
$5$ 按下的第一个有问题的按键可能是愚蠢的按键或者安静的按键,按键次数很少。 $N \leq 50$
$4$ 按下的第一个有问题的按键可能是愚蠢的按键或者安静的按键,按键次数可能很多。 $N \leq 5 \times 10^5$

C: Harvest Waterloo

时间限制 3.00s 内存限制 512.00MB

题目描述

有一款新出现的广受欢迎的收割模拟游戏叫做 Harvest Waterloo。游戏在一块矩形南瓜地上进行,南瓜地里有成捆的干草和不同大小的南瓜。游戏开始时,一个农民在其中一个南瓜的位置上。

农民通过在整片土地上向左、向右、向上或向下移动来收割南瓜。农民不能斜着移动,不能穿过干草,也不能离开田地。

你的工作是确定农民收获的南瓜的总价值。其中一个小南瓜值 $1$ 美元,一个中等大小的南瓜值 $5$ 美元,而一个大南瓜值 $10$ 美元。

输入格式

输入的第一行是一个整数 $R > 0$ 表示南瓜地的行数。

第二行是一个整数 $C > 0$ 表示南瓜地的列数。

接下来 $R$ 行描述了整个南瓜地。每行包含 $C$ 个字符并且每个字符要么表示一个南瓜,要么表示干草:S 表示小南瓜,M 表示中等大小的南瓜,L 表示一个大南瓜,* 表示干草。

下一行包含一个整数 $A$ 满足 $0 \leq A < R$,最后一行是一个整数 $B$ 满足 $0 \leq B < C$。表示农民一开始在第 $A$ 行第 $B$ 列的位置。南瓜地的左上角称为第 $0$ 行第 $0$ 列。

输出格式

输出一个整数 $V$ 表示农民能够收割的南瓜的总价值。

样例 #1

样例输入 #1

6
6
**LMLS
S*LMMS
S*SMSM
******
LLM*MS
SSL*SS
5
1

样例输出 #1

37

样例 #2

样例输入 #2

6
6
**LMLS
S*LMMS
S*SMSM
***SLL
LLM*MS
SSL*SS
2
4

样例输出 #2

88

提示

【样例 1 解释】

农民在第 $5$ 行第 $1$ 列开始可以收割 $6$ 个南瓜。可以收割到 $2$ 个小南瓜,$1$ 个中等大小的南瓜和 $3$ 个大南瓜。收割的南瓜的总价值是 $2 \times 1 + 1 \times 5 + 3 \times 10 = 37$。

【样例 2 解释】

农民在第 $2$ 行第 $4$ 列开始可以收割 $19$ 个南瓜。可以收割到 $8$ 个小南瓜,$6$ 个中等大小的南瓜和 $5$ 个大南瓜。收割的南瓜的总价值是 $8 \times 1 + 6 \times 5 + 5 \times 10 = 88$。

【数据范围】

本题采用捆绑测试。

对于所有数据,保证 $1\leq R,C\leq 10^5$,$1\leq R\times C\leq 10^5$。

下面的表格显示了 $15$ 分的分配方案:

分值 描述 范围
$1$ 南瓜地很小并且不存在干草。 $R \times C \leq 100$
$4$ 南瓜地很小并且干草把南瓜地分割为一些矩形区域。 $R \times C \leq 100$
$5$ 南瓜地很小并且干草可以在任意位置。 $R \times C \leq 100$
$5$ 南瓜地可能很大并且干草可以在任意位置。 $R \times C \leq 10^5$

D: 超速

时间限制 1.00s 内存限制 128.00MB

题目描述

超速行驶是一种危险的犯法行为,大大增加了交通事故导致悲惨后果的可能性。不幸的是使用使用雷达和相机控制速度并不能完全解决问题。为了防止这种行为的出现,根据汽车在一段道路上的行驶时间来罚款,可以对超速行为进行限制。

现在有 $n$ 段从 $1\sim n$ 编号的公路。第 $i$ 段公路长 $l_i$ 米,其限速为 $v_i$ 米每秒。超速就要罚款,但是为了体现按劳分配,还要对不同程度的超速设置不同的罚款金额。

具体来说,如果不超速则不收罚款;否则,用 $e$ 表示汽车在这段公路上的最大速度减去限速的值:

  • 如果 $0<e\leq a_1$,则惩罚为 $f_1$ 个货币单位。

  • 如果 $a_1<e\leq a_2$,则惩罚为 $f_2$ 个货币单位。

  • ...

  • 如果 $a_{m-2}<e\leq a_{m-1}$,则惩罚为 $f_{m-1}$ 个货币单位。

  • 如果 $a_{m-1}<e$,则惩罚为 $f_m$ 个货币单位。

目前,有 $q$ 辆车要经过这 $n$ 段道路,每辆车在 $s_i$ 时间到达 $1$ 号路段,在 $t_i$ 时间离开 $n$ 号路段。

你需要计算每辆车在所有路段中最高被罚款的金额至少是多少。

时间从道路开放起计算,即从 $0$ 开始计算。

输入格式

第一行一个正整数 $n$,表示道路段数。

接下来的两行,每行 $n$ 个数,第一行为 $v_i$,第二行为 $l_i$。

第四行为一个正整数 $m$,表示罚款的 $m$ 种不同范围。

接下来的两行,第一行 $m-1$ 个数,为 $a_i$;第二行 $m$ 个数,为 $f_i$。

第七行为一个正整数 $q$,表示共有 $q$ 辆车。

接下来的 $q$ 行,每行两个整数 $s_i,t_i$。

输出格式

输出共 $q$ 行。

对于每辆车,输出它最少被罚款的金额。

样例 #1

样例输入 #1

3
10 20 30
400 500 600
6
1 5 10 12 16
100 300 600 800 1000 1500
3
10 100
20 70
45 100

样例输出 #1

0
800
600

提示

对于 $100\%$ 的数据,$1\leq n\leq 10$,$1\leq v_i,l_i,a_i,f_i\leq 10^9$,$1\leq m\leq 10^5$,$1\le q\le 10^5$,$1\leq s_i<t_i\leq 10^9$。

任务编号 特殊限制 分值
$1$ $n=1,m=1$ $5$
$2$ $m=1$ $10$
$3$ $n=1,m\leq 10$ $9$
$4$ $n=1$ $12$
$5$ $m\leq 10,a_i\leq 10$ $13$
$6$ $m\leq 10$ $14$
$7$ 无特殊限制 $37$

E: Painting Roads

时间限制 1.00s 内存限制 512.00MB

题目描述

Kitchener 市的市长 Alanna 成功地改进了该市的道路规划。然而,来自 RedBlue 市的一位售货员仍然抱怨道路的颜色不够丰富。Alanna 的下一个任务就是粉刷一些道路。

Kitchener 市的道路规划可以表示为 $N$ 个十字路口和 $M$ 条道路,第 $i$ 条道路连接第 $u_i$ 个十字路口和第 $v_i$ 个十字路口。一开始所有道路都是灰色的。Alanna 想要把一些道路染成红色或者蓝色,满足以下条件:

  • 对于每一条灰色道路,假设其连接十字路口 $u_i$ 和十字路口 $v_i$,一定存在一条从十字路口 $u_i$ 到十字路口 $v_i$ 的路径,满足路径上的道路颜色红色和蓝色交替出现,任何道路都不是灰色的。

为了降低城市的支出,Alanna 希望尽可能少地对道路进行染色。请帮助 Alanna 设计一个符合要求的染色方案。

输入格式

输入的第一行包含两个整数 $N$ 和 $M$($1\leq N, M \leq 2 \times 10^5$)。

接下来 $M$ 行中的第 $i$ 行包含两个整数 $u_i$ 和 $v_i$ 表示存在一条连接十字路口 $u_i$ 和十字路口 $v_i$ 的道路($1 \leq u_i, v_i \leq N$,$u_i \neq v_i$)。

任意两个十字路口之间至多存在一条道路。

输出格式

输出一个长度为 $M$ 的字符串,表示染色的方案。第 $i$ 个字符表示第 $i$ 条道路的颜色,R 表示红色,B 表示蓝色,G 表示灰色(未染色)。

注意你必须在满足条件的情况下最小化染色的道路数目。如果存在多个这样的染色方案,输出任意一个。

样例 #1

样例输入 #1

5 7
1 2
2 4
5 2
4 5
4 3
1 3
1 4

样例输出 #1

BRGBBGG

样例 #2

样例输入 #2

4 2
1 2
3 4

样例输出 #2

BB

提示

【样例 1 解释】

十字路口以及有效的道路的示意图如下所示,该方案最小化了染色道路的数量。请注意,每条道路上的颜色显示为 R(红色)、B(蓝色)或 G(灰色)。

所有为染色的道路都满足条件:

  • 第二条路标记为 $G_2$ 连接了十字路口 $2$ 和 $4$,路径 $2, 1, 4$ 上的道路被染上红色、蓝色。
  • 第三条路标记为 $G_3$ 连接了十字路口 $5$ 和 $2$,路径 $5, 4, 1, 2$ 上的道路被染上红色、蓝色、红色。
  • 第五条路标记为 $G_5$ 连接了十字路口 $4$ 和 $3$,路径 $4, 1, 3$ 上的道路被染上蓝色、红色。

【样例 2 解释】

请注意 Kitchener 的道路可能不是连通的。

【数据范围】

本题采用捆绑测试。

对于所有数据,保证 $1\leq N, M \leq 2 \times 10^5$,$1 \leq u_i, v_i \leq N$,$u_i \neq v_i$。

下面的表格显示了 $15$ 分的分配方案:

分值 附加条件
$2$ 对任意 $1 \leq i < N$ 存在一条连接 $i$ 和 $i + 1$ 的道路(还可能存在其他道路)
$3$ 图连通并且 $N = M$
$3$ 任何道路都不同时属于至少两个简单环(见下文定义)
$7$

定义:若用 $u \leftrightarrow v$ 表示一条连接 $u$ 和 $v$ 的道路,则称 $k \geq 3$ 且所有 $w_i$ 互不相同是序列 $w_1 \leftrightarrow w_2 \leftrightarrow \cdots \leftrightarrow w_k \leftrightarrow w_1$ 为简单环。

共 90 篇博客