Logo lxn的博客

博客

OIER对拍的使用

2025-10-25 07:55:37 By lxn

1.何为对拍?

对拍:通过暴力程序去验证优化后的程序是否正确,称之为对拍(优化后的程序不知道正确性,但是可以通过暴力程序去验证优化后的程序是否正确)

2.什么时候用对拍?

  • a.没有测试数据的情况。
  • b.可以网络评测,但交上去后不知道自己错在哪里的的情况。 比如ABC347的C题。很多同学错了几遍也找不到错误的原因。

3.怎么写对拍?

需要准备的原材料: - a.待检验的程序。 - b.一个保证正确的暴力程序 - c.数据生成器

先生成小数据,大数据不利于查错,小数据无错的情况,再试试边界数据等特殊情况。都过的话再测试大数据。 - d.对拍程序

把这四个程序都编译完成,生成EXE文件。

4.实战演练

例题:ABC347C

  • a.待检验的程序。zhyt的C题代码,总是错一个点。文件名abc347c.CPP
#include <bits/stdc++.h>
using namespace std;
#define TRACE 1
#define tcout TRACE && cout

#define int long long
int n,a,b;
int x[1000005];
signed main()
{
     cin>>n>>a>>b;
     for(int i=1;i<=n;i++)
     {
         cin>>x[i];
         x[i]%=(a+b);
         /*if(x[i]==0)
         {
             x[i]=a+b;
         }*/
     }
     sort(x+1,x+n+1);
     if(x[n]-x[1]+1<=a){
         cout<<"Yes";      
     }    
     else{    
         cout<<"No";
     }
    return 0;
}
  • b.一个保证正确的暴力程序,文件名abc347c-baoli.cpp
//暴力思路,枚举当前是星期几,挨个算那些日期是不是在周末。
#include <bits/stdc++.h>
using namespace std;
#define TRACE 1
#define tcout TRACE && cout

#define int long long
int n,a,b;
int x[1000005];
bool check(int k){
    for(int i=1;i<=n;i++){
        if((k+x[i])%(a+b)>=a)return false;
    }
    return true;
}
signed main()
{
     cin>>n>>a>>b;
     for(int i=1;i<=n;i++)
     {
         cin>>x[i];
         x[i]--;
         x[i]%=(a+b);
     }
     for(int i=0;i<a+b;i++){
         if(check(i)){
             cout<<"Yes";
             return 0;
         }
     }
     cout<<"No";

    return 0;
}
  • c.数据生成器
#include<bits/stdc++.h>
using namespace std;

int main() {
    srand((int)time(NULL));
    int n=rand()%4+2;
    int a=rand()%5+1,b=rand()%5+1;
    cout<<n<<" "<<a<<" "<<b<<endl;
    for(int i=1;i<=n;i++){
        cout<<rand()%(a+b)+1<<" "; 
    }    
}
  • d.对拍程序
#include<cstdio>
#include<cstdlib>
#include<ctime>

int main()
{   long s,t;
    while(1){
        system("cls");
        do{
            system("makedata.exe> try.in"); //data是数据生成程序
            s=clock();
            system("abc347c.exe< try.in > try1.out");  //a是要交的程序
            t=clock();
            system("abc347c-baoli.exe< try.in > try2.out");  //b是正确的程序
            if(system("fc try1.out try2.out > nul"))
                break;
            else printf("AC time: %ldms\n",t-s);
        }while(1);
        printf("WA time: %ldms\n",t-s);  //运行时间 
        system("fc try1.out try2.out");
        system("pause>nul");
    }
    return 0;
}

运行检验

  • 本次出现的结果如下: ``` AC time: 367ms AC time: 254ms AC time: 129ms AC time: 131ms AC time: 181ms AC time: 129ms WA time: 136ms 正在比较文件 try1.out 和 TRY2.OUT * try1.out No * TRY2.OUT Yes


- 找出错误数据:然后我们打开try.in文件看看

2 3 4 7 6

## 错误调试
现在可以发现0和6的差值>=a,但也是可以都安排在休假范围内的。,错误的原因是要转圈看是否可以都在假期范围内。
- $x_1,x_2,...x_n$,这时候$x_n-x_1+1$
- $x_2,x_3,...x_n,x_1$,这时候$x1-x_2+1$,排序后会$\leq 0$,因此需要再加上$a+b$.

变换到一般话的情况

- $x_{i+1},...x_n,x_1,...x_i$,这时候判断 $x_i-x_{i+1}+1+a+b \leq a$
那你会修改自己的程序了吗?快来试试吧。

## 修改后的代码

```cpp
#include <bits/stdc++.h>

using namespace std;

#define TRACE 1
#define tcout TRACE && cout

#define int long long
int n,a,b;
int x[1000005];
signed main()
{
     cin>>n>>a>>b;
     for(int i=1;i<=n;i++)
     {
         cin>>x[i];
         x[i]%=(a+b);
     }
     sort(x+1,x+n+1);
     if(x[n]-x[1]+1<=a){
         cout<<"Yes";
          return 0;
     }
     else {
         for(int i=1;i<n;i++)
             if(x[i]+a+b-x[i+1]+1<=a){ 
             cout<<"Yes";
              return 0;
         }
     }        
    cout<<"No";

    return 0;
}

数据生成器

参考链接

山东CSP-JS考前注意事项

2025-10-24 09:37:21 By lxn

试机注意事项

  1. 存储检查:先确认可以存储的盘符
  2. 文件保护:建立测试文件并重启,检查保护是否开放
  3. 编译测试:编写简单代码进行编译运行测试

考试前准备

试机结束至考试开始期间

  • 编译标准:C++14 标准下不能使用 gets(),读取带空格的字符串使用:

    getline(cin, s);  // 或
    fgets();
  • 编辑器设置

    • 开启自动保存选项
    • 修改编译参数:-O2 -std=c++14 -Wall -Wl,--stack=100000000
    • 注意编译警告,确保函数有返回值
  • 代码验证:上交前必须编译测试

    g++ -O2 -std=c++14 -Wall -Wl,--stack=1000000000 a.cpp -o a.exe

考试流程

文件操作规范

#include <cstdio>
using namespace std;

int main() {
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);

    // 代码逻辑

    return 0;
}

重要提醒: - freopen 放在流同步前面 - 使用流同步时不要用 fclose - 字符输出要仔细核对,使用写字板查看 .in 文件

读题审题策略

  1. 仔细阅读:不要节省读题时间,理解题意是得分基础
  2. 关注限制:注意时间、空间限制条件
  3. 细节把握
    • 注意上下界和特殊条件
    • 仔细阅读子任务数据范围
  4. 策略选择
    • 通读所有题目,选择可做题先做
    • 分析特殊数据,争取子任务分数
    • 避免死磕难题

考试技巧

心态与策略

信息奥赛考的是心态,打好暴力提升下限,沉稳分析提升上限!

  • 分任务拿分:不要死磕一个题目
  • 子程序设计:根据数据范围编写子程序,在主程序中灵活调用
  • 复杂度计算:分析数据范围,计算时空复杂度

数据分治技巧

// 示例:根据数据范围选择算法
if (n <= 1000) {
    // 使用暴力解法
    brute_force();
} else {
    // 使用优化算法
    optimized_solution();
}

常见问题与解决方案

初始化问题

  • 算法开始时进行必要的初始化
  • 将初始化代码作为算法的一部分编写

边界情况处理

  1. 整数溢出:中间过程可能爆 int/long long
  2. 数据范围:关注上下界和极限情况

多测试数据注意事项

// 清空数据结构
void clear_data() {
    // 使用 for 循环清空,避免 memset 误用
    for (int i = 0; i <= n; i++) {
        data[i] = 0;
    }
}

清空要点: - 不确定时全部清空 - 注意边界位置 - 多测时必须读完所有输入

数组管理

const int MAXN = 100000 + 10;  // 多开一些空间

int arr[MAXN];  // 使用常量定义数组大小

空间计算: - 线段树开4倍空间 - 大数组要计算总空间 - 动态开空间考虑极限情况 - STL容器注意额外空间开销

溢出防护

// 加法和乘法检查溢出
long long result = (long long)a * b;  // 防止乘法溢出
int sum = a + b;  // 可能溢出,考虑用 long long

// 取模操作
result = (a * b) % MOD;  // 中间过程取模

变量管理

  • 易混淆变量使用明确命名
  • 使用注释标记重要变量
  • 修改代码时检查所有相关位置

调试与验证

对拍策略

  1. 数据覆盖:测试上下界和极限数据
  2. 暴力验证:确保暴力解法正确性
  3. 减少重合:避免正解和暴力犯相同错误

RE问题排查

  1. STL安全

    if (!container.empty()) {
        value = container.front();  // 判空后访问
    }
  2. 迭代器安全:避免在 begin()--end()++

  3. 数据结构完整性:考虑空节点情况
  4. 清理调试代码:提交前移除所有调试语句

实用工具

文件比较工具

FC命令使用说明

# 基本用法
fc file1.out file2.out

# 忽略大小写
fc /c file1.out file2.out

# 以ASCII方式比较
fc /a file1.out file2.out

# 以二进制方式比较
fc /b file1.out file2.out

# 显示不同行的行号
fc /n file1.out file2.out

时间检测工具

#include <iostream>
#include <ctime>
#include <iomanip>
using namespace std;

#define time_now double(clock())/CLOCKS_PER_SEC

int main() {
    double time = double(clock()) / CLOCKS_PER_SEC;
    while (1) {
        if (time_now - time > 0.8) break;
    }
    cout << fixed << setprecision(5) << time_now - time;
    return 0;
}

空间计算工具

#include <iostream>
using namespace std;

bool STSTST;
int a[114514];
int b[30][40000];
bool EDEDED;

int main() {
    cout << "USE " << (&EDEDED - &STSTST) / 1024.0 / 1024.0 << "MB" << endl;
    return 0;
}

考试结束前检查

  1. 最后15分钟:检查文件版本是否正确
  2. 文件操作:确认已取消调试用的文件输入输出注释
  3. 编译验证:修改文件后必须重新编译测试

祝各位考生在CSP2025中取得优异成绩!

C++文件比较程序

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using namespace std;

bool compareFiles(const string& file1, const string& file2) {
    ifstream f1(file1);
    ifstream f2(file2);

    if (!f1.is_open()) {
        cout << "无法打开文件: " << file1 << endl;
        return false;
    }
    if (!f2.is_open()) {
        cout << "无法打开文件: " << file2 << endl;
        return false;
    }

    string line1, line2;
    int lineNum = 1;
    vector<int> diffLines;

    while (getline(f1, line1) && getline(f2, line2)) {
        // 去除行尾空格和换行符
        while (!line1.empty() && isspace(line1.back())) 
            line1.pop_back();
        while (!line2.empty() && isspace(line2.back())) 
            line2.pop_back();

        if (line1 != line2) {
            diffLines.push_back(lineNum);
            cout << "第 " << lineNum << " 行不同:" << endl;
            cout << "文件1: " << line1 << endl;
            cout << "文件2: " << line2 << endl;
            cout << "---" << endl;
        }
        lineNum++;
    }

    // 检查文件长度是否一致
    if (f1.eof() != f2.eof()) {
        cout << "文件长度不同!" << endl;
        return false;
    }

    if (diffLines.empty()) {
        cout << "文件内容完全一致!" << endl;
        return true;
    } else {
        cout << "共发现 " << diffLines.size() << " 处不同" << endl;
        return false;
    }
}

int main() {
    string outputFile = "a.out";      // 考试输出文件
    string sampleFile = "b.out";      // 大样例输出文件

    cout << "开始比较文件..." << endl;
    cout << "考试输出: " << outputFile << endl;
    cout << "样例文件: " << sampleFile << endl;
    cout << "==========================" << endl;

    if (compareFiles(outputFile, sampleFile)) {
        cout << "✓ 恭喜!输出与样例一致" << endl;
    } else {
        cout << "✗ 输出与样例存在差异" << endl;
    }

    return 0;
}

CSP-J/S近五年题型难度分析与备赛总结

2025-10-24 09:24:08 By lxn

CSP-J 2020-2024 题型难度分析

真题难度分布表

年份 题目 难度 算法
2020 优秀的拆分 入门 枚举
2020 直播获奖 普及 模拟、排序
2020 表达式 普及/提高 模拟、栈
2020 方格取数 普及/提高 动态规划DP
2021 分糖果 普及 数学
2021 插入排序 普及/提高 枚举、排序
2021 网络连接 普及/提高 模拟、字符串、STL
2021 小熊的果篮 普及/提高 模拟、STL、链表
2022 乘方 入门 枚举
2022 解密 普及 数学、二分
2022 逻辑表达式 普及/提高 模拟、栈、表达式求值
2022 上升点列 普及/提高 动态规划DP
2023 小苹果 普及 数学、找规律
2023 公路 普及 贪心、前缀和
2023 一元二次方程 普及/提高 模拟
2023 旅游巴士 普及/提高 图论、二分+BFS
2024 扑克牌 入门 模拟、STL
2024 地图探险 普及 模拟
2024 小木棍 普及/提高 动态规划DP、贪心
2024 接龙 提高/省选 动态规划DP、图论

CSP-S 2020-2024 题型难度分析

真题难度分布表

年份 题目 难度 算法
2020 儒略日 普及/提高 模拟、数学、二分
2020 动物园 普及/提高 数学、贪心、进制、位运算
2020 函数调用 提高/省选- 动态规划DP、拓扑排序
2020 贪吃蛇 NOI/NOI+/CTSC 贪心、队列、堆
2021 廊桥分配 普及/提高 模拟、前缀和、队列
2021 括号序列 提高/省选- 动态规划DP、区间DP
2021 回文 普及/提高 字符串、贪心
2021 交通规划 省选/NOI- 网络流、平面图
2022 假期计划 提高/省选- 广播、折半搜索
2022 策略游戏 普及+/提高 贪心、线段树、ST表
2022 星战 省选/NOI- 图论、哈希
2022 数据传输 省选/NOI- 动态规划DP、矩阵乘法
2023 密码锁 普及- 模拟、枚举
2023 消消乐 提高/省选- 动态规划DP、哈希
2023 结构体 提高/省选- 模拟
2023 种树 提高/省选- 贪心、二分
2024 决斗 普及- 贪心
2024 超速检测 普及+/提高 图论、最短路、次短路
2024 染色 提高/省选- 动态规划DP
2024 擂台游戏 NOI/NOI+/CTSC 贪心、递推、树形DP、差分

二、CSP-J 分析与备赛策略

总结与分析

  • 难度分布:以入门和普及-难度为主,每年会有1-2道普及+/提高甚至更高难度的题目用于区分层次。2024年出现了提高/省选难度的题目,说明难度有小幅提升趋势。

  • 算法考查:以模拟、枚举、数学(找规律、简单数论)、简单DP、基础数据结构(栈、链表、STL的简单应用)为主,注重对编程基础和逻辑思维的考查。

  • 趋势特点:题目越来越贴近实际应用场景,对代码的可读性和规范性要求有所提高;同时部分题目开始融合多个基础算法,考查综合运用能力(如2024年接龙结合了DP和图论)。

难度走势

  • 整体趋势:2020—2022 重基础+模拟+数学;2023—2024 出现明显算法进阶化(DP、图论、贪心占比上升)。

  • 题型分布

    • 模拟题:约占40%
    • 数学/找规律题:约占25%
    • 动态规划DP:2020, 2022, 2024(方格取数、小木棍、接龙等)
    • 模拟与枚举:每年都有(扑克牌、地图探险、直播获奖等)
    • 数学与规律:2021-2023(分糖果、解密、小苹果等)
    • STL与链表:2021, 2024(小熊的果篮、扑克牌)
    • 图论与BFS:2023, 2024(旅游巴士、接龙)

题型分布特点

  1. 第1题:多为入门难度,考查枚举、模拟、基础数学
  2. 第2题:普及到普及+,考查模拟、排序、简单贪心、数学
  3. 第3题:普及+/提高,常考动态规划、表达式求值、图论、数据结构
  4. 第4题:普及+/提高到提高/省选,考查较复杂的动态规划、图论、贪心等

常考知识点

  • 基础算法:枚举、模拟、排序
  • 数学:简单数论、找规律、基础组合
  • 数据结构:栈、队列、链表、STL应用
  • 动态规划:线性DP、状态机DP
  • 图论:BFS、最短路径
  • 其他:贪心、二分、前缀和

备赛策略

  1. 打好基础:熟练掌握C++语法和STL
  2. 刷题重点
    • 第1、2题:刷普及及以下难度的模拟、枚举、数学题
    • 第3题:重点练习动态规划、栈与队列、简单图论
    • 第4题:练习提高难度的DP、贪心、图论题
  3. 模拟实战训练:多做历年真题和模拟赛,适应比赛节奏
  4. 时间分配:前两题尽量快速AC,留足时间攻克后两题

备赛建议

阶段一(基础夯实)

  • 掌握:循环、数组、字符串、结构体
  • 刷题:洛谷普及-组、CSP-J历年T1-T2

阶段二(算法训练)

  • 专项:枚举+模拟+排序
  • 掌握:STL基础(vector, queue, map)

阶段三(进阶算法)

  • DP专题:路径型、序列型
  • 图论基础:BFS、DFS、最短路径(理解层面)

阶段四(冲刺实战)

  • 历年真题全模拟(限时4小时)
  • 调整策略:保证T1-T3全AC,T4拿部分分

三、CSP-S 分析与备赛策略

总结与分析

  • 难度分布:题目难度跨度大,从普及到NOI/CTSC级别均有涉及,每年都会有1-2道高难度(省选/NOI及以上)题目,同时也包含一定比例的普及+到提高级题目用于保底得分。

  • 算法考查:动态规划(DP)是绝对的核心考点,几乎每年都有多道题涉及;贪心、模拟、数学(数论、进制等)、图论(网络流、树链剖分等)、数据结构(线段树、ST表、堆、队列等)也是高频考点。

  • 趋势特点:高难度题目越来越注重算法的综合运用(如2024年擂台游戏融合了贪心、递推、树形DP、差分等多种算法),对选手的思维深度和代码实现能力要求较高。

难度走势

  • 总体趋势
    • 2020~2021:稳中求进(动态规划、贪心)
    • 2022起:算法难度显著提升(图论、网络流、矩阵快速幂)
    • 2023~2024:CSP-S 已与 NOI 难度接轨

重点算法分布

模块 出现频率 常考题型
动态规划DP ★★★★☆ 函数调用、数据传输、染色
贪心算法 ★★★☆☆ 动物园、策略游戏、决斗
图论算法 ★★★☆☆ 交通规划、星战、擂台游戏
字符串与哈希 ★★★☆☆ 消消乐、回文、表达式
数学/位运算 ★★★☆☆ 动物园、密码锁
搜索与组合 ★★★☆☆ 假期计划、策略游戏

难度层级

等级 占比 特征
普及/提高 35% 模拟、贪心、基础DP
提高+/省选 45% 状压DP、区间DP、搜索剪枝
NOI级 20% 树形DP、网络流、复杂图论

题型分布特点

  1. 第1题:普及-到普及+/提高,考查模拟、数学、枚举
  2. 第2题:普及+/提高到提高/省选,考查贪心、数据结构、DP
  3. 第3题:提高/省选,考查较复杂的DP、图论、数据结构
  4. 第4题:省选/NOI-到NOI/CTSC,考查高级算法与复杂问题建模

常考知识点

  • 数据结构:线段树、ST表、哈希、队列、堆
  • 动态规划:区间DP、树形DP、状态压缩DP、动态DP
  • 图论:最短路、网络流、拓扑排序、LCA
  • 数学:数论、组合数学、矩阵快速幂

备赛策略

  1. 系统学习

    • 掌握所有普及组知识点,并深入学习提高组内容
    • 重点学习动态规划、图论、数据结构
  2. 刷题方向

    • 第1题:保证稳定AC,练习模拟、数学、枚举
    • 第2题:练习贪心、数据结构、基础DP
    • 第3题:重点练习提高/省选-难度的DP、图论、数据结构
    • 第4题:尝试理解题解,学习高级算法
  3. 模拟赛与复盘

    • 定期参加模拟赛,严格计时
    • 每场赛后详细复盘,查漏补缺
  4. 时间管理

    • 前两题控制在1.5小时内完成
    • 留足时间思考第3题,第4题尽量拿部分分

备赛建议

基础巩固(算法巩固)

  • 系统掌握:DFS/BFS、二分、贪心、堆、并查集
  • 刷题范围:CSP-S T1、T2,普及+到提高组题库

进阶训练(核心算法)

  • 深入学习:动态规划(线性DP、区间DP、状态压缩DP)
  • 图论专题:最短路、拓扑排序、网络流、LCA
  • 数据结构:线段树、树状数组、ST表

冲刺阶段(综合实战)

  • 模拟全卷训练(限时4小时)
  • 分析历年CSP-S T3-T4思路
  • 以「优化DP + 贪心」为主要突破点

进阶拓展(面向NOI)

  • 学习:矩阵快速幂、树形DP、差分约束
  • 推荐OJ:洛谷提高组专题、AtCoder DP Contest、Codeforces比赛、CSP历年题库

20251022csps模拟赛

2025-10-22 19:33:51 By lxn

XJT/模拟赛1题解

数组 (array)

Array

根据合成规则容易发现一个被合成的数只会由$k^x$个连续的相同的数来组成,换句话说$b_{i}$必须满足$b_{i}=s*k^x$,所以原问题就转化成不断判断是否存在$k^x$个连续的s,我们考虑把$a_{i}$不断地除以k直到不能整除,这样就将$a_{i}$分解为$s*k^x$,接下来对$b_{i}$一个一个判断即可,详见参考代码。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=5e6;
int n,m,k;
bool flag;
int a[N],b[N],c[N],l;
int main()
{
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    int t;scanf("%d",&t);
    while(t--)
    {
        flag=0;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)scanf("%d",&b[i]);
        for(int i=1;i<=n;i++)
        {
            c[i]=1;
            while(a[i]%k==0)a[i]/=k,c[i]*=k;
        }
        for(int i=1,j=1;i<=m;i++)
        {
            while(c[j]==0&&j<=n)j++;
            if(j>n){flag=1;break;}
            if(b[i]%a[j]!=0){flag=1;break;}
            int sum=b[i]/a[j];
            while(sum%k==0)sum/=k;
            if(sum!=1){flag=1;break;}
            sum=b[i]/a[j];
            if(c[j]>=sum)c[j]-=sum;
            else 
            {
                sum-=c[j];c[j]=0;
                while(a[j+1]==a[j]&&sum!=0&&j<n)
                {
                    if(sum>=c[j+1])
                    {
                        sum-=c[j+1];
                        c[j+1]=0;
                    } 
                    else c[j+1]-=sum,sum=0;
                    j++;
                }
                if(sum!=0){flag=1;break;}
            }
            if(flag)break; 
        }
        if(flag||c[n]!=0)printf("No\n");
        else printf("Yes\n");
    }
    return 0;
 }

行走 (walk)

Walk

假设在某个点之前我们已经到达了标号最小的位置,要想经过的标号最多就要使得终点标号变得尽量大,所以我们令所有的0尽可能的转化为k,但是同时也要考虑最后还要走回原点,令s为现在从头开始能走到的位置,对于每一个0能转化的值取决于后面的0的个数,设后面的0的个数为x,简单的推导可以发现这个值就是min(k,x*k-s),若得到的值小于-k则说明无法回到原点。枚举每一个时间,将该时间考虑在这个时间之前达到最小位置然后将所有的0填数字,最后取最大值,复杂度$O(n^2)$。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e4;
int cnt[N];
long long n,k;
long long ans;
long long tot;
long long a[N];
long long b[N];
int main()
{
    freopen("walk.in","r",stdin);
    freopen("walk.out","w",stdout);
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i],tot+=a[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)b[j]=a[(i+j-2)%n+1];
        cnt[n+1]=0;
        for(int j=n;j>=1;j--)cnt[j]=cnt[j+1]+(b[j]==0);
        bool flag=0;
        long long s=tot,mi=b[1],ma=b[1],sum=0;
        for(int j=1;j<=n;j++)
        {
            if(b[j]==0)
            {
                b[j]=min(k,cnt[j+1]*k-s);
                if(b[j]<-k){flag=1;break;}
                s+=b[j];
            }
            sum+=b[j];
            mi=min(mi,sum);
            ma=max(ma,sum);
        }
        if(!flag&&s==0)ans=max(ans,ma-mi+1);
    }
    if(ans==0)cout<<-1<<endl;
    else cout<<ans<<endl;
    return 0;
}

塔 (tower)

Tower

容易发现对于一个叶子节点v,任意一个经过v的路径都必然以v为端点。接下来我们将每个叶子节点的塔的效率增加1然后将其他所有节点的高度减1,直到所有的叶子节点都被删去,这时候我们加入新的叶子节点再次重复上述操作,但是此时我们增加的是被删去的节点的塔的效率,这样重复的操作下去就能得到最终的答案,当然由于题目的设定对于只剩一个节点的情况下,我们需要修改两个塔的效率,详见参考代码。

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
typedef long long ll;
using namespace std;
const int N=1e6; 
int n;
ll ans;
int d[N];
ll val[N];
vector<int>e[N];
pair<ll,int>s[N];
set<pair<ll,int>>S;
int main()
{
    freopen("tower.in","r",stdin);
    freopen("tower.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&val[i]);
        s[i]={val[i],i};
    }
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        d[x]++;
        d[y]++;
        e[x].emplace_back(y);
        e[y].emplace_back(x);
    }
    ans=0;
    sort(s+1,s+n+1);
    for(int i=1;i<=n;i++)if(d[i]==1)S.insert({val[i],i});
    for(int i=1;i<=n;i++)
    {
        while(S.size()>0&&(*S.begin()).first<s[i].first)
        {
            int x=(*S.begin()).second;
            d[x]=-1;
            S.erase(S.begin());
            for(auto y:e[x])
            {
                d[y]--;
                if(d[y]==0||d[y]==1)S.insert({val[y],y});
            }
        }
        ans+=max(2ll,ll(S.size()))*(s[i].first-s[i-1].first);
    }
    printf("%lld",ans);
    return 0;
}

子串 (substr)

Substr

容易发现我们可以用一种朴素的dp来解决这个问题,定义$dp_{i,m}$为前i个字符中,删除序列的二进制串为m的情况下所能获得的最优答案,当然由于字符串的存储复杂所以显然时间复杂度和空间复杂度都是不合法的,因此我们考虑对这个算法进行优化。 对于存储相同长度字符串的两个状态$dp_{i1,m1},dp_{i2,m2}$,由于字典序的特殊性,显然我们只用保留字典序更小的方案,所以我们改变策略只用bool类型来存储是否有最小字典序的方案到达某个状态,详见代码。

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1e4;
int n,m,M;
char c[N];
bool f[N][N];
int main()
{
    freopen("substr.in","r",stdin);
    freopen("substr.out","w",stdout);
    scanf("%s",c+1);
    n=strlen(c+1);
    m=log2(n);M=(1<<m)-1;
    memset(f,0,sizeof(f));
    for(int i=0;i<=n;++i)f[i][i]=1;
    for(int i=1;i<=n-M;++i)
    {
        char mi = 'z'; 
        for(int j=i-1;j-i+1<=M;j++) if(f[j][j-i+1])mi=min(mi,c[j+1]); 
        for(int j=i;j-i<=M;j++)f[j][j-i]=f[j-1][j-i]&(c[j]==mi); 
        for(int j=i;j-i<=M;j++)
        {
            for(int k=0;k<m&&j+(1<<k)-i<=M;k++)
            {
                if(!(((j-i)>>k)&1))f[j+(1<<k)][j+(1<<k)-i]|=f[j][j-i];
            }
        }
        putchar(mi);
    }
    return 0;
}

cf1696c cf1680d cf1637f cf938e

lxn Avatar