├── VERSION ├── .gitignore ├── docs ├── code_template │ ├── init │ │ ├── index.md │ │ ├── manifest.js │ │ ├── io.md │ │ ├── init_template.md │ │ └── io_extend.md │ ├── graph_theory │ │ ├── index.md │ │ ├── manifest.js │ │ ├── GPPF.md │ │ ├── Dijkstra.md │ │ ├── MCMF(spfa).md │ │ ├── Maxflow&Mincut(Dinic).md │ │ └── SPFA_extend.md │ ├── number_theory │ │ ├── index.md │ │ ├── manifest.js │ │ ├── CRT.md │ │ └── miller-rabin算法.md │ ├── string_algorithm │ │ ├── index.md │ │ ├── manifest.js │ │ └── AC-automaton.md │ ├── manifest.js │ └── index.md ├── solution │ ├── manifest.js │ ├── icpc2019shanghai │ │ ├── manifest.js │ │ ├── index.md │ │ ├── L.md │ │ ├── B.md │ │ ├── D.md │ │ └── J.md │ ├── icpc2019shenyang │ │ ├── manifest.js │ │ ├── index.md │ │ ├── E.md │ │ ├── F.md │ │ └── H.md │ ├── 2019.md │ └── index.md ├── .vuepress │ ├── styles │ │ └── index.styl │ ├── public │ │ ├── tools │ │ │ └── image │ │ │ │ ├── image1.png │ │ │ │ ├── image10.png │ │ │ │ ├── image11.png │ │ │ │ ├── image12.png │ │ │ │ ├── image13.png │ │ │ │ ├── image14.png │ │ │ │ ├── image15.png │ │ │ │ ├── image16.png │ │ │ │ ├── image17.png │ │ │ │ ├── image18.png │ │ │ │ ├── image19.png │ │ │ │ ├── image2.png │ │ │ │ ├── image20.png │ │ │ │ ├── image21.png │ │ │ │ ├── image22.png │ │ │ │ ├── image23.jpg │ │ │ │ ├── image3.png │ │ │ │ ├── image4.png │ │ │ │ ├── image5.png │ │ │ │ ├── image6.png │ │ │ │ ├── image7.png │ │ │ │ ├── image8.png │ │ │ │ └── image9.png │ │ └── solution │ │ │ ├── icpc2019shanghai │ │ │ └── tutorial.pdf │ │ │ └── icpc2019shenyang │ │ │ └── tutorial.pdf │ ├── enhanceApp.js │ └── config.js ├── contribute │ ├── manifest.js │ ├── gen_template.md │ └── index.md ├── oj_document │ ├── manifest.js │ ├── index.md │ ├── spj.md │ └── introduction.md ├── README.md └── tools │ └── index.md ├── .github ├── shell │ ├── maintain_version.sh │ ├── build_version.js │ ├── after_build.sh │ └── build_cdn_environment.js └── workflows │ ├── pr.yml │ └── cdn.yml ├── .travis ├── id_rsa.enc └── ssh_config ├── template └── code_template │ └── template.md ├── README.md ├── deploy └── deploy.sh ├── .travis.yml ├── LICENSE ├── package.json └── cli └── gen-code.js /VERSION: -------------------------------------------------------------------------------- 1 | 19 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /docs/code_template/init/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 初始设置 3 | --- 4 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 图论 3 | --- 4 | -------------------------------------------------------------------------------- /docs/code_template/number_theory/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数论 3 | --- 4 | -------------------------------------------------------------------------------- /docs/code_template/string_algorithm/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 字符串算法 3 | --- 4 | -------------------------------------------------------------------------------- /docs/solution/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "题解" 3 | }; 4 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | pre { 2 | overflow-y: hidden!important 3 | } 4 | -------------------------------------------------------------------------------- /docs/contribute/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "构建本文档" 3 | }; 4 | -------------------------------------------------------------------------------- /docs/oj_document/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "CUPOJ开发文档" 3 | }; 4 | -------------------------------------------------------------------------------- /.github/shell/maintain_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node .github/shell/build_version.js 3 | 4 | -------------------------------------------------------------------------------- /docs/code_template/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "模板库", 3 | sort: false 4 | }; 5 | -------------------------------------------------------------------------------- /.travis/id_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/.travis/id_rsa.enc -------------------------------------------------------------------------------- /docs/code_template/init/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "初始设置", 3 | sort: false 4 | }; 5 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "图论", 3 | sort: false 4 | }; 5 | -------------------------------------------------------------------------------- /docs/code_template/number_theory/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "数论", 3 | sort: false 4 | }; 5 | -------------------------------------------------------------------------------- /docs/code_template/string_algorithm/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "字符串算法", 3 | sort: false 4 | }; 5 | -------------------------------------------------------------------------------- /.travis/ssh_config: -------------------------------------------------------------------------------- 1 | Host github.com 2 | User git 3 | StrictHostKeyChecking no 4 | IdentityFile ~/.ssh/id_rsa 5 | IdentitiesOnly yes 6 | -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image1.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image10.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image11.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image12.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image13.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image14.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image15.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image16.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image17.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image18.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image19.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image2.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image20.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image21.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image22.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image23.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image3.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image4.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image5.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image6.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image7.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image8.png -------------------------------------------------------------------------------- /docs/.vuepress/public/tools/image/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/tools/image/image9.png -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "ICPC2019上海网络赛", 3 | sort: true, 4 | sortFn: (a, b) => a === b ? 0 : a > b ? 1 : -1 5 | }; 6 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shenyang/manifest.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "ICPC2019沈阳网络赛", 3 | sort: true, 4 | sortFn: (a, b) => a === b ? 0 : a > b ? 1 : -1 5 | }; 6 | -------------------------------------------------------------------------------- /docs/.vuepress/public/solution/icpc2019shanghai/tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/solution/icpc2019shanghai/tutorial.pdf -------------------------------------------------------------------------------- /docs/.vuepress/public/solution/icpc2019shenyang/tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUPACM-Docs/HEAD/docs/.vuepress/public/solution/icpc2019shenyang/tutorial.pdf -------------------------------------------------------------------------------- /docs/solution/2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2019年网络赛 3 | sidebarDepth: 2 4 | --- 5 | # 2019年网络赛题解 6 | ## ICPC沈阳 7 | [题解](/solution/icpc2019shenyang/) 8 | 9 | ## ICPC上海 10 | [题解](/solution/icpc2019shanghai/) 11 | -------------------------------------------------------------------------------- /docs/oj_document/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 介绍 3 | sidebarDepth: 2 4 | --- 5 | 6 | # 介绍 7 | CUP Online Judge是基于Express.js + Vue.js开发的在线评测系统。 8 | 9 | 关于系统的相关开发文档,请访问[CUP Online Judge开发文档](/oj_document/introduction/) 10 | -------------------------------------------------------------------------------- /template/code_template/template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 请将本句改为导航栏标题 3 | --- 4 | 5 | # 请将本句更改算法标题 6 | 7 | ## 说明 8 | 9 | 10 | ## 使用 11 | 12 | 13 | ## Tips 14 | 15 | 16 | ## 代码 17 | ```请将此处文字改为代码语言,如cpp/java/sh/python等 18 | 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | import 'github-markdown-css/github-markdown.css' 2 | export default ({ 3 | Vue, // VuePress 正在使用的 Vue 构造函数 4 | options, // 附加到根实例的一些选项 5 | router, // 当前应用的路由实例 6 | siteData // 站点元数据 7 | }) => { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.github/shell/build_version.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const version = parseInt(fs.readFileSync("VERSION").toString("utf-8")); 3 | console.log(`Update version to ${version + 1}`); 4 | fs.writeFileSync("VERSION", (version + 1).toString()); 5 | console.log(`Write version successful`); 6 | -------------------------------------------------------------------------------- /docs/code_template/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 算法模板库 3 | --- 4 | # 算法模板库 5 | ::: tip 6 | 若您对需要的算法所在的分类不明确的,请使用搜索框进行搜索 7 | ::: 8 | ## [初始设置](/code_template/init/) 9 | 10 | ## [数论](/code_template/number_theory/) 11 | 12 | ## [图论](/code_template/graph_theory/) 13 | 14 | ## [字符串](/code_template/string_algorithm/) 15 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shenyang/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 概述 3 | --- 4 | 5 | # ICPC2019沈阳网络赛 6 | 7 | ## 官方题解 8 | [tutorial.pdf](/solution/icpc2019shenyang/tutorial.pdf) 9 | 10 | ## 通过情况 11 | * 通过 6/11 12 | * 校排名: 112 13 | 14 | |A|B|C|D|E|F|G|H|I|J|K| 15 | |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| 16 | |❌|⭕️|⭕️|❌|⭕️|⭕️|❌|⭕️|❌|❌|⭕️| 17 | 18 | ::: tip 19 | ⭕: 现场通过 20 | 21 | ❌: 现场未通过 22 | ::: 23 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 概述 3 | --- 4 | 5 | # ICPC2019上海网络赛 6 | 7 | ## 官方题解 8 | [tutorial.pdf](/solution/icpc2019shanghai/tutorial.pdf) 9 | 10 | ## 通过情况 11 | 12 | * 通过 4/11 13 | * 校排名: 118 14 | 15 | |A|B|C|D|E|F|G|H|I|J|K|L| 16 | |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| 17 | |❌|⭕️|❌|⭕️|❌|❌|❌|❌|❌|⭕️|❌|⭕️| 18 | 19 | ::: tip 20 | ⭕: 现场通过 21 | 22 | ❌: 现场未通过 23 | ::: 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUPACM-Docs 2 | ![Publish releases to CDN repository](https://github.com/CUP-ACM-Programming-Club/CUPACM-Docs/workflows/Publish%20releases%20to%20CDN%20repository/badge.svg) 3 | 4 | 中国石油大学(北京)程序设计俱乐部文档库 5 | 6 | ## Install 7 | ```sh 8 | $ git clone https://github.com/RichardLitt/standard-readme.git 9 | $ npm i 10 | ``` 11 | 12 | ## Dev 13 | ```sh 14 | $ npm run docs:dev 15 | ``` 16 | 17 | ## publish 18 | 该仓库使用Travis CI自动发布。合并到该仓库后可在[https://docs.cupacm.com](https://docs.cupacm.com)查看 19 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request checker 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | name: Checkout code 16 | 17 | - name: Use Node.js environment 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Build distribution 23 | run: | 24 | npm i 25 | npm run docs:build 26 | -------------------------------------------------------------------------------- /.github/shell/after_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | WEBPATH="https://cdn.jsdelivr.net/gh/CUP-ACM-Programming-Club/CUPACM-Docs-CDN@v$(cat VERSION)/" 3 | if [[ "$OSTYPE" == "darwin"* ]]; then 4 | sed -i "" "s|a.p=\"/\"|a.p=\"$WEBPATH\"|g" docs/.vuepress/dist/assets/js/app.*.js 5 | sed -i "" "s|/assets/img/|$WEBPATH/assets/img/|g" docs/.vuepress/dist/assets/css/*.css 6 | else 7 | sed -i "s|a.p=\"/\"|a.p=\"$WEBPATH\"|g" docs/.vuepress/dist/assets/js/app.*.js 8 | sed -i "s|/assets/img/|$WEBPATH/assets/img/|g" docs/.vuepress/dist/assets/css/*.css 9 | fi 10 | node .github/shell/build_cdn_environment.js 11 | -------------------------------------------------------------------------------- /deploy/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | npm run docs:build 8 | 9 | git clone https://github.com/CUP-ACM-Programming-Club/CUPACM-Docs.git -b gh-pages pages 10 | rm -rf pages/*.html 11 | rm -rf pages/assets 12 | rm -rf pages/dist 13 | cp -r docs/.vuepress/dist/* pages 14 | cd pages 15 | git add -A 16 | git commit -m "deploy `TZ=UTC-8 date +'%Y-%m-%d %H:%M:%S'`" 17 | git config user.email 'gxlhybh@gmail.com' 18 | git config user.name 'Ryan Lee' 19 | git push -f git@github.com:CUP-ACM-Programming-Club/CUPACM-Docs.git gh-pages 20 | 21 | cd - 22 | -------------------------------------------------------------------------------- /docs/solution/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 题解 3 | sidebarDepth: 2 4 | --- 5 | # 题解添加规范 6 | ## ICPC/CCPC赛事题解 7 | 请在该目录下新建一个文件夹,在文件夹中创建以下文件: 8 | * `manifest.js`文件 9 | * `index.md(或README.md)`文件(两种文件不能共存,请注意) 10 | * `${题号}.md`文件,`${题号}`请替换成实际的题目号码,如`1000.md`或`A.md` 11 | 12 | 在该目录下寻找**今年**的markdown文件,如`2019.md`,在其中以如下格式添加 13 | ```markdown 14 | ## 比赛名称 15 | [题解](/solution/your_contest_directory/) 16 | ``` 17 | 其中目录请替换成你建立的文件夹 18 | 19 | 其中关于`manifest.js`文件的格式,请参照[附加功能](/contribute/#附加功能)的格式进行创建 20 | 21 | ## 其他平台题解 22 | 请在该目录下找到该平台文件夹,若不存在则新建文件夹,以**平台名称英文命名** 23 | 创建文件格式请参照[ICPC/CCPC赛事题解](/solution/#icpc-ccpc赛事题解)中给出的文件要求创建。 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | lang: zh-CN 4 | description: 中国石油大学(北京) 程序设计俱乐部 文档库 5 | actionLink: /code_template/index 6 | actionText: 查看模板 7 | features: 8 | - title: 算法模板 9 | details: 整理常用的竞赛算法模板 10 | - title: 题解整理 11 | details: 及时总结,共同学习 12 | - title: 开发指南 13 | details: 开发工具、技巧整理 14 | footer: MIT Licensed | Copyright @ 2019-present CUPACM Programming Club 15 | --- 16 | 17 | # 本地构建 18 | ::: tip 19 | 请阅读右上角的构建本文档 20 | ::: 21 | ```shell script 22 | # fork到你的Github仓库 23 | 24 | # Clone到本地 25 | git clone https://github.com/your_github_name/CUPACM-Docs.git 26 | 27 | # 写作 28 | npm run docs:dev 29 | 30 | # 推至Github 31 | git push 32 | 33 | # 提交PR 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/contribute/gen_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速添加模板库模板 3 | --- 4 | 5 | # 快速添加模板库模板 6 | 7 | 编写代码模板的同学需要按照代码模板对模板库的内容进行增删。而添加模板Markdown文件这个过程是相对枯燥模版化的工作。 8 | 9 | 因此这里提供了一个快速在`code_template`文件夹下添加一个按照代码模板文件编写的md文件 10 | 11 | 贡献者只需要执行 12 | ```shell script 13 | npm run gen:code 14 | ``` 15 | 16 | 根据命令行提示输入内容。 17 | 以下为范例: 18 | ::: tip 19 | `>`箭头代表命令行输出,`<`箭头代表您输入的内容,在实际执行中没有这个箭头。 20 | ::: 21 | ```shell script 22 | $ > 请输入Markdown文件名(无需添加后缀名) 23 | $ < io_extend 24 | $ > 模板添加成功,请前往/path/to/dir/docs/code_template/io_extend.md编辑内容 25 | $ > 添加文件到Git记录 26 | $ > 成功 27 | ``` 28 | 29 | 您只需要直接前往新的文件中根据模板内容更改即可。 30 | 31 | ::: warning 32 | 若您输入的文件名与文件夹中的文件重复,程序会停止生成工作,不会覆盖源文件。 33 | ::: 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | node_js: 4 | - node 5 | branch: master 6 | cache: 7 | directories: 8 | - node_modules 9 | before_install: 10 | - '[[ -n ${encrypted_c1ae533aefad_iv+x} ]] && openssl aes-256-cbc -K $encrypted_c1ae533aefad_key -iv $encrypted_c1ae533aefad_iv 11 | -in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d && chmod 600 ~/.ssh/id_rsa && eval $(ssh-agent) && ssh-add ~/.ssh/id_rsa && cp .travis/ssh_config ~/.ssh/config 12 | || echo "Pull Request mode"' 13 | - git config --global user.name 'Ryan Lee' 14 | - git config --global user.email 'gxlhybh@gmail.com' 15 | - export TZ='Asia/Shanghai' 16 | script: 17 | - '[[ -n ${encrypted_c1ae533aefad_iv+x} ]] && chmod a+x ./deploy/deploy.sh && ./deploy/deploy.sh || npm run docs:build' 18 | dist: xenial 19 | -------------------------------------------------------------------------------- /docs/code_template/init/io.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 输入输出挂 3 | --- 4 | # 输入输出挂 5 | 6 | ## 说明 7 | 快速读写模板 8 | 9 | ## 使用 10 | ```cpp 11 | int num; 12 | rn(num); 13 | o(num); 14 | ``` 15 | 16 | ## Tips 17 | 注意buf数组大小是否在内存范围内 18 | 19 | ## 代码 20 | ```cpp 21 | inline char nc() { 22 | static char buf[100000], *p1 = buf, *p2 = buf; 23 | return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++; 24 | } 25 | template 26 | bool rn(T& v) { 27 | static char ch; 28 | while (ch != EOF && !isdigit(ch)) ch = nc(); 29 | if (ch == EOF) return false; 30 | for (v = 0; isdigit(ch); ch = nc()) 31 | v = v * 10 + ch - '0'; 32 | return true; 33 | } 34 | 35 | template 36 | void o(T p) { 37 | static int stk[70], tp; 38 | if (p == 0) { putchar('0'); return; } 39 | if (p < 0) { p = -p; putchar('-'); } 40 | while (p) stk[++tp] = p % 10, p /= 10; 41 | while (tp) putchar(stk[tp--] + '0'); 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 中国石油大学(北京)ACM程序设计俱乐部 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/L.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: L. Digit sum 3 | --- 4 | # L. Digit sum 5 | ## 题意 6 | 求$b$进制下$\displaystyle\sum_{i=1}^{n}S(i), S(i) = digitSum(i)$ 7 | $b < 10, n < 1000000$ 8 | ## 思路 9 | $b$进制下有 10 | $$ 11 | \begin{aligned} 12 | S(0) &= 0 \\ 13 | S(b * n + k) &= S(n) + k (k < b, n \geq 0) 14 | \end{aligned} 15 | $$ 16 | 17 | 考虑预处理,对于每个询问查询时间$O(1)$。 18 | 预处理时间约为$O(n * b)$ 19 | 20 | ## 代码 21 | ```cpp 22 | #include 23 | #include 24 | using namespace std; 25 | const int maxn = 1e6 + 10; 26 | using ll = long long; 27 | ll arr[11][maxn]; 28 | 29 | int main() 30 | { 31 | for(int i = 2; i <= 10; ++i) { 32 | for(int j = 0; i * j + i < maxn; ++j) { 33 | for(int k = 0; k < i; ++k) { 34 | arr[i][i * j + k] = arr[i][j] + k; 35 | } 36 | } 37 | } 38 | for(int i = 2; i <= 10; ++i) { 39 | for(int j = 1; j < maxn; ++j) { 40 | arr[i][j] += arr[i][j - 1]; 41 | } 42 | } 43 | int T; 44 | int kase = 0; 45 | cin >> T; 46 | while(T--) { 47 | int n, b; 48 | cin >> n >> b; 49 | cout << "Case #" << (++kase) << ": "; 50 | cout << arr[b][n] << '\n'; 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cupacm-docs", 3 | "version": "0.0.1", 4 | "description": "CUPACM Programming Club docs", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "docs:dev": "vuepress dev docs", 12 | "docs:build": "./.github/shell/maintain_version.sh && vuepress build docs", 13 | "gen:code": "./cli/gen-code.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/ryanlee2014/CUPACM-Docs.git" 18 | }, 19 | "author": "Ryan Lee", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/ryanlee2014/CUPACM-Docs/issues" 23 | }, 24 | "homepage": "https://github.com/ryanlee2014/CUPACM-Docs#readme", 25 | "devDependencies": { 26 | "@dovyp/vuepress-plugin-clipboard-copy": "^1.0.0-alpha.7", 27 | "@vuepress/plugin-back-to-top": "^1.3.0", 28 | "vuepress": "^1.3.0", 29 | "vuepress-plugin-mermaidjs": "^1.2.1" 30 | }, 31 | "dependencies": { 32 | "@ryanlee2014/markdown-it-katex": "^1.0.4", 33 | "cheerio": "^1.0.0-rc.3", 34 | "github-markdown-css": "^3.0.1", 35 | "glob": "^7.1.6", 36 | "katex": "^0.11.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/B.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: B. Light bulbs 3 | --- 4 | # B. Light bulbs 5 | 6 | ## 题意 7 | 把$[0, n - 1)$的灯泡,初始关闭,每次操作一个区间,将其状态反转,问最后打开多少灯泡 8 | $n <= 1000000, m <= 1000, T <= 1000$ 9 | ## 思路 10 | ::: warning 11 | $n < 1000000 && T <= 1000$,说明你不能用$O(n)$的算法,只能用$O(mlog(m))$的算法 12 | ::: 13 | 区间排序求前缀和,实现过程多样。 14 | ## 代码 15 | ```cpp 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | map mp; 21 | int n, k; 22 | void init() { 23 | mp.clear(); 24 | cin >> n >> k; 25 | for(int i = 0; i < k; ++i) { 26 | int l, r; 27 | cin >> l >> r; 28 | ++mp[l + 1]; 29 | --mp[r + 2]; 30 | } 31 | } 32 | 33 | int main() { 34 | ios::sync_with_stdio(false); 35 | cin.tie(0); 36 | cout.tie(0); 37 | int T; 38 | cin >> T; 39 | int kase = 0; 40 | while(T--) { 41 | init(); 42 | int cur = 0, tot = 0; 43 | mp[0] = mp[n + 1] = 0; 44 | map::iterator iter = mp.begin(), prev = mp.begin(); 45 | ++iter; 46 | while(iter != mp.end()) { 47 | if (cur % 2) { 48 | tot += iter->first - prev->first; 49 | } 50 | cur += iter->second; 51 | prev = iter; 52 | ++iter; 53 | } 54 | cout << "Case #" << (++kase) << ": " << tot << '\n'; 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/GPPF.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 图论--最大势 3 | --- 4 | 5 | # 图论--最大势 6 | 7 | ## 说明 8 | - 最大势算法:求弦图的完美消除序列算法 9 | 10 | ## 使用 11 | - 创建数据结构: GPPF 12 | - 建图成功后调用`GPPF::solve()` 13 | 14 | ## Tips 15 | 16 | 17 | ## 代码 18 | ```cpp 19 | struct GPPF{ 20 | #define mp make_pair 21 | #define irange(i, arr) for(auto&i:arr) 22 | #define range(i, a, b) for(auto i=a;i<=b;++i) 23 | #define pi pair 24 | static const int mxn = 1e5+5; 25 | bool vis[mxn], use[mxn]; 26 | int n,m,ans,cnt[mxn]; 27 | vectorG[mxn]; 28 | GPPF(int n,int m):n(n),m(m){ 29 | range(i,1,m){ 30 | int u,v; 31 | cin>>u>>v; 32 | G[u].push_back(v); 33 | G[v].push_back(u); 34 | } 35 | } 36 | void solve(){ 37 | priority_queue q; 38 | range(i, 1, n)q.push(mp(0, i)); 39 | while (!q.empty()) { 40 | while(!q.empty() and vis[q.top().second])q.pop(); 41 | if(q.empty())break; 42 | int u = q.top().second; 43 | q.pop(); 44 | vis[u] = true; 45 | if(not use[cnt[u]]){ 46 | ++ans; 47 | use[cnt[u]] = true; 48 | } 49 | irange(i,G[u]){ 50 | if(vis[i])continue; 51 | ++cnt[i]; 52 | q.push(mp(cnt[i],i)); 53 | } 54 | } 55 | cout<>=1; 38 | } 39 | return sum; 40 | } 41 | ll exgcd(ll a,ll b,ll &x,ll &y){ 42 | if(b==0){ 43 | x=1,y=0; 44 | return a; 45 | } 46 | ll gcd=exgcd(b,a%b,y,x); 47 | y-=(a/b)*x; 48 | return gcd; 49 | } 50 | ll fun(){ 51 | ll x,y; 52 | ll M=m[1],ans=(a[1]%M+M)%M; 53 | for(int i=2;i<=n;i++){ 54 | ll gcd=exgcd(M,m[i],x,y); 55 | ll c=((a[i]-ans)%m[i]+m[i])%m[i]; 56 | if(c%gcd) return -1; //无解 57 | x=mymul(x,c/gcd,m[i]/gcd); 58 | ans+=x*M; 59 | M*=m[i]/gcd; 60 | ans=(ans%M+M)%M; 61 | } 62 | return ans; 63 | } 64 | int main(){ 65 | ios::sync_with_stdio(false); 66 | cin.tie(0); 67 | cout.tie(0); 68 | scanf("%d",&n); 69 | for(int i=1;i<=n;i++) 70 | scanf("%lld%lld",&m[i],&a[i]);//m[i]代表ai,a[i]代表bi 71 | printf("%lld\n",fun()); 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /.github/shell/build_cdn_environment.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const cheerio = require("cheerio"); 5 | const version = fs.readFileSync("VERSION"); 6 | const webPath = `https://cdn.jsdelivr.net/gh/CUP-ACM-Programming-Club/CUPACM-Docs-CDN@v${version}`; 7 | 8 | function rewriteSrc (element, $, attr) { 9 | element.each(function () { 10 | const src = $(this).attr(attr); 11 | if (src.indexOf("http") !== -1) { 12 | return; 13 | } 14 | $(this).attr(attr, webPath + src); 15 | }) 16 | } 17 | 18 | function writeSrc (element, $, dir) { 19 | element.each(function () { 20 | const src = $(this).attr("src"); 21 | if (src.indexOf("http") !== -1) { 22 | return; 23 | } 24 | $(this).attr("src", webPath + path.join(dir, src)); 25 | }) 26 | } 27 | 28 | function loadFile() { 29 | const htmlFileList = glob.sync(`docs/.vuepress/dist/**`) 30 | .filter(name => { 31 | return fs.lstatSync(name).isFile() 32 | }) 33 | .filter(name => { 34 | return name.substring(name.length - 5) === '.html'; 35 | }); 36 | htmlFileList.forEach(filePath => { 37 | const content = fs.readFileSync(filePath, "utf-8"); 38 | const $ = cheerio.load(content); 39 | const script = $("script"); 40 | const css = $("link"); 41 | rewriteSrc(script, $, "src"); 42 | rewriteSrc(css, $, "href"); 43 | writeSrc($("img"), $, path.dirname(filePath).replace("docs/.vuepress/dist", "")); 44 | fs.writeFileSync(filePath, $.html()); 45 | }); 46 | } 47 | 48 | loadFile(); 49 | -------------------------------------------------------------------------------- /cli/gen-code.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const readline = require('readline'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const {execSync} = require('child_process'); 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout 10 | }); 11 | 12 | function basePathResolver(rootPath) { 13 | return function () { 14 | return path.resolve(rootPath, ...arguments); 15 | } 16 | } 17 | 18 | function successExit() { 19 | process.exit(0); 20 | } 21 | 22 | function errorExit() { 23 | process.exit(0); 24 | } 25 | 26 | function checkExisted(file) { 27 | return fs.existsSync(file); 28 | } 29 | 30 | const rootPath = path.resolve(__dirname, '../'); 31 | 32 | const rootPathResolver = basePathResolver(rootPath); 33 | 34 | rl.question('请输入Markdown文件名(无需添加后缀名)\n', answer => { 35 | if (answer.length === 0) { 36 | console.log('非法输入'); 37 | errorExit(); 38 | } 39 | const srcFile = rootPathResolver('template', 'code_template', 'template.md'); 40 | const destFile = rootPathResolver('docs', 'code_template', `${answer}.md`); 41 | if (checkExisted(destFile)) { 42 | console.log('文件已存在,请使用其他文件名。'); 43 | console.log('程序结束'); 44 | errorExit(); 45 | } 46 | try { 47 | fs.copyFileSync(srcFile, destFile); 48 | } catch (e) { 49 | console.error('复制模板失败'); 50 | console.error(e); 51 | errorExit(); 52 | } 53 | if (checkExisted(destFile)) { 54 | console.log(`模板添加成功,请前往${destFile}编辑内容`); 55 | } else { 56 | console.log(`模板添加失败`); 57 | errorExit(); 58 | } 59 | console.log('添加文件到Git记录'); 60 | const stdout = execSync(`cd ${rootPath} && git add -A`); 61 | console.log('成功'); 62 | rl.close(); 63 | successExit(); 64 | }); 65 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/D.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: D. Counting Sequences I 3 | --- 4 | # D. Counting Sequences I 5 | ## 题意 6 | 统计有多少个序列满足和等于积 7 | 8 | 即$\displaystyle\sum a_i = \displaystyle\prod a_i$ 9 | 10 | ## 思路 11 | dfs + 剪枝,**和OEIS上的某个数列没有关系,仅仅为前9个结果巧合相同** 12 | 13 | ## 代码 14 | ```cpp 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #define ll long long 22 | using namespace std; 23 | 24 | const ll MOD = 1e9 + 7; 25 | const int maxn = 310 * 500; 26 | 27 | ll dp[maxn]; 28 | ll a[310]; 29 | ll ans, sum, _max; 30 | 31 | bool cmp(const ll& x, const ll& y) 32 | { 33 | return x > y; 34 | } 35 | 36 | int main() 37 | { 38 | ios::sync_with_stdio(false); 39 | cin.tie(nullptr); 40 | cout.tie(nullptr); 41 | int T, n; 42 | cin >> T; 43 | while (T--) 44 | { 45 | cin >> n; 46 | memset(dp, 0, sizeof(dp)); 47 | ans = sum = _max = 0; 48 | for (int i = 1; i <= n; i++) 49 | { 50 | cin >> a[i]; 51 | sum += a[i]; 52 | } 53 | sort(a + 1, a + n + 1, cmp); 54 | dp[0] = 1; 55 | for (int i = 1; i <= n; i++) 56 | { 57 | _max += a[i]; 58 | for (int j = min(_max, sum / 2 + 1000); j >= a[i]; j--) 59 | { 60 | if (dp[j - a[i]] > 0) 61 | { 62 | dp[j] += dp[j - a[i]]; 63 | dp[j] %= MOD; 64 | if (j - a[i] <= sum - j && 65 | j >= sum - j) 66 | { 67 | ans += dp[j - a[i]]; 68 | ans %= MOD; 69 | } 70 | } 71 | } 72 | } 73 | cout << ans << '\n'; 74 | } 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shanghai/J.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: J. Stone game 3 | --- 4 | # J. Stone game 5 | 6 | ## 题意 7 | > 待补充 8 | ## 思路 9 | * 对所有石子从大到小排序,进行dp。 10 | * 我们考虑取出的那一堆石子,f[i][j] 表示该堆石子里最小的石子为 i,总价值 为 j 的方案数,这个通过dp来算。 11 | * 对于所有最小石子为 i 的方案,可以求出其左右边界,那么对应答案加上改 区间内的 f[i][l] ~ f[i][r]。其中 i 这一维可以略去。 12 | ## 代码 13 | ```cpp 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define ll long long 21 | using namespace std; 22 | 23 | const ll MOD = 1e9 + 7; 24 | const int maxn = 310 * 500; 25 | 26 | ll dp[maxn]; 27 | ll a[310]; 28 | ll ans, sum, _max; 29 | 30 | bool cmp(const ll& x, const ll& y) 31 | { 32 | return x > y; 33 | } 34 | 35 | int main() 36 | { 37 | ios::sync_with_stdio(false); 38 | cin.tie(nullptr); 39 | cout.tie(nullptr); 40 | int T, n; 41 | cin >> T; 42 | while (T--) 43 | { 44 | cin >> n; 45 | memset(dp, 0, sizeof(dp)); 46 | ans = sum = _max = 0; 47 | for (int i = 1; i <= n; i++) 48 | { 49 | cin >> a[i]; 50 | sum += a[i]; 51 | } 52 | sort(a + 1, a + n + 1, cmp); 53 | dp[0] = 1; 54 | for (int i = 1; i <= n; i++) 55 | { 56 | _max += a[i]; 57 | for (int j = min(_max, sum / 2 + 1000); j >= a[i]; j--) 58 | { 59 | if (dp[j - a[i]] > 0) 60 | { 61 | dp[j] += dp[j - a[i]]; 62 | dp[j] %= MOD; 63 | if (j - a[i] <= sum - j && 64 | j >= sum - j) 65 | { 66 | ans += dp[j - a[i]]; 67 | ans %= MOD; 68 | } 69 | } 70 | } 71 | } 72 | cout << ans << '\n'; 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/code_template/init/init_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 初始模板 3 | --- 4 | 5 | # 初始模板 6 | 7 | ## 说明 8 | - 环境要求: `g++ -std=c++11` 9 | 10 | ## 使用 11 | - 将模板复制到`./main.cpp`后食用。 12 | - 配合命令行工具[Qpro](https://pypi.org/project/Qpro/)食用更香。 13 | - 环境要求: `python::3` 14 | 15 | ## Tips 16 | 17 | ## 代码 18 | ```cpp 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | 41 | typedef long long LL; 42 | typedef unsigned long long ULL; 43 | #define pi pair 44 | #define lowbit(x) (x)&(-(x)) 45 | #define mp make_pair 46 | #define irange(i, arr) for(auto&i:arr) 47 | #define range(i, a, b) for(auto i=a;i<=b;++i) 48 | #define itrange(i, a, b) for(auto i=a;i!=b;++i) 49 | #define rerange(i, a, b) for(auto i=a;i>=b;--i) 50 | #define IOS ios::sync_with_stdio(false), cin.tie(0) 51 | #define fill(arr, tmp) memset(arr,tmp,sizeof(arr)) 52 | using namespace std; 53 | /// here to write const value like: const int mod = 1e9+7 54 | 55 | 56 | /// here to write data structure 57 | 58 | 59 | void init() { /// here to write init function 60 | 61 | } 62 | 63 | void solve() { /// here to write main algorithm 64 | 65 | } 66 | 67 | int main(int argc, char**args) { 68 | IOS; 69 | init(); 70 | solve(); 71 | return 0; 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shenyang/E.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: E.Gugugu's upgrade schemes 3 | --- 4 | # E.Gugugu's upgrade schemes 5 | ## 题意 6 | 计算贝尔数(**Bell number**),对结果取$mod$ 7 | ## 思路 8 | 本题是原题: [BZOJ 3501(CUPOJ 4415)](https://oj.cupacm.com/problem/submit/4415) 9 | 用贝尔三角预处理贝尔数,对于$B_{n}, n \geq mod$的情况,有 10 | $$ 11 | B_{p+n} \equiv B_n+B_{n+1}(\bmod p) 12 | $$ 13 | $$ 14 | B_{p^m+n} \equiv mB_n+B_{n+1}(\bmod p) 15 | $$ 16 | ## 代码 17 | ```cpp 18 | #include 19 | 20 | using namespace std; 21 | using ll = long long; 22 | const int maxn = 1001; 23 | int mod; 24 | 25 | int f[maxn], s[maxn][maxn]; 26 | 27 | int cal(ll n) { 28 | int m = 0, b[maxn], c[maxn], d[70]; 29 | for (int i = 0; i <= mod; ++i) { 30 | b[i] = f[i] % mod; 31 | } 32 | while (n) { 33 | d[m++] = n % mod; 34 | n /= mod; 35 | } 36 | for (int i = 1; i < m; ++i) 37 | for (int j = 1; j <= d[i]; ++j) { 38 | for (int k = 0; k < mod; ++k) { 39 | c[k] = (b[k] * i + b[k + 1]) % mod; 40 | } 41 | c[mod] = (c[0] + c[1]) % mod; 42 | for (int k = 0; k <= mod; ++k) { 43 | b[k] = c[k]; 44 | } 45 | } 46 | return c[d[0]]; 47 | } 48 | 49 | ll bell(ll n) { 50 | if (n < maxn)return f[n]; 51 | return (cal(n) + mod) % mod; 52 | } 53 | 54 | void init() { 55 | f[0] = f[1] = s[0][0] = s[1][0] = 1, s[1][1] = 2; 56 | for (int i = 2; i < maxn; i++) { 57 | int j; 58 | for (f[i] = s[i][0] = s[i - 1][i - 1], j = 1; j <= i; j++) { 59 | s[i][j] = (s[i - 1][j - 1] + s[i][j - 1]) % mod; 60 | } 61 | } 62 | } 63 | 64 | int main() { 65 | ios::sync_with_stdio(false); 66 | cin.tie(0); 67 | cout.tie(0); 68 | int T; 69 | cin >> T; 70 | while (T--) { 71 | ll n; 72 | cin >> n >> mod; 73 | init(); 74 | cout << bell(n) << '\n'; 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/Dijkstra.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dijkstra 3 | --- 4 | 5 | # Dijkstra(堆优化) 6 | 7 | ## 说明 8 | 9 | ::: warning 10 | 11 | 写程序时一定要初始化建图。且假定图是不带负权的有向图或无向图。 12 | 13 | ::: 14 | 15 | [题目:Luogu P4779](https://www.luogu.org/problem/P4779)给定一个 *N*个点,*M*条有向边的带非负权图,请你计算从 *S* 出发,到每个点的距离。 16 | 17 | 数据保证你能从 *S* 出发到任意点。 18 | 19 | 20 | ## 使用 21 | 22 | ```cpp 23 | //配合建图模板 24 | struct edge 25 | { 26 | int to, dis, next; //to去哪里,dis权值。 27 | }Node[MAXN]; 28 | int head[MAXN]; 29 | int vis[MAXN]; 30 | int dis[MAXN];//dis[i]代表点给定的点S到点i的最短距离 31 | int n, m, s,cnt=0; 32 | void add_edge( int u, int v, int d ) 33 | { 34 | cnt++; 35 | Node[cnt].dis = d; 36 | Node[cnt].to = v; 37 | Node[cnt].next = head[u]; 38 | head[u] = cnt; 39 | } 40 | struct node//建堆 41 | { 42 | int dis; 43 | int pos; 44 | bool operator <( const node &x )const 45 | { 46 | return x.dis < dis; 47 | } 48 | }; 49 | priority_queue q; 50 | ``` 51 | 52 | ## Tips 53 | 54 | 注意数组开的大小 55 | 56 | ## 代码 57 | 58 | ```cpp 59 | void dijkstra() 60 | { 61 | dis[s] = 0; 62 | q.push( ( node ){0, s} );//题目中是S点,如果存在多点,更改函数int dijkstra(int s),此处不变 63 | while( !q.empty() ) 64 | { 65 | node tmp = q.top(); 66 | q.pop(); 67 | int x = tmp.pos, d = tmp.dis; 68 | if( vis[x] ) 69 | continue; 70 | vis[x] = 1; 71 | for( int i = head[x]; i; i = Node[i].next ) 72 | { 73 | int y = Node[i].to; 74 | if( dis[y] > dis[x] + Node[i].dis ) 75 | { 76 | dis[y] = dis[x] + Node[i].dis; 77 | if( !vis[y] ) 78 | { 79 | q.push( ( node ){dis[y], y} ); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | /*int main() 86 | { 87 | start;//参照#define start ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) 88 | scanf( "%d%d%d", &n, &m, &s ); 89 | for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;//初始化 90 | for(int i = 0; i < m; ++i ) 91 | { 92 | int u, v, d; 93 | scanf("%d%d%d", &u, &v, &d ); 94 | add_edge( u, v, d );//题目中是建立单向边 95 | } 96 | dijkstra();//跑一边 97 | for( int i = 1; i <= n; i++ ) 98 | printf( "%d ", dis[i] );//从s点出发到各个顶点的距离 99 | cout< 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | using namespace std; 30 | using ll = long long; 31 | 32 | const int maxn = 5e5 + 10; 33 | int arr[maxn]; 34 | pair pir[maxn]; 35 | int main() 36 | { 37 | ios::sync_with_stdio(false); 38 | cin.tie(0); 39 | cout.tie(0); 40 | int n, k; 41 | while(cin >> n >> k) { 42 | ll sum = 0; 43 | for (int i = 1; i <= n; ++i) { 44 | cin >> arr[i]; 45 | sum += arr[i]; 46 | } 47 | if (n == 1) { 48 | cout << 0 << '\n'; 49 | return 0; 50 | } 51 | ll average = sum / n; 52 | sort(arr + 1, arr + 1 + n); 53 | int prev = -1, pos = 0; 54 | for (int i = 1; i <= n; ++i) { 55 | if (arr[i] != prev) { 56 | prev = arr[i]; 57 | pir[++pos] = {arr[i], 1}; 58 | } else { 59 | ++pir[pos].second; 60 | } 61 | } 62 | 63 | ll cur = 0; 64 | int apos = 1; 65 | bool finish = false; 66 | for (int i = 1; i <= pos; ++i) { 67 | if (pir[i].first >= average) { 68 | cout << (sum % n ? 1 : 0) << '\n'; 69 | finish = true; 70 | break; 71 | } 72 | apos = i; 73 | ll increment = (ll)pir[i].second * (ll)abs(pir[i].first - min(pir[i + 1].first, (int) average)); 74 | if (cur + increment > k) { 75 | break; 76 | } 77 | cur += increment; 78 | pir[i + 1].second += pir[i].second; 79 | } 80 | if (finish) continue; 81 | int lower = pir[apos].first + ((k - cur) / pir[apos].second); 82 | int bpos = pos; 83 | cur = 0; 84 | for(int i = pos; i; --i) { 85 | bpos = i; 86 | ll decrement = (ll)pir[i].second * (ll)abs(pir[i].first - pir[i - 1].first); 87 | if (cur + decrement > k) { 88 | break; 89 | } 90 | cur += decrement; 91 | pir[i - 1].second += pir[i].second; 92 | } 93 | int upper = pir[bpos].first - ((k - cur) / pir[bpos].second); 94 | cout << upper - lower << '\n'; 95 | } 96 | } 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/code_template/number_theory/miller-rabin算法.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 米勒拉宾素性测试 3 | --- 4 | 5 | # 米勒拉宾素性测试 6 | 7 | ## 说明 8 | 快速进行大素数验证。 9 | 10 | ## 使用 11 | | 功能 | 函数 | 样例调用 | 样例返回 | 12 | |:---|:---|:---|:---| 13 | |素性测试| `bool Miller_Rabin(LL n)`| `Miller_Rabin(29)` | true | 14 | |分解素因数| `void findfac(LL n)` | `findfac(10)` | factor数组:`[2,5]` | 15 | 16 | ## Tips 17 | - 唯一可调外部参数: `const int S=20;`为米勒拉宾算法验证次数,失误率为: $\frac{1}{2^S}$ 18 | 19 | ## 代码 20 | ```cpp 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #define range(i,a,b) for(int i=a;i<=b;++i) 28 | #define rerange(i,a,b) for(int i=a;i>=b;--i) 29 | #define LL long long 30 | #define fill(arr,tmp) memset(arr,tmp,sizeof(arr)) 31 | using namespace std; 32 | const int S=20; 33 | LL mult_mod(LL a,LL b,LL c){ 34 | a%=c; 35 | b%=c; 36 | long long ret=0; 37 | while(b){ 38 | if(b&1){ret+=a;ret%=c;} 39 | a<<=1; 40 | if(a>=c)a%=c; 41 | b>>=1; 42 | } 43 | return ret; 44 | } 45 | LL pow_mod(LL x,LL n,LL mod){ 46 | if(n==1)return x%mod; 47 | x%=mod; 48 | LL tmp=x; 49 | LL ret=1; 50 | while(n){ 51 | if(n&1) ret=mult_mod(ret,tmp,mod); 52 | tmp=mult_mod(tmp,tmp,mod); 53 | n>>=1; 54 | } 55 | return ret; 56 | } 57 | bool check(LL a,LL n,LL x,LL t){ 58 | LL ret=pow_mod(a,x,n); 59 | LL last=ret; 60 | range(i,1,t){ 61 | ret=mult_mod(ret,ret,n); 62 | if(ret==1&&last!=1&&last!=n-1) return true; 63 | last=ret; 64 | } 65 | if(ret!=1) return true; 66 | return false; 67 | } 68 | bool Miller_Rabin(LL n){ 69 | if(n<2)return false; 70 | if(n==2)return true; 71 | if((n&1)==0) return false; 72 | LL x=n-1; 73 | LL t=0; 74 | while((x&1)==0){x>>=1;t++;} 75 | range(i,0,S-1){ 76 | LL a=rand()%(n-1)+1; 77 | if(check(a,n,x,t))return false; 78 | } 79 | return true; 80 | } 81 | LL factor[100]; 82 | int tol; 83 | LL gcd(LL a,LL b){ 84 | if(a==0)return 1; 85 | if(a<0) return gcd(-a,b); 86 | while(b){ 87 | long long t=a%b; 88 | a=b; 89 | b=t; 90 | } 91 | return a; 92 | } 93 | LL Pollard_rho(LL x,LL c){ 94 | LL i=1,k=2; 95 | LL x0=rand()%x; 96 | LL y=x0; 97 | while(1){ 98 | i++; 99 | x0=(mult_mod(x0,x0,x)+c)%x; 100 | LL d=gcd(y-x0,x); 101 | if(d!=1&&d!=x) return d; 102 | if(y==x0) return x; 103 | if(i==k){y=x0;k+=k;} 104 | } 105 | } 106 | void findfac(LL n){ 107 | if(Miller_Rabin(n)){ 108 | factor[tol++]=n; 109 | return; 110 | } 111 | LL p=n; 112 | while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1); 113 | findfac(p); 114 | findfac(n/p); 115 | } 116 | int main(){ 117 | long long n; 118 | while(scanf("%lld",&n)!=EOF){ 119 | tol=0; 120 | /* 121 | findfac(n); 122 | for(int i=0;i q; 58 | 59 | void build() { 60 | for (int i = 0; i < 26; i++) 61 | if (tr[0][i]) q.push(tr[0][i]); 62 | while (q.size()) { 63 | int u = q.front(); 64 | q.pop(); 65 | for (int i = 0; i < 26; i++) { 66 | if (tr[u][i]) 67 | fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]); 68 | else 69 | tr[u][i] = tr[fail[u]][i]; 70 | } 71 | } 72 | } 73 | 74 | // int query(char *t) { // 返回最大的出现次数 75 | // int u = 0, res = 0; 76 | // for (int i = 1; t[i]; i++) { 77 | // u = tr[u][t[i] - 'a']; 78 | // for (int j = u; j; j = fail[j]) val[j]++; 79 | // } 80 | // for (int i = 0; i <= tot; i++) 81 | // if (idx[i]) res = max(res, val[i]), cnt[idx[i]] = val[i]; 82 | // return res; 83 | // } 84 | 85 | } // namespace AC 86 | 87 | 88 | // int n; 89 | // char s[N][100], t[L]; 90 | 91 | // int main() { 92 | // while (~scanf("%d", &n)) { 93 | // if (n == 0) break; 94 | // AC::init(); 95 | // for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1), AC::insert(s[i], i); 96 | // AC::build(); 97 | // scanf("%s", t + 1); 98 | // int x = AC::query(t); 99 | // printf("%d\n", x); 100 | // for (int i = 1; i <= n; i++) 101 | // if (AC::cnt[i] == x) printf("%s\n", s[i] + 1); 102 | // } 103 | // return 0; 104 | // } 105 | 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const version = fs.readFileSync(path.resolve(process.cwd(), "VERSION")); 5 | 6 | console.log(`Version: ${version}`); 7 | 8 | const webPath = `https://cdn.jsdelivr.net/gh/CUP-ACM-Programming-Club/CUPACM-Docs-CDN@v${version}/`; 9 | module.exports = { 10 | base: "/", 11 | title: "CUPACM Documents", 12 | head: [ 13 | ['link', {rel: 'stylesheet', href: 'https://shadow.elemecdn.com/npm/katex@0.11.0/dist/katex.min.css'}] 14 | ], 15 | plugins: [ 16 | ['@vuepress/back-to-top'], 17 | ['mermaidjs'], 18 | ['@dovyp/vuepress-plugin-clipboard-copy', true] 19 | ], 20 | markdown: { 21 | lineNumbers: true, 22 | plugins: ['@ryanlee2014/katex'] 23 | }, 24 | themeConfig: { 25 | repo: 'CUP-ACM-Programming-Club/CUPACM-Docs', 26 | editLinks: true, 27 | docsDir: 'docs', 28 | docsBranch: 'master', 29 | editLinkText: '在Github上编辑此页面', 30 | sidebar: loadSidebarContents(), 31 | lastUpdated: '上次更新', 32 | nav: [ 33 | { 34 | text: '首页', link: '/' 35 | }, 36 | { 37 | text: '模板库', link: '/code_template/' 38 | }, 39 | { 40 | text: '题解', link: '/solution/' 41 | }, 42 | { 43 | text: '构建本文档', link: '/contribute/' 44 | }, 45 | { 46 | text: 'CUPOJ开发文档', link: '/oj_document/' 47 | }, 48 | { 49 | text: 'Jetbrains教育版申请', link: '/tools/' 50 | } 51 | ] 52 | } 53 | }; 54 | 55 | function loadSidebarContents() { 56 | const sidebarMap = {}; 57 | const set = new Set(); 58 | glob.sync(`docs/**`) 59 | .map(dir => dir.replace('docs', '')) 60 | .filter(dir => path.dirname(dir) !== '.') 61 | .forEach(dir => set.add(path.dirname(dir))); 62 | Array.from(set) 63 | .sort((a, b) => a === b ? 0 : a > b ? -1 : 1) 64 | .forEach(dir => { 65 | const filePath = path.resolve(__dirname, `../${dir}/manifest`); 66 | const manifest = fs.existsSync(`${filePath}.js`) ? require(filePath) : {}; 67 | const sidebarList = loadDirContents(dir.substring(1)); 68 | const sortFn = manifest.sort ? sidebarList.sort: el => el; 69 | sidebarMap[`${dir}/`] = sortWrapper(sidebarList, sortFn, manifest.sortFn); 70 | }); 71 | return sidebarMap 72 | } 73 | 74 | function loadDirContents(dir) { 75 | return ['', ...glob.sync(`docs/${dir}/*.md`) 76 | .map(f => f.replace('docs/', '') 77 | .replace(dir + '/', '') 78 | .replace('\.md', '')) 79 | .filter(e => e !== 'index') 80 | ]; 81 | } 82 | 83 | function sortWrapper(iterableObject, sortFn, compareFn) { 84 | sortFn.call(iterableObject, compareFn); 85 | return iterableObject; 86 | } 87 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/MCMF(spfa).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 最小费用最大流 3 | --- 4 | 5 | # 最小费用最大流 6 | 7 | ## 说明 8 | 使用结构体,可改用命名空间 9 | 10 | ## 使用 11 | $d.addedge$先后四个参数分别为起点,终点,容量,费用(单项边) 12 | $d.MinCostMaxFlow$先后三个参数分别为源点,汇点,费用(引用),返回的为最大流 13 | 14 | ## Tips 15 | **1、调用$d.MinCostMaxFlow$时,记得传入$cost$(一般设置为$0$)** 16 | **2、一定一定要重置结构体的$n$(总点数),因此构造超级源点和超级汇点时一般编号连续** 17 | 18 | ## 代码 19 | ``` cpp 20 | struct MCMF {//记得重置n 21 | struct Edge { 22 | int from, to, cap, flow, cost; //起点,终点,容量,流量,花费 23 | Edge(int u, int v, int c, int f, int w) : from(u), to(v), cap(c), flow(f), cost(w) {} 24 | }; 25 | 26 | int n, m; //结点数,边数(包括反向弧),源点s,汇点t 27 | vector edges; //边表。edges[e]和edges[e^1]互为反向弧 28 | vector G[maxn]; //邻接表,G[i][j]表示结点i的第j条边在edges数组中的序号 29 | bool inq[maxn]; //是否在队列中 30 | int d[maxn]; //Bellman-Ford 31 | int p[maxn]; //上一条弧 32 | int a[maxn]; //可改进量 33 | 34 | void init(int n) { 35 | this->n = n; 36 | edges.clear(); 37 | for (int i = 0; i <= n; i++) 38 | G[i].clear(); 39 | } 40 | 41 | void addedge(int from, int to, int cap, int cost) { 42 | edges.push_back(Edge(from, to, cap, 0, cost)); 43 | edges.push_back(Edge(to, from, 0, 0, -cost)); 44 | m = edges.size(); 45 | G[from].push_back(m - 2); 46 | G[to].push_back(m - 1); 47 | } 48 | 49 | bool BellmanFord(int s, int t, int &flow, long long &cost)//SPFA 50 | { 51 | for (int i = 0; i <= n; i++) 52 | d[i] = inf; 53 | memset(inq, false, sizeof(inq)); 54 | d[s] = 0; 55 | inq[s] = true; 56 | p[s] = 0; 57 | a[s] = inf; 58 | queue Q; 59 | Q.push(s); 60 | while (!Q.empty()) { 61 | int u = Q.front(); 62 | Q.pop(); 63 | inq[u] = false; 64 | for (int i = 0; i < G[u].size(); i++) { 65 | Edge &e = edges[G[u][i]]; 66 | if (e.cap > e.flow && d[e.to] > d[u] + e.cost) { 67 | d[e.to] = d[u] + e.cost; 68 | p[e.to] = G[u][i]; 69 | a[e.to] = min(a[u], e.cap - e.flow); 70 | if (!inq[e.to]) { 71 | Q.push(e.to); 72 | inq[e.to] = true; 73 | } 74 | } 75 | } 76 | } 77 | if (d[t] == inf) return false; 78 | flow += a[t]; 79 | cost += (long long) d[t] * (long long) a[t]; 80 | for (int u = t; u != s; u = edges[p[u]].from) { 81 | edges[p[u]].flow += a[t]; 82 | edges[p[u] ^ 1].flow -= a[t]; 83 | } 84 | return true; 85 | } 86 | 87 | int MinCostMaxFlow(int s, int t, long long &cost) { 88 | int flow = 0; 89 | cost = 0; 90 | while (BellmanFord(s, t, flow, cost)); 91 | return flow; 92 | } 93 | 94 | } d; 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/Maxflow&Mincut(Dinic).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 最大流&&最小割(Dinic) 3 | --- 4 | 5 | # 最大流&&最小割(使用$Dinic$) 6 | 7 | ## 说明 8 | 使用结构体,可改用命名空间 9 | 10 | ## 使用 11 | $d.ClearFlow$表示将流清空(有利于添边重新计算) 12 | $d.AddEdge$表示添加单向边 13 | $d.maxflow$表示最大流(需要输入源点和汇点) 14 | $d.ClearAll$表示初始化(适用于多组数据) 15 | $d.Mincut$表示最小割的编号集合,调用d.edges可查询到该边 16 | 17 | ## Tips 18 | 最小割需在调用最大流后进行 19 | 20 | ## 代码 21 | ``` cpp 22 | struct Edge { 23 | int from, to, cap, flow; 24 | }; 25 | 26 | struct Dinic { 27 | int n, m, s, t; 28 | vector edges; // 边数的两倍 29 | vector G[maxn]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号 30 | bool vis[maxn]; // BFS使用 31 | int d[maxn]; // 从起点到i的距离 32 | int cur[maxn]; // 当前弧指针 33 | 34 | void ClearAll(int n) { 35 | for (int i = 0; i < n; i++) G[i].clear(); 36 | edges.clear(); 37 | } 38 | 39 | void ClearFlow() { 40 | for (int i = 0; i < edges.size(); i++) edges[i].flow = 0; 41 | } 42 | 43 | void AddEdge(int from, int to, int cap) { 44 | edges.push_back((Edge) {from, to, cap, 0}); 45 | edges.push_back((Edge) {to, from, 0, 0});//反向弧容量为0 46 | m = edges.size(); 47 | G[from].push_back(m - 2); 48 | G[to].push_back(m - 1); 49 | } 50 | 51 | bool BFS() { 52 | memset(vis, 0, sizeof(vis)); 53 | queue Q; 54 | Q.push(s); 55 | vis[s] = 1; 56 | d[s] = 0; 57 | while (!Q.empty()) { 58 | int x = Q.front(); 59 | Q.pop(); 60 | for (int i = 0; i < G[x].size(); i++) { 61 | Edge &e = edges[G[x][i]]; 62 | if (!vis[e.to] && e.cap > e.flow) { 63 | vis[e.to] = 1; 64 | d[e.to] = d[x] + 1; 65 | Q.push(e.to); 66 | } 67 | } 68 | } 69 | return vis[t]; 70 | } 71 | 72 | int DFS(int x, int a) { 73 | if (x == t || a == 0) return a; 74 | int flow = 0, f; 75 | for (int &i = cur[x]; i < G[x].size(); i++) { 76 | Edge &e = edges[G[x][i]]; 77 | if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) { 78 | e.flow += f; 79 | edges[G[x][i] ^ 1].flow -= f; 80 | flow += f; 81 | a -= f; 82 | if (a == 0) break; 83 | } 84 | } 85 | return flow; 86 | } 87 | 88 | int Maxflow(int s, int t) { 89 | this->s = s; 90 | this->t = t; 91 | int flow = 0; 92 | while (BFS()) { 93 | memset(cur, 0, sizeof(cur)); 94 | flow += DFS(s, inf); 95 | } 96 | return flow; 97 | } 98 | 99 | vector Mincut() { // call this after maxflow 100 | vector ans; 101 | for (int i = 0; i < edges.size(); i++) { 102 | Edge &e = edges[i]; 103 | if (vis[e.from] && !vis[e.to] && e.cap > 0) ans.push_back(i); 104 | } 105 | return ans;//返回一个vector,存储最小割集合 106 | } 107 | } d; 108 | 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/code_template/graph_theory/SPFA_extend.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SPFA最短路&&判断正负环 3 | --- 4 | 5 | # SPFA最短路(扩展判断正负环) 6 | 7 | ## 说明 8 | 9 | ::: warning 10 | 11 | 在没有负环、单纯求最短路径,不建议使用SPFA算法,而是用Dijkstra算法。在有负环的情况下,可以考虑SPFA算法。 12 | 13 | ::: 14 | 15 | ## 使用 16 | 17 | ```cpp 18 | //配合建图模板 19 | struct edge 20 | { 21 | int from,to,w,next;//from 代表从哪里开始,to是到哪里去 w代表权值 22 | }Node[MAXN]; 23 | int head[MAXN]; 24 | int vis[MAXN];//判断vis[i]是否在队列中 25 | int dist[MAXN];//dist[i]代表点给定的点S到点i的最短距离 26 | int n,m,t; 27 | void add(int i,int j,int w)//i为第一个点,j为第二个点,w为其权值。注意当为无向图的时候要建立两条边 28 | { 29 | Node[t].from=i; 30 | Node[t].to=j; 31 | Node[t].w=w; 32 | Node[t].next=head[i]; 33 | head[i]=t++; 34 | } 35 | ``` 36 | 37 | ## Tips 38 | 39 | 注意数组开的大小 40 | 41 | ## 代码 42 | 43 | ```cpp 44 | void spfa(int s) 45 | { 46 | queue q; 47 | for(int i=1;i<=n;i++) 48 | dist[i]=inf; 49 | memset(vis,false,sizeof(vis)); 50 | q.push(s); 51 | dist[s]=0; 52 | while(!q.empty()) 53 | { 54 | int u=q.front(); 55 | q.pop(); 56 | vis[u]=false; 57 | for(int i=head[u];i!=-1;i=Node[i].next) 58 | { 59 | int v=Node[i].to; 60 | if(dist[v]>dist[u]+Node[i].w) 61 | { 62 | dist[v]=dist[u]+Node[i].w; 63 | if(!vis[v]) 64 | { 65 | vis[v]=true; 66 | q.push(v); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | /*int main() 73 | { 74 | int a,b,c,s,e; 75 | scanf("%d%d",&n,&m); 76 | t=0; 77 | memset(head,-1,sizeof(head)); 78 | while(m--) 79 | { 80 | scanf("%d%d%d",&a,&b,&c); 81 | add(a,b,c);//加入边a,b 82 | } 83 | scanf("%d%d",&s,&e);从s跑到e 84 | spfa(s);//跑一边spfa 85 | if(dist[e]==inf) printf("-1\n"); 86 | else printf("%d\n",dist[e]);//输出距离 87 | return 0; 88 | }*/ 89 | ``` 90 | 91 | ## 扩展 92 | 93 | #### [P3385 【模板】负环](https://www.luogu.org/problem/P3385) 94 | 95 | 判断是否有负环(从源点1开始) 96 | 97 | #### 代码 98 | 99 | ```cpp 100 | /* 101 | struct edge 102 | { 103 | int from,to,w,next;//同上 104 | }Node[MAXN]; 105 | int n,m,s,cnt,x,y,z,c[MAXN]; 106 | int dist[MAXN],vis[MAXN],head[MAXN]; 107 | */ 108 | int spfa() 109 | { 110 | queue q; 111 | for(int i=1;i<=n;i++) 112 | dis[i]=INF; 113 | dis[1]=0; 114 | memset(vis,0,sizeof(vis)); 115 | memset(c,0,sizeof(c)); 116 | q.push(1);//入队 这一个可有可无,因为他是从源点1入队的,如果从任一一个顶点,修改int spfa(int s) 117 | //q.push(s){s代表一个其中一个顶点顶点} 118 | vis[1]=1;//初始化 119 | while(!q.empty()) 120 | { 121 | int u=q.front(); 122 | q.pop();//出队 123 | vis[u]=0;//出队标记 124 | for(int i=head[u];i!=-1;i=Node[i].next)//所有u能走的路 125 | { 126 | int v=Node[i].to; 127 | if(dis[v]>dis[u]+Node[i].w) 128 | { 129 | dis[v]=dis[u]+Node[i].w;//取最小的 130 | c[v]++;//更新 131 | if(c[v]>n) return 0;//出现超过n次表示就有负环 132 | if(vis[v]==0)//如果这个点没在队列里就标记入队 133 | { 134 | vis[v]=1; 135 | q.push(v); 136 | } 137 | } 138 | } 139 | } 140 | return 1; 141 | } 142 | /*int main() 143 | { 144 | int t; 145 | cin>>t; 146 | while(t--) 147 | { 148 | cin>>n>>m; 149 | memset(head,-1,sizeof(head)); 150 | for(int i=1;i<=m;i++) 151 | { 152 | cin>>x>>y>>z; 153 | add(x,y,z);//同上 154 | if(z>=0)//题目中 若w<0则为单向,否则双向 若是双向添加双向边,其他同上 155 | add(y,x,z); 156 | } 157 | if(spfa()==0) cout<<"YE5"< 117 | graph TD; 118 | U[Worker]--通知更新-->A[Master] 119 | A--广播-->B[Worker 2] 120 | A--广播-->C[Worker 3] 121 | A--广播-->D[Worker 4] 122 | A--广播-->E[Worker 5] 123 | 124 | ``` 125 | 包围您所输入的流程图代码。 126 | 效果如下: 127 | 128 | graph TD; 129 | U[Worker]--通知更新-->A[Master] 130 | A--广播-->B[Worker 2] 131 | A--广播-->C[Worker 3] 132 | A--广播-->D[Worker 4] 133 | A--广播-->E[Worker 5] 134 | 135 | 136 | ### 附加功能 137 | 您在目录下添加文件夹时,为了实现一些扩展特性,可以在文件夹目录下添加`manifest.js`文件。 138 | 139 | 文件的内容模板如下: 140 | ```javascript 141 | module.exports = { 142 | sort: true, 143 | sortFn: (a, b) => a - b 144 | } 145 | ``` 146 | `sort`: 要求对该文件夹下的文件进行排序。排序函数若未给出,则根据字典序进行排序。 147 | 148 | 若需要使用自定义排序函数,请添加`sortFn`属性,包含一个传入`sort()`中的比较函数。一个典型的比较函数写法如上述代码中的`sortFn` 149 | 150 | ### 预览文档 151 | 通过VuePress的构建工具,我们可以一边编写文档,一边在浏览器中查看文档经过编译的结果。 152 | 153 | 您需要通过执行 154 | ```shell script 155 | npm run docs:dev 156 | ``` 157 | 启动热更新模式。在开发环境启动完成后,程序会返回一个本地预览地址,您可以通过该地址访问您本地构建的文档。 158 | 159 | 脚手架会监听文档目录下文件的变动情况,在您保存文件后,VuePress会自动进行编译,并把内容推送到本地测试地址的页面中。 160 | 161 | ::: warning 162 | 由于webpack热更新的限制,暂时无法自动更新侧边栏,因此若想浏览侧边栏的效果,请重启脚本并刷新页面。 163 | ::: 164 | 165 | ### 发布 166 | 当您完成对文档更改,并通过Git提交到您的Github fork repo以后,请给主仓库推一个Pull Request,必要的时候可以要求管理员对您的更改进行Review。 167 | 168 | **新手务必指定管理员进行Code Review,以保证提交格式合法规范。** 169 | 170 | Github Actions会对您的PR进行打包测试。打包测试通过以后管理员才会考虑将您的更改合并到主分支。 171 | 172 | 管理员在同意您的PR后,Github Actions会根据您更新的内容进行一次完整构建,并将编译好的`dist`文件夹下的文件推送至主仓库的`gh-pages`分支下。您可以查看commit log确认更新推送情况。 173 | 174 | ::: tip 175 | Pull Request请尽量用于合并变更到主分支,其他情况下尽可能使用本地git进行版本管理 176 | 为保证您的分支清爽整洁,请善用`git pull --rebase`命令。 177 | ::: 178 | 179 | 在构建完成后,您便可以通过本文档的地址查看更改后的最新的内容。 180 | 181 | ::: warning 182 | 预定义构建脚本使用了JsDelivr作为CDN加速本文档静态文件(除html)的访问速度,由于发布存在延迟,CDN的资源库不能及时更新。 183 | 请等待约15分钟直到CDN节点完成缓存分发任务 184 | ::: 185 | -------------------------------------------------------------------------------- /docs/tools/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CLion安装和激活 3 | --- 4 | 5 | ::: tip 6 | 本文原作者为[@Slian](https://github.com/Slian22) 7 | ::: 8 | 9 | # CLion安装和激活 10 | ::: tip 11 | [@ryanlee2014](https://github.com/ryanlee2014): Jetbrains套件下的其他IDE也可以使用本Manual的前五点完成激活。 12 | ::: 13 | 14 | ::: tip 15 | [RhythmLian](https://github.com/Rhythmicc): CLion的实用扩展工具。 16 | ::: 17 | 18 | ::: warning 19 | 本文内容以Windows视角进行整理,关于macOS系统中不同的部分将会使用**warning** tag特别标注 20 | ::: 21 | 22 | ## 关于ToolBox 23 | 24 | ![image1](image/image1.png) 25 | 26 | 27 | 28 | 简单来说,就是可以下载JetBrain全家桶(我全都要) 29 | 30 | ## First: 登录激活学生邮箱 31 | 32 | 登录[中国石油大学(北京)学生邮箱](http://mail.student.cup.edu.cn/),默认用户名为学号(例如2017010001),默认密码为生日(例如20000101),第一次登录后需要修改密码,请务必牢记修改后的密码。 33 | 34 | ![image2](image/image2.png) 35 | 36 | ## Second: 申请JetBrain账号&&申请学生免费版本JetBrain全家桶 37 | 38 | **在此之前一定要申请一个JetBrain的账号** 39 | 40 | 点击前往[JetBrains账号申请链接](https://account.jetbrains.com/login) 41 | 42 | Not Registered yet? 43 | 44 | **Create JetBrains Account!** 45 | 46 | **注意,你所申请的邮箱账号必须是你的学生邮箱号!** 47 | 48 | ![image23](image/image23.jpg) 49 | 50 | 之后请点击前往[JetBrains Toolbox 专业开发工具 51 | 学生免费授权计划](https://www.jetbrains.com/zh/student/) 52 | 53 | ![image3](image/image3.png) 54 | 55 | ![image4](image/image4.png) 56 | 57 | 点击立刻申请,就会出现以下画面 58 | 59 | ![image5](image/image5.png) 60 | 61 | 填写 62 | 63 | ![image6](image/image6.png) 64 | 65 | 以下必点 66 | 67 | ![image7](image/image7.png) 68 | 69 | 重新打开学生邮箱,此时会看到来自Jetbrains的验证邮件,点击链接即可。 70 | 71 | ![image8](image/image8.png) 72 | 73 | 在新打开的页面中设置Jetbrains账号的密码即可。 74 | 75 | ## Third:下载ToolBox 76 | 77 | 登录页面下载 78 | 79 | [Toolbox App](https://www.jetbrains.com/toolbox/app/) 80 | 81 | **注意** 82 | 83 | ::: warning 84 | macOS用户同样需要注意用户名问题,请参考macOS的用户名更改方式 85 | ::: 86 | 87 | 如果你的用户名字为中文(之前我得用户名为C:\Users\单联天),请务必设置第四步。如果用户名为英语,即可跳过第四步。 88 | 89 | ![image9](image/image9.png) 90 | 91 | 下载完成以后 92 | 93 | ![image10](image/image10.png) 94 | 95 | 点击右上角的Setting键,找到相关目录,然后找到文档目录,将文件迁到别的盘(为了节省C盘空间) 96 | ::: tip 97 | [@ryanlee2014](https://github.com/ryanlee2014): 如果你的C盘空间足够,那么建议你装在C盘,可以节省许多不必要的麻烦。 98 | ::: 99 | 100 | ::: warning 101 | macOS用户无需切换盘符 102 | ::: 103 | 104 | 文件迁到别的盘的情况下,下载的文档即可在别的盘下载下来。 105 | 106 | (ToolBox会将所有文件下载到自己的目录下) 107 | 108 | ## Forth:Windows10修改中文用户文件夹 109 | 110 | **尽早将所有文件名用英文命名,这是一个好习惯。(不然配置编译器的时候就会很吃亏)** 111 | 112 | 参考文章:Windows10_如何修改用户文件夹下的中文用户文件夹名 113 | 114 | [https://blog.csdn.net/tanzey/article/details/82657816](https://blog.csdn.net/tanzey/article/details/82657816) 115 | 116 | ::: warning 117 | macOS用户更改用户名的教程请自行通过搜索引擎查找。 118 | 请善用[Google](https://www.google.com),如果你在校园网可以使用[Google IPv6](https://ipv6.google.com) 119 | ::: 120 | 121 | 亲身实践做完之后,用杀毒软件做一下检测,自动将别的环境变量改过来了。 122 | 123 | (不然也可以自己重装一下系统,换一个用户名-English) 124 | 125 | 或者,可以花个钱,重装系统。 126 | 127 | ## Fifth:下载并激活CLion 128 | 129 | ### 下载 130 | 131 | 打开ToolBox,找到CLion,点击install。 132 | 133 | 等待即可 134 | 135 | 最终文档目录在开始菜单中的最近添加显示 136 | 137 | ![image11](image/image11.png) 138 | 139 | ### 激活 140 | 141 | 打开CLion,在出现的激活窗口输入Jetbrains账号(学生邮箱)密码(自己设定),点击“Activate”按钮即可。 142 | 143 | ![image12](image/image12.png) 144 | 145 | ## Sixth:配置CLion 146 | 147 | ::: warning 148 | macOS用户请使用 149 | ```shell script 150 | brew install gcc 151 | ``` 152 | 安装GCC套件,若没有安装`HomeBrew`的可以Google安装 153 | 154 | 然后在CLion新建好的项目中,找到`CMakeLists.txt`文件,在`add_executable`前一行添加 155 | ```shell script 156 | set(CMAKE_C_COMPILER /usr/local/bin/gcc-9) 157 | set(CMAKE_CXX_COMPILER /user/local/bin/g++-9) 158 | ``` 159 | 其中那个`9`代表你安装的gcc版本,请根据实际安装的版本更改版本号。如`gcc-8`,`g++-8`。 160 | ::: 161 | 162 | **方法一:下载MinGW压缩包** 163 | 164 | **(1)下载MinGW** 165 | 这里必须注意的是下载压缩包,不要下载离线安装的版本,不然你会装到猴年马月,而且安装难度较大。 166 | 打开下载地址:[MinGW](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/) 进入下载页面 167 | 168 | ![image13](image/image13.png) 169 | 170 | **注意:不要手贱点绿色按钮去下载!!!** 171 | 往下拉,一直拉到下面的界面,然后就可以下载压缩包了。 172 | 173 | ![image14](image/image14.png) 174 | 175 | 下载了以后必须是解压啊,然后开始配置CLion。 176 | 177 | 打开CLion,左上角File-Settings-Build-Toolchains,然后点击 **+** 号 178 | 179 | ![image15](image/image15.png) 180 | 181 | Environment选择MinGW,然后填入刚刚解压的MinGW64的路径(这是我的路径): 182 | 183 | E:\FOrMinGW\mingw64 184 | 185 | 然后CLion会自动帮你填上所有你该填的东西,点击OK,等调试的小虫子变绿就可以了。 186 | 187 | **当然,也会出现无法检测成功的情况,这时候就需要手动填写啦。** 188 | 调试完成!!! 189 | 190 | **方法二:借助DEV C++** 191 | 192 | **(1)下载安装DEV C++** 193 | https://sourceforge.net/projects/orwelldevcpp/ 194 | 195 | 安装默认路径即可,注意,选择full安装!!! 196 | 197 | **(2)配置CLion** 198 | 199 | 同样地,打开CLion,左上角File-Settings-Build-Toolchains,然后点击 **+** 号 200 | 201 | ![image16](image/image16.png) 202 | 203 | ![image17](image/image17.png) 204 | 205 | Environment选择MinGW,然后填入刚刚安装的dev cpp的MinGW64的路径: 206 | 207 | E:\ Dev-Cpp\MinGW64 208 | 209 | 然后OK,等一段时间就可以了。 210 | 211 | ## Seventh:运行一个简单的Program 212 | 213 | · 左上角File->New Project 214 | 215 | ![image18](image/image18.png) 216 | 217 | Location是你的存放程序的地址。 218 | 219 | 我这里设置的是E:\HelloWorld 220 | 221 | ![image19](image/image19.png) 222 | 223 | Create以后,出现以下画面,稍微等一会(CLion在加载程序) 224 | 225 | ![image20](image/image20.png) 226 | 227 | 右键程序(随便一处就行) 228 | 229 | 点击Run ‘HelloWorld’或者使用快捷键Ctrl+Shift+F10运行程序 230 | 231 | ![image21](image/image21.png) 232 | 233 | 或者点击右上角的绿色剪头即可运行程序。 234 | 235 | ![image22](image/image22.png) 236 | 237 | The End。 238 | 239 | ## Eighth:CLion进阶 240 | 241 | [CLion基本使用方法](https://blog.csdn.net/CSDNhuaong/article/details/88094027) 242 | 243 | [Clion配置参考](https://www.jianshu.com/p/1aa989808e15) 244 | 245 | 246 | ## Ninth: CLion扩展工具 247 | 248 | - [Qpro](https://pypi.org/project/Qpro/), author: [RhythmLian](https://github.com/Rhythmicc)**(必须保证电脑拥有Python)** 249 | -------------------------------------------------------------------------------- /docs/oj_document/spj.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何为题目编写Special Judge 3 | sidebarDepth: 2 4 | --- 5 | # 前言 6 | 根据CUP Online Judge开发文档,我们知道Judger支持除了文本对比以外的第二种判题模式: Special Judge。本文主要为题目编写者及做题的同学用于学习或者进行开发使用。 7 | 8 | # Special Judge原理简介 9 | 根据[judge_client.cpp#L339](https://github.com/CUP-ACM-Programming-Club/CUP-Online-Judge-Judger/blob/master/judge_client.cpp#L339),当题目信息参数带有`spj=1`时,Judger会执行Special Judge模块进行判题,而不是通过默认策略对用户输出与std进行对比 10 | ```cpp 11 | JudgeResult judge_solution(int &ACflg, double &usedtime, double time_lmt, int isspj, 12 | int p_id, char *infile, char *outfile, char *userfile, char *usercode, int &PEflg, 13 | int lang, char *work_dir, int &topmemory, int mem_lmt, 14 | int solution_id, int num_of_test, string &global_work_dir) { 15 | //usedtime-=1000; 16 | shared_ptr languageModel(getLanguageModel(lang)); 17 | cout << "Used time" << endl; 18 | cout << usedtime << endl; 19 | cout << time_lmt * 1000 * (use_max_time ? 1 : num_of_test) << endl; 20 | cout << "judge solution: infile: " << infile << " outfile: " << outfile << " userfile: " << userfile << endl; 21 | int comp_res; 22 | if (!ALL_TEST_MODE) 23 | num_of_test = static_cast(1.0); 24 | if (ACflg == ACCEPT 25 | && usedtime > time_lmt * 1000 * (use_max_time ? 1 : num_of_test)) { 26 | cout << "Time Limit Exceeded" << endl; 27 | usedtime = time_lmt * 1000; 28 | ACflg = TIME_LIMIT_EXCEEDED; 29 | } 30 | if (topmemory > mem_lmt * STD_MB) 31 | ACflg = MEMORY_LIMIT_EXCEEDED; //issues79 32 | languageModel->fixACFlag(ACflg); 33 | // compare 34 | if (ACflg == ACCEPT) { 35 | if (isspj) { 36 | comp_res = SpecialJudge::newInstance().setDebug(DEBUG).run(oj_home, p_id, infile, outfile, userfile, 37 | usercode, global_work_dir); 38 | } else { 39 | shared_ptr compare(getCompareModel()); 40 | compare->setDebug(DEBUG); 41 | comp_res = compare->compare(outfile, userfile); 42 | } 43 | if (comp_res == WRONG_ANSWER) { 44 | ACflg = WRONG_ANSWER; 45 | if (DEBUG) 46 | printf("fail test %s\n", infile); 47 | } else if (comp_res == PRESENTATION_ERROR) 48 | PEflg = PRESENTATION_ERROR; 49 | ACflg = comp_res; 50 | } 51 | //jvm popup messages, if don't consider them will get miss-WrongAnswer 52 | languageModel->fixFlagWithVMIssue(work_dir, ACflg, topmemory, mem_lmt); 53 | return {ACflg, usedtime, topmemory, 0}; 54 | } 55 | ``` 56 | Special Judge模块的执行原理为: 57 | [SpecialJudge.cpp#L19](https://github.com/CUP-ACM-Programming-Club/CUP-Online-Judge-Judger/blob/master/model/judge/policy/SpecialJudge.cpp#L19) 58 | 1. 判断spj文件类型,调用对应Runtime启动,在入参中塞入 59 | 1. std 输入 60 | 2. std 输出 61 | 3. 用户输出 62 | 4. 用户代码 63 | 2. 处理spj返回的结果,并对返回结果做兼容性处理。 64 | 65 | # 编写spj程序 66 | 从判题原理可以得出,spj程序目前支持使用 67 | * static binary executable 68 | * Node.js 69 | * Python 3 70 | 进行编写。 71 | 72 | spj程序通过各自语言的协议从arguments读取用户输出、std输入输出和用户代码信息。 73 | 74 | spj通过判题处理后用程序返回值将判题结果返回。目前支持两种返回的方式: 75 | 1. 使用0代表`ACCEPT`,1代表`WRONG ANSWER` 76 | 2. 使用标准结果枚举返回 77 | 78 | Judger最终会将两种结果归一化,变为标准结果返回给后端服务。 79 | 标准结果枚举可参考该文件: 80 | [index.json](https://github.com/ryanlee2014/CUP-Online-Judge-Frontend/blob/typescript/src/type/index.json) 81 | ```json 82 | "cn": [ 83 | "等待", 84 | "等待重判", 85 | "编译中", 86 | "运行并评判", 87 | "答案正确", 88 | "格式错误", 89 | "答案错误", 90 | "时间超限", 91 | "内存超限", 92 | "输出超限", 93 | "运行错误", 94 | "编译错误", 95 | "编译成功", 96 | "运行完成", 97 | "已加入队列", 98 | "提交被拒绝", 99 | "系统错误", 100 | "" 101 | ] 102 | ``` 103 | 或者该文件 104 | [staic_var.h](https://github.com/CUP-ACM-Programming-Club/CUP-Online-Judge-Judger/blob/master/header/static_var.h) 105 | ```cpp 106 | enum judge_procedure { 107 | WAITING = 0, 108 | WAITING_REJUDGE = 1, 109 | COMPILING = 2, 110 | RUNNING_JUDGING = 3, 111 | ACCEPT = 4, 112 | PRESENTATION_ERROR = 5, 113 | WRONG_ANSWER = 6, 114 | TIME_LIMIT_EXCEEDED = 7, 115 | MEMORY_LIMIT_EXCEEDED = 8, 116 | OUTPUT_LIMIT_EXCEEDED = 9, 117 | RUNTIME_ERROR = 10, 118 | COMPILE_ERROR = 11, 119 | COMPILE_OK = 12, 120 | TEST_RUN = 13, 121 | SUBMITTED = 14, 122 | SYSTEM_REJECTED = 15, 123 | SYSTEM_ERROR = 16 124 | }; 125 | ``` 126 | 127 | ## 一些spj的参考: 128 | C/C++ 129 | ```cpp 130 | #include 131 | #include 132 | #include 133 | 134 | using namespace std; 135 | 136 | int main(int argc, char *args[]) 137 | { 138 | ifstream in(args[1]); 139 | ifstream out(args[2]); 140 | ifstream user(args[3]); 141 | ifstream code(args[4]); 142 | string s; 143 | while (getline(code, s)) 144 | { 145 | if (s.find("1299743") != s.npos||s.find("1299000")!=s.npos||s.find("1299740")!=s.npos||s.find("1147678")!=s.npos||s.find("1299742"!=s.npos)) 146 | { 147 | return 1; 148 | } 149 | } 150 | string s1,s2; 151 | while(out>>s1,user>>s2) 152 | { 153 | if(s1!=s2)return 1; 154 | } 155 | if(!out.eof())return 1; 156 | if(!user.eof())return 1; 157 | return 0; 158 | } 159 | 160 | ``` 161 | Node.js 162 | ```javascript 163 | const fs = require("fs") 164 | const argv = process.argv 165 | try { 166 | let eps = 1e-6 167 | let stdout = fs.readFileSync(argv[3]).toString().trim() 168 | let userout = fs.readFileSync(argv[4]).toString().trim() 169 | let stdoutArray = stdout.split("\n") 170 | let useroutArray = userout.split("\n") 171 | let len = stdoutArray.length 172 | if(len !== useroutArray.length) { 173 | process.exit(6) 174 | } 175 | for(let i = 0;i < len; ++i) { 176 | if(isNaN(useroutArray[i])) { 177 | process.exit(6) 178 | } 179 | if(Math.abs(parseFloat(stdoutArray[i]) - parseFloat(useroutArray[i])) > eps) { 180 | process.exit(6) 181 | } 182 | } 183 | process.exit(4) 184 | } 185 | catch(e) { 186 | process.exit(15) 187 | } 188 | ``` 189 | 目前没有使用Python 3进行spj的例子,因此这里不单独给出Python 3 sample. 190 | 191 | # 上传SPJ的方法 192 | 通过【题目编辑】页面上传文件即可。 193 | ![](https://i.328888.xyz/2023/01/05/WBqNU.png) 194 | 上传前可通过[Problem 1498 SPJ测试](https://oj.cup.edu.cn/problem/edit/1498)对SPJ进行合法性测试 195 | -------------------------------------------------------------------------------- /docs/code_template/init/io_extend.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 增强版输入输出挂 3 | --- 4 | 5 | # 增强版输入输出挂 6 | 7 | ## 说明 8 | 9 | 原文链接:[输入输出挂备份](https://www.haoyuan.info/?p=381) 10 | 在输入挂的基础上做了平滑使用的封装 11 | 12 | ## 使用 13 | 如同`cin`,`cout`一样平滑输入输出 14 | ```cpp 15 | int a; 16 | long long b; 17 | char c; 18 | string d; 19 | char str[100]; 20 | fin >> a >> b >> c >> d >> str << a << b << c << d << str;(wwww) 21 | ``` 22 | 23 | ## Tips 24 | ::: warning 25 | 若不存在宏定义`ONLINE_JUDGE`,输入输入将使用`cin` 26 | ::: 27 | ## 代码 28 | ```cpp 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | using ll = long long; 42 | /* 43 | * 输入挂 44 | * 场场AK buff 45 | */ 46 | namespace IO { 47 | using namespace std; 48 | class endln { 49 | }; 50 | 51 | class iofstream { 52 | private: 53 | int idx; 54 | bool eof; 55 | char buf[100005], *ps, *pe; 56 | char bufout[100005], outtmp[50], *pout, *pend; 57 | 58 | inline void rnext() { 59 | if (++ps == pe) 60 | pe = (ps = buf) + fread(buf, sizeof(char), sizeof(buf) / sizeof(char), stdin), eof = true; 61 | //eof = ps != pe; 62 | } 63 | 64 | inline void write() { 65 | fwrite(bufout, sizeof(char), pout - bufout, stdout); 66 | pout = bufout; 67 | } 68 | 69 | public: 70 | iofstream() : idx(-1), eof(true) { 71 | pe = (ps = buf) + 1; 72 | pend = (pout = bufout) + 100005; 73 | } 74 | 75 | ~iofstream() { 76 | write(); 77 | } 78 | 79 | template 80 | inline bool fin(T &ans) { 81 | #ifdef ONLINE_JUDGE 82 | ans = 0; 83 | T f = 1; 84 | if (ps == pe) { 85 | return eof = false; 86 | } 87 | do { 88 | rnext(); 89 | if ('-' == *ps) f = -1; 90 | } while (!isdigit(*ps) && ps != pe); 91 | if (ps == pe) { 92 | return eof = false; 93 | } 94 | do { 95 | ans = (ans << 1) + (ans << 3) + *ps - 48; 96 | rnext(); 97 | } while (isdigit(*ps) && ps != pe); 98 | ans *= f; 99 | #else 100 | cin >> ans; 101 | #endif 102 | return true; 103 | } 104 | 105 | template 106 | inline bool fdb(T &ans) { 107 | #ifdef ONLINE_JUDGE 108 | ans = 0; 109 | T f = 1; 110 | if (ps == pe) return false;//EOF 111 | do { 112 | rnext(); 113 | if ('-' == *ps) f = -1; 114 | } while (!isdigit(*ps) && ps != pe); 115 | if (ps == pe) return false;//EOF 116 | do { 117 | ans = ans * 10 + *ps - 48; 118 | rnext(); 119 | } while (isdigit(*ps) && ps != pe); 120 | ans *= f; 121 | if (*ps == '.') { 122 | rnext(); 123 | T small = 0; 124 | do { 125 | small = small * 10 + *ps - 48; 126 | rnext(); 127 | } while (isdigit(*ps) && ps != pe); 128 | while (small >= 1) { 129 | small /= 10; 130 | } 131 | ans += small; 132 | } 133 | #else 134 | cin >> ans; 135 | #endif 136 | return true; 137 | } 138 | 139 | /* 140 | * 输出挂 141 | * 超强 超快 142 | */ 143 | 144 | inline bool out_char(const char c) { 145 | #ifdef ONLINE_JUDGE 146 | *(pout++) = c; 147 | if (pout == pend) write(); 148 | #else 149 | cout << c; 150 | #endif 151 | return true; 152 | } 153 | 154 | inline bool out_str(const char *s) { 155 | #ifdef ONLINE_JUDGE 156 | while (*s) { 157 | *(pout++) = *(s++); 158 | if (pout == pend) write(); 159 | } 160 | #else 161 | cout << s; 162 | #endif 163 | return true; 164 | } 165 | 166 | template 167 | inline bool out_double(T x, int idx) { 168 | char str[50]; 169 | string format = "%"; 170 | if (~idx) { 171 | format += '.'; 172 | format += (char) (idx + '0'); 173 | } 174 | format += "f"; 175 | sprintf(str, format.c_str(), x); 176 | out_str(str); 177 | return true; 178 | } 179 | 180 | template 181 | inline bool out_int(T x) { 182 | #ifdef ONLINE_JUDGE 183 | if (!x) { 184 | out_char('0'); 185 | return true; 186 | } 187 | if (x < 0) x = -x, out_char('-'); 188 | int len = 0; 189 | while (x) { 190 | outtmp[len++] = x % 10 + 48; 191 | x /= 10; 192 | } 193 | outtmp[len] = 0; 194 | for (int i = 0, j = len - 1; i < j; ++i, --j) swap(outtmp[i], outtmp[j]); 195 | out_str(outtmp); 196 | #else 197 | cout << x; 198 | #endif 199 | return true; 200 | } 201 | 202 | 203 | inline iofstream &operator<<(const double &x) { 204 | out_double(x, idx); 205 | return *this; 206 | } 207 | 208 | inline iofstream &operator<<(const int &x) { 209 | out_int(x); 210 | return *this; 211 | } 212 | 213 | inline iofstream &operator<<(const unsigned long long &x) { 214 | out_int(x); 215 | return *this; 216 | } 217 | 218 | inline iofstream &operator<<(const unsigned &x) { 219 | out_int(x); 220 | return *this; 221 | } 222 | 223 | inline iofstream &operator<<(const long &x) { 224 | out_int(x); 225 | return *this; 226 | } 227 | 228 | inline iofstream &operator<<(const ll &x) { 229 | out_int(x); 230 | return *this; 231 | } 232 | 233 | inline iofstream &operator<<(const endln &x) { 234 | out_char('\n'); 235 | return *this; 236 | } 237 | 238 | 239 | inline iofstream &operator<<(const char *x) { 240 | out_str(x); 241 | return *this; 242 | } 243 | 244 | inline iofstream &operator<<(const string &x) { 245 | out_str(x.c_str()); 246 | return *this; 247 | } 248 | 249 | inline iofstream &operator<<(const char &x) { 250 | out_char(x); 251 | return *this; 252 | } 253 | 254 | inline bool setw(int x) { 255 | if (x >= 0) { 256 | idx = x; 257 | return true; 258 | } 259 | return false; 260 | } 261 | 262 | inline iofstream &operator>>(int &x) { 263 | if (!fin(x) && !x)eof = false; 264 | return *this; 265 | } 266 | 267 | inline iofstream &operator>>(ll &x) { 268 | if (!fin(x) && !x)eof = false; 269 | return *this; 270 | } 271 | 272 | inline iofstream &operator>>(double &x) { 273 | if (!fdb(x) && x == 0.0)eof = false; 274 | return *this; 275 | } 276 | 277 | inline iofstream &operator>>(float &x) { 278 | if (!fdb(x) && x == 0.0)eof = false; 279 | return *this; 280 | } 281 | 282 | inline iofstream &operator>>(unsigned &x) { 283 | if (!fin(x) && !x)eof = false; 284 | return *this; 285 | } 286 | 287 | inline iofstream &operator>>(unsigned long long &x) { 288 | if (!fin(x) && !x)eof = false; 289 | return *this; 290 | } 291 | 292 | inline explicit operator bool() { 293 | return eof; 294 | } 295 | 296 | inline char getchar() { 297 | #ifdef ONLINE_JUDGE 298 | if (ps == pe) { 299 | return eof = false; 300 | } 301 | rnext(); 302 | if (ps + 1 == pe) 303 | eof = false; 304 | return *ps; 305 | #else 306 | return std::getchar(); 307 | #endif 308 | } 309 | 310 | inline iofstream &operator>>(char *str) { 311 | #ifdef ONLINE_JUDGE 312 | if (ps == pe) { 313 | eof = false;//EOF 314 | return *this; 315 | } 316 | do { 317 | rnext(); 318 | } while (isspace(*ps) && iscntrl(*ps) && ps != pe); 319 | if (ps == pe) { 320 | eof = false;//EOF 321 | return *this; 322 | } 323 | do { 324 | *str = *ps; 325 | ++str; 326 | rnext(); 327 | } while (!(isspace(*ps) || iscntrl(*ps)) && ps != pe); 328 | *str = '\0'; 329 | return *this; 330 | #else 331 | cin >> str; 332 | return *this; 333 | #endif 334 | } 335 | 336 | inline iofstream &operator>>(string &str) { 337 | #ifdef ONLINE_JUDGE 338 | str.clear(); 339 | if (ps == pe) { 340 | eof = false;//EOF 341 | return *this; 342 | } 343 | do { 344 | rnext(); 345 | } while (isspace(*ps) && iscntrl(*ps) && ps != pe); 346 | if (ps == pe) { 347 | eof = false;//EOF 348 | return *this; 349 | } 350 | do { 351 | str += *ps; 352 | rnext(); 353 | } while (!(isspace(*ps) || iscntrl(*ps)) && ps != pe); 354 | return *this; 355 | #else 356 | cin >> str; 357 | return *this; 358 | #endif 359 | } 360 | }; 361 | 362 | static iofstream fin; 363 | static endln ln; 364 | } 365 | using IO::fin; 366 | ``` 367 | -------------------------------------------------------------------------------- /docs/solution/icpc2019shenyang/H.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: H.Texas hold'em Poker 3 | --- 4 | # H.Texas hold'em Poker 5 | ## 题意 6 | 若干人打德州扑克(~~并不是~~),根据给定的规则比较谁更大,若根据规则两人大小相等则比较姓名字典序大小。 7 | 牌面一共有8种,此处不复述题目内容 8 | ## 思路 9 | 大模拟题,细节很多。**注意多组样例** 10 | 重载`<`,建立比较函数,预处理牌面类型即可。 11 | ## 代码 12 | ```cpp 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | enum { 23 | STRAIGHT = 49, 24 | ROYAL_STRAIGHT = 50, 25 | FOUR = 48, 26 | THREE_TWO = 47, 27 | TWO_PAIR = 44, 28 | PAIR = 43, 29 | THREE = 45, 30 | HIGHEST = 42 31 | }; 32 | 33 | vector convert(string& s) { 34 | vector vec; 35 | int sz = s.length(); 36 | for(int i = 0; i < sz; ++i) { 37 | if (s[i] == '1') { 38 | ++i; 39 | vec.push_back(10); 40 | continue; 41 | } 42 | switch(s[i]) { 43 | case 'J': 44 | vec.push_back(11); 45 | break; 46 | case 'Q': 47 | vec.push_back(12); 48 | break; 49 | case 'K': 50 | vec.push_back(13); 51 | break; 52 | case 'A': 53 | vec.push_back(1); 54 | break; 55 | default: 56 | vec.push_back(s[i] - '0'); 57 | } 58 | } 59 | return vec; 60 | } 61 | 62 | struct Node { 63 | string name; 64 | string _card; 65 | vectorcard; 66 | int Four; 67 | int FourRemain; 68 | int Three; 69 | int ThreeRemain; 70 | 71 | pairThreeTwo; 72 | pair TwoPair; 73 | int TwoPairRemain; 74 | int PPair; 75 | int PPairSum; 76 | int highest; 77 | int type = -1; 78 | Node(){} 79 | Node(string& name, string& card){ 80 | this->_card = card; 81 | this->name = name; 82 | } 83 | bool operator < (const Node& v) const { 84 | int val = type; 85 | int target_val = v.type; 86 | if (val != target_val) { 87 | return val > target_val; 88 | } 89 | if (type == ROYAL_STRAIGHT) { 90 | return name < v.name; 91 | } 92 | if (type == STRAIGHT) { 93 | if (card != v.card) { 94 | return card > v.card; 95 | } 96 | return name < v.name; 97 | } 98 | if (type == FOUR) { 99 | if (Four != v.Four) { 100 | return Four > v.Four; 101 | } 102 | if (FourRemain == v.FourRemain) { 103 | return name < v.name; 104 | } 105 | else { 106 | return FourRemain > v.FourRemain; 107 | } 108 | } 109 | if (type == THREE_TWO) { 110 | if (ThreeTwo != v.ThreeTwo) { 111 | return ThreeTwo > v.ThreeTwo; 112 | } 113 | return name < v.name; 114 | } 115 | if (type == THREE) { 116 | if (Three != v.Three) { 117 | return Three > v.Three; 118 | } 119 | if (ThreeRemain == v.ThreeRemain) { 120 | return name < v.name; 121 | } 122 | else { 123 | return ThreeRemain > v.ThreeRemain; 124 | } 125 | } 126 | if (type == TWO_PAIR) { 127 | if (TwoPair != v. TwoPair) { 128 | return TwoPair > v.TwoPair; 129 | } 130 | if (TwoPairRemain == v.TwoPairRemain) { 131 | return name < v.name; 132 | } 133 | else { 134 | return TwoPairRemain > v.TwoPairRemain; 135 | } 136 | } 137 | if (type == PAIR) { 138 | if (PPair != v.PPair) { 139 | return PPair > v.PPair; 140 | } 141 | if (PPairSum == v.PPairSum) { 142 | return name < v.name; 143 | } 144 | else { 145 | return PPairSum > v.PPairSum; 146 | } 147 | } 148 | if (highest != v.highest) { 149 | return highest > v.highest; 150 | } 151 | return name < v.name; 152 | } 153 | 154 | int cal(Node node) const { 155 | if (type == -1) return node.cal(); 156 | return type; 157 | } 158 | 159 | void init() { 160 | type = cal(); 161 | } 162 | 163 | int cal() { 164 | if (royalStraight()) { 165 | return ROYAL_STRAIGHT; 166 | } 167 | if (straight()) { 168 | return STRAIGHT; 169 | } 170 | if (four()) { 171 | return FOUR; 172 | } 173 | if (threeTwo()) { 174 | return THREE_TWO; 175 | } 176 | if (three()) { 177 | return THREE; 178 | } 179 | if (two_pair()) { 180 | return TWO_PAIR; 181 | } 182 | if (one_pair()) { 183 | return PAIR; 184 | } 185 | highestCard(); 186 | return HIGHEST; 187 | } 188 | 189 | bool royalStraight() { 190 | genCard(); 191 | if (_card.find("10") != _card.npos && _card.find("J") != _card.npos && _card.find("Q") != _card.npos && _card.find("K") != _card.npos && _card.find("A") != _card.npos) { 192 | return true; 193 | } 194 | return false; 195 | } 196 | 197 | void genCard() { 198 | if (card.size() == 0) { 199 | card = convert(_card); 200 | sort(card.begin(), card.end()); 201 | } 202 | } 203 | 204 | bool straight() { 205 | genCard(); 206 | vector&array = card; 207 | for(int i = 0; i < array.size() - 1; ++i) { 208 | if (array[i] + 1 != array[i + 1]) { 209 | return false; 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | bool highestCard() { 216 | genCard(); 217 | highest = accumulate(card.begin(), card.end(), 0); 218 | return true; 219 | } 220 | 221 | bool one_pair() { 222 | genCard(); 223 | vector&array = card; 224 | vector >vec; 225 | for(int & i : card) { 226 | if (vec.empty()) { 227 | vec.push_back({i, 1}); 228 | continue; 229 | } 230 | if (i == (*vec.rbegin()).first) { 231 | ++(*vec.rbegin()).second; 232 | } 233 | else { 234 | vec.push_back({i, 1}); 235 | } 236 | } 237 | bool ok = false; 238 | for(auto & i : vec) { 239 | if (i.second == 2) { 240 | PPair = i.first; 241 | ok = true; 242 | } 243 | else { 244 | PPairSum += i.first; 245 | } 246 | } 247 | return ok; 248 | } 249 | 250 | bool two_pair() { 251 | genCard(); 252 | vector&array = card; 253 | vector >vec; 254 | for(int & i : card) { 255 | if (vec.empty()) { 256 | vec.push_back({i, 1}); 257 | continue; 258 | } 259 | if (i == (*vec.rbegin()).first) { 260 | ++(*vec.rbegin()).second; 261 | } 262 | else { 263 | vec.push_back({i, 1}); 264 | } 265 | } 266 | vectornum; 267 | int cnt = 0; 268 | for(int i = 0; i < vec.size(); ++i) { 269 | if (vec[i].second == 2) { 270 | ++cnt; 271 | num.push_back(vec[i].first); 272 | } 273 | else { 274 | TwoPairRemain = vec[i].first; 275 | } 276 | } 277 | if(cnt == 2) { 278 | TwoPair = {max(num[0], num[1]), min(num[0], num[1])}; 279 | return true; 280 | } 281 | return false; 282 | } 283 | 284 | bool three() { 285 | genCard(); 286 | vector&array = card; 287 | int element = array[0]; 288 | int times = 1; 289 | for(int i = 1; i < array.size(); ++i) { 290 | if (array[i] == element) { 291 | ++times; 292 | if (times >= 3) { 293 | break; 294 | } 295 | } 296 | else { 297 | element = array[i]; 298 | times = 1; 299 | } 300 | } 301 | if (times >= 3) { 302 | Three = element; 303 | ThreeRemain = 0; 304 | for(int i = 0; i < array.size(); ++i) { 305 | if (array[i] != element) { 306 | ThreeRemain += array[i]; 307 | } 308 | } 309 | } 310 | return times >= 3; 311 | } 312 | 313 | bool threeTwo() { 314 | genCard(); 315 | sets; 316 | for(int i : card) { 317 | s.insert(i); 318 | } 319 | if (s.size() == 2) { 320 | vector ele(s.begin(), s.end()); 321 | int cnt = 0; 322 | for(int i : card) { 323 | if (i == ele[0]) ++cnt; 324 | } 325 | if (cnt == 3) { 326 | ThreeTwo = {ele[0], ele[1]}; 327 | } 328 | else { 329 | ThreeTwo = {ele[1], ele[0]}; 330 | } 331 | return true; 332 | } 333 | return false; 334 | } 335 | 336 | bool four() { 337 | genCard(); 338 | vector&array = card; 339 | int element = array[0]; 340 | int times = 1; 341 | for(int i = 1; i < array.size(); ++i) { 342 | if (array[i] == element) { 343 | ++times; 344 | if (times >= 4) { 345 | break; 346 | } 347 | } 348 | else { 349 | element = array[i]; 350 | times = 1; 351 | } 352 | } 353 | if (times >= 4) { 354 | Four = element; 355 | for(int i = 0 ; i < array.size(); ++i) { 356 | if (array[i] != element) { 357 | FourRemain = array[i]; 358 | break; 359 | } 360 | } 361 | } 362 | return times >= 4; 363 | } 364 | }; 365 | const int maxn = 1e5 + 10; 366 | Node node[maxn]; 367 | 368 | int main() { 369 | ios::sync_with_stdio(false); 370 | cin.tie(0); 371 | cout.tie(0); 372 | int n; 373 | while(cin >> n) { 374 | string name, card; 375 | for (int i = 0; i < n; ++i) { 376 | cin >> name >> card; 377 | node[i] = Node(name, card); 378 | node[i].init(); 379 | } 380 | sort(node, node + n); 381 | for (int i = 0; i < n; ++i) { 382 | cout << node[i].name << '\n'; 383 | } 384 | } 385 | } 386 | ``` 387 | -------------------------------------------------------------------------------- /docs/oj_document/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CUP Online Judge开发文档 3 | sidebarDepth: 2 4 | --- 5 | **CUP Online Judge**是一个以Vue.js作为前端框架,ExpressJS 作为后端框架开发的在线评测系统。 6 | 7 | 8 | *注: CUP Virtual Judge属于较为独立的、技术栈未成熟的另外一个程序,因此在本程序中不会涉及到与Virtual Judge系统相关的接口开发问题。* 9 | 10 | **CUP Virtual Judge由于国内各大OJ调整对海外IP地址访问的策略,现在暂时停止提交功能,保留题目内容供查询。** 11 | 12 | ## 判题机重构 13 | 本OJ判题机重构自HUSTOJ。 14 | 由于需求的变更,当前的判题机和原本的HUSTOJ有了很大的差别。CUPOJ Judger无法向下兼容HUSTOJ。 15 | ~~也许HUSTOJ可以向上兼容CUPOJ~~ 16 | 17 | 本判题机和原判题机的区别是对判题语言模块进行了抽象,并将除了判题机核心模块做成动态模块,可自行动态引用。 18 | 同时,HUSTOJ判题机使用libmysql-devel等mysql提供的C库实现拉取数据与更新,而该功能目前已经在CUPOJ Judger中移除。 19 | 20 | 数据的更新和获得通过文件及Socket获取。判题机本身可独立于数据库运行,这是CUPOJ Judger和HUSTOJ判题机的另一大区别。 21 | 22 | 目前判题机的mysql接口已经被单独封装成抽象基类,通过引用动态库调用具体实现。 23 | 24 | CUPOJ Judger的所有动态库均定位在`/usr/lib/cupjudge`目录下。 25 | 26 | 事实证明,通过使用策略模式重构判题机更有利于解耦判题机中的功能,实现开闭原则。 27 | 28 | 下面将具体讲述如何针对判题机开发新语言模组 29 | 30 | ### 为Judger开发新语言模组 31 | CUPOJ Judger语言模组均继承`Language`基类,该基类为抽象类,成员为 32 | 33 | 34 | classDiagram 35 | class Language 36 | Language: #int DEBUG 37 | Language: +run(int memory) void 38 | Language: +setProcessLimit() void 39 | Language: +setCompileProcessLimit() void 40 | Language: +compile(std::vector&, const char*, const char*) void 41 | Language: +buildRuntime(const char* work_dir) void 42 | Language: +double buildTimeLimit(double timeLimit, double bonus) 43 | Language: +buildMemoryLimit(int memoryLimit, int bonus) int 44 | Language: +setExtraPolicy(const char* oj_home, const char* work_dir) void 45 | Language: +initCallCounter(int* call_counter) void 46 | Language: +setCompileExtraConfig() void 47 | Language: +setCompileMount(const char* work_dir) void 48 | Language: +getCompileResult(int status) int 49 | Language: +fixACStatus(int acFlag) int 50 | Language: +getMemory(rusage ruse, pid_t pid) int 51 | Language: +buildChrootSandbox(const char* work_dir) void 52 | Language: +runMemoryLimit(rlimit& LIM) void 53 | Language: +fixACFlag(int& ACflg) void 54 | Language: +enableSim() bool 55 | Language: +fixFlagWithVMIssue(char *work_dir, int &ACflg, int &topmemory,int mem_lmt) void 56 | Language: +std::string getFileSuffix() 57 | Language: +gotErrorWhileRunning(bool error) bool 58 | Language: +isValidExitCode(int exitcode) bool 59 | Language: #setCPULimit() void 60 | Language: #setFSizeLimit() void 61 | Language: #setASLimit() void 62 | Language: #setAlarm() void 63 | 64 | 65 | 66 | classDiagram 67 | class Language 68 | class C11 69 | class C99 70 | class Cpp11 71 | ckass Clang 72 | Language<|--C11 73 | Language<|--Java 74 | Language<|--Bash 75 | Language<|--Csharp 76 | Language<|--Pascal 77 | Language<|--Python2 78 | Language<|--Python3 79 | C11<|--C99 80 | C11<|--Cpp11 81 | C11<|--Clang 82 | Java<|--Java 8 83 | Java<|--Java 7 84 | 85 | 86 | ```cpp 87 | #ifndef JUDGE_CLIENT_LANGUAGE_H 88 | #define JUDGE_CLIENT_LANGUAGE_H 89 | #define extlang extern "C" Language* 90 | #define deslang extern "C" void 91 | #define HOJ_MAX_LIMIT (-1) 92 | const int call_array_size = 512; 93 | #include 94 | #include 95 | #include 96 | class Language { 97 | public: 98 | virtual void run(int memory) = 0; 99 | virtual void setProcessLimit(); 100 | virtual void setCompileProcessLimit(); 101 | virtual void compile(std::vector&, const char*, const char*); 102 | virtual void buildRuntime(const char* work_dir); 103 | virtual double buildTimeLimit(double timeLimit, double bonus); 104 | virtual int buildMemoryLimit(int memoryLimit, int bonus); 105 | virtual void setExtraPolicy(const char* oj_home, const char* work_dir); 106 | virtual void initCallCounter(int* call_counter) = 0; 107 | virtual void setCompileExtraConfig(); 108 | virtual void setCompileMount(const char* work_dir); 109 | virtual int getCompileResult(int status); 110 | virtual int fixACStatus(int acFlag); 111 | virtual int getMemory(rusage ruse, pid_t pid); 112 | virtual void buildChrootSandbox(const char* work_dir); 113 | virtual void runMemoryLimit(rlimit& LIM); 114 | virtual void fixACFlag(int& ACflg); 115 | virtual bool enableSim(); 116 | virtual void fixFlagWithVMIssue(char *work_dir, int &ACflg, int &topmemory,int mem_lmt); 117 | virtual std::string getFileSuffix() = 0; 118 | virtual bool gotErrorWhileRunning(bool error); 119 | virtual bool isValidExitCode(int exitcode); 120 | virtual ~Language(); 121 | protected: 122 | virtual void setCPULimit(); 123 | virtual void setFSizeLimit(); 124 | virtual void setASLimit(); 125 | virtual void setAlarm(); 126 | }; 127 | 128 | typedef Language* createLanguageInstance(); 129 | 130 | typedef void destroyLanguageInstance(Language*); 131 | 132 | 133 | #endif //JUDGE_CLIENT_LANGUAGE_H 134 | 135 | ``` 136 | 137 | 下面将具体讲述该基类各个部分的含义与具体功能。 138 | 139 | ```cpp 140 | #define extlang extern "C" Language* 141 | #define deslang extern "C" void 142 | ``` 143 | `extlang`和`deslang`是一组宏定义。当判题机需要引入一个语言模组,会调用`getLanguageModel`方法: 144 | 145 | #### getLanguageModel 146 | ```cpp 147 | Language* getLanguageModel(int language) { 148 | string languageName = languageNameReader.GetString(to_string(language)); 149 | void* languageHandler = dlopen(("/usr/lib/cupjudge/lib" + languageName + ".so").c_str(), RTLD_LAZY); 150 | if (!languageHandler) { 151 | cerr << "Cannot load library: " << dlerror() << endl; 152 | exit(1); 153 | } 154 | dlerror(); 155 | createLanguageInstance* createInstance = (createLanguageInstance*) dlsym(languageHandler, (string("createInstance") + languageName).c_str()); 156 | const char* dlsym_error = dlerror(); 157 | if (dlsym_error) { 158 | cerr << "Cannot load symbol create: " << dlsym_error << endl; 159 | exit(1); 160 | } 161 | // destroyLanguageInstance* destroyInstance = (destroyLanguageInstance*) dlsym(languageHandler, (string("destroyInstance") + languageName).c_str()); 162 | // dlsym_error = dlerror(); 163 | // if (dlsym_error) { 164 | // cerr << "Cannot load symbol create: " << dlsym_error << endl; 165 | // exit(1); 166 | // } 167 | 168 | Language* languageInstance = createInstance(); 169 | return languageInstance; 170 | } 171 | ``` 172 | 173 | 如以上代码,不难发现该方法通过languageNameReader根据`lang`变量读取了一个字符串。 174 | 175 | 其中,`languageNameReader`是一个JSON类的封装,在这里读取了`/home/judge/etc/language.json`文件。 176 | 177 | 该文件中设置了`lang`变量与对应动态链接库文件名的映射。 178 | 179 | 因此在这里,我们从`languageNameReader`拿到了动态链接库名,并使用`dlfcn.h`头文件动态引入`Language`基类的语言实现实例指针,并返回。 180 | 181 | 在代码中,我们发现`createInstance`方法是取自`lib${languageName}.so`文件中使用`extern "C" createInstance${languageName}()`暴露的方法。 182 | 183 | 而在上面,我们提到`extlang`,`deslang`这一组宏定义。这组宏定义用于定义暴露给Judger调用的一组构造和析构函数。 184 | 185 | 暴露的两个方法名称需要遵守以下命名方式: 186 | `createInstance${languageName}()` 187 | `destroyInstance${languageName}(Language*)` 188 | `languageName`与`language.json`中的语言名对应。 189 | 190 | 如在`language.json`中定义了`C11`语言字段为`"0": "c11"` 191 | 那么这两个方法则命名为: 192 | `createInstancec11()` 193 | `destroyInstancec11(Language*)` 194 | 195 | 而在`getLanguageModel`中则使用`createInstancec11`拿到构造函数的返回指针。 196 | 197 | ```cpp 198 | #define HOJ_MAX_LIMIT (-1) 199 | ``` 200 | 201 | 该宏定义定义了允许的syscall的值。 202 | 可通过在`syscall`目录下设定syscall32.h与syscall64.h头文件,定义判题过程中允许的syscall。 203 | 判题机会通过ptrace跟踪用户程序运行中使用的syscall,并在用户程序调用syscall之前进行检查。 204 | 未经允许的syscall调用将会被判题机以`RUNTIME ERROR`结束进程。 205 | 206 | ```cpp 207 | virtual void run(int memory) = 0; 208 | ``` 209 | 210 | 该方法定义了判题机启动用户已编译的程序文件所调用的方法。 211 | 以`C11`为例,该方法的实现为 212 | ```cpp 213 | void C11::run(int memory) { 214 | execl("./Main", "./Main", (char *) nullptr); 215 | } 216 | ``` 217 | 218 | 需要注意的是,该方法强制重写,所有语言模组必须自行重写该纯虚函数。 219 | 调用则需要以`execl`方法启动。在判题机中,该进程将会作为Judger的子进程`fork`出来,并重定向了输入输出文件流及错误流。 220 | `execl`的使用方法请参考Linux C中关于exec系列函数的手册。 221 | 222 | ```cpp 223 | virtual void setProcessLimit(); 224 | ``` 225 | 226 | 该方法设定用户程序运行时的进程数限制。 227 | 考虑到不同语言的代码运行时会运行的进程数量不同,请根据判题实际可能使用的进程数进行限制。 228 | `Language`默认提供一个`setrlimit`NPROC为1的实现。若该方法不被重写,则限制用户进程仅允许一个进程。 229 | 230 | ```cpp 231 | virtual void setCompileProcessLimit(); 232 | ``` 233 | 234 | 该方法限制判题机在编译用户代码过程中的系统资源限制。 235 | 考虑到不同语言的编译过程对资源要求的不同,该方法建议重写。 236 | 方法默认提供一个基本的资源限制,适用于C及C++语言环境的要求。 237 | 该方法默认调用`protected`中提供的`set*`函数,用于设置编译器资源使用限制,以防止编译炸弹的安全隐患。 238 | 239 | 因此重写该方法时,请注意一并重写`protected`下的所有`set`方法。 240 | 241 | ```cpp 242 | virtual void compile(std::vector&, const char*, const char*); 243 | ``` 244 | 245 | 该方法提供了一个通用语言具体的compile流程。`Language`基类提供了一个通配编译流程,可以不用重写该方法。 246 | 247 | 不过仍然需要注意的是,编译的shell参数表保存在`/home/judge/etc/compile.json`文件中,以 248 | `[语言id]:[编译参数数组]`的json文件保存,因此需要手动为该文件添加编译参数,使Judger能够将编译参数数组读入并传入该`compile`方法。 249 | 250 | 目前所有语言中仅`Java`语言需要重写该方法,因为其编译需要`java_xms`和`java_xmx`两个参数。 251 | 252 | ```cpp 253 | virtual void buildRuntime(const char* work_dir); 254 | ``` 255 | 256 | 该方法在创建沙箱时调用。 257 | 该方法将该语言需要的运行时复制到`/home/judge/run${runid}/`目录下。 258 | 259 | 运行时,判题机在根据情况将环境`chroot`到沙箱中,因此要将运行时依赖的库文件拷贝到这里。 260 | 261 | `Language`基类提供了一套`shell`的动态库依赖的拷贝方案,而对于C语言等可编译静态库、独立运行的二进制程序,该过程可以忽略。 262 | 因此需要强制重写该方法并留空运行内容。 263 | 264 | 而对于其他的需要运行时的语言,则不仅要调用基类的方法,也要自行编写拷贝语言运行时动态库的过程。 265 | 266 | ```cpp 267 | virtual double buildTimeLimit(double timeLimit, double bonus); 268 | virtual int buildMemoryLimit(int memoryLimit, int bonus); 269 | ``` 270 | 271 | 该方法用于设置用户程序运行时限。传入两个参数分别是 272 | * timeLimit: 题目设置时间限制 273 | * memoryLimit: 题目设置内存限制 274 | * bonus: 预设的语言延长时限 275 | 276 | 返回值是判题机将采纳的时间限制。 277 | 278 | 由于约定俗成的某些规矩,我们可以对某些运行比较慢的运行时适当给出宽限时间空间,因此可通过重写该方法,返回一个合适的时间限制。 279 | 280 | **但是请不要在这里恶作剧,这样对自己没有好处,对做题者也没有好处。** 281 | 282 | 该方法默认提供一个直接返回`timeLimit`(`memoryLimit`)的函数。建议C/C++不要重写该方法,其他语言可适当重写该方法。 283 | 284 | ```cpp 285 | virtual void setExtraPolicy(const char* oj_home, const char* work_dir); 286 | ``` 287 | 288 | 这个方法用于设置语言附加的一些特性或限制。目前仅限用于Java语言设置`policy`。 289 | 若有其他需要额外添加的特性,可以考虑加入。 290 | 291 | ```cpp 292 | virtual void initCallCounter(int* call_counter) = 0; 293 | ``` 294 | 295 | 初始化syscall追踪数组。 296 | 297 | 该方法将会传入一个数组,用于`ptrace`记录调用过的syscall。 298 | 由于不同语言运行过程中可能调用不同的syscall,因此请使用record模式先对一个默认程序运行,根据输出的内容设置syscall。 299 | 300 | 关于这一个部分的设置,请参考`C11`对于syscall的设置方法,这里先不详细展开。 301 | 302 | 该方法强制重写。 303 | 304 | 305 | ```cpp 306 | virtual void buildSeccompSandbox() 307 | ``` 308 | 309 | 设置Sccomp沙箱设置。 310 | 该方法将根据Syscall设置创建一个基于seccomp的沙箱。该功能和ptrace实现的沙箱类似,据说会有一定的性能提升,可参考其他已实现的库的实现方式。 311 | 312 | 该方法强烈建议重写。 313 | 314 | ```cpp 315 | virtual void setCompileExtraConfig(); 316 | ``` 317 | 318 | 该方法在`compile`运行前调用。用于为编译流程添加额外的设置。 319 | 320 | 在实际使用中有Pascal和Freebasic需要重定向输入流,其他语言可以不重写(因为一般用不上)。 321 | 322 | ```cpp 323 | virtual void setCompileMount(const char* work_dir); 324 | ``` 325 | 326 | 该方法将系统的一些文件夹绑定到沙箱中。 327 | 328 | 由于编译过程中某些语言使用到了系统文件夹,为了保证这些语言能够成功编译,`Language`基类默认提供了几个基础文件夹的`mount`过程。 329 | 330 | 对于不需要这些过程的语言,可以重写该方法并留空。 331 | 332 | 若有其他mount设定,也可以重写该方法并自行定义该过程。 333 | 334 | ```cpp 335 | virtual int getCompileResult(int status); 336 | ``` 337 | 338 | 该方法定义了获得编译结果的函数。 339 | 340 | 该方法传入`status`,该值为运行`compile`cli程序获得的返回值。一般来说,该值应该为0,代表编译程序正常退出。 341 | 342 | 考虑到某些语言即使返回0,也并不代表编译通过。因此需要重写该判断流程,通过return返回非0的编译错误结果。 343 | 344 | 默认返回`status`。若非特别需要,可不重写。 345 | 346 | ```cpp 347 | virtual int fixACStatus(int acFlag); 348 | ``` 349 | 该方法用于在某些特殊情况修复AC结果 350 | 351 | 该方法传入AC状态。 352 | 353 | 该方法在运行结束后执行。 354 | 355 | 考虑到像CPython环境(或其他语言)可能将运行错误等其他错误通过`stdout`或者`stderr`输出。 356 | 357 | 因此可以通过该函数插桩,修改判题结果。 358 | 359 | ```cpp 360 | virtual int getMemory(rusage ruse, pid_t pid); 361 | ``` 362 | 363 | 该方法用于返回程序运行内存。 364 | 365 | 传入`wait4`返回的ruse和用户程序pid。 366 | 367 | 根据不同的运行时可能有不同的内存使用检测方法。 368 | 369 | 该方法有缺省返回。若非特别需要,可以不用重写该方法 370 | 371 | ```cpp 372 | virtual void buildChrootSandbox(const char* work_dir); 373 | ``` 374 | 375 | 该方法用于构建chroot环境。 376 | 377 | 一般不需要重写该方法。 378 | 379 | 然而,针对类似Java语言在chroot以后无法运行的情况,因此本方法需要重写为一个空函数。 380 | 381 | ```cpp 382 | virtual std::string getFileSuffix() = 0; 383 | ``` 384 | 385 | 该方法用于返回该语言的通用文件后缀。 386 | 387 | 用户代码将会作为文件写入磁盘,为了编译器正确识别文件,需要强制重写该方法。 388 | 389 | ```cpp 390 | virtual void fixACFlag(int& ACflg); 391 | ``` 392 | 393 | 该方法与`fixACStatus`相同。 394 | 395 | 唯一的区别是该方法在`judge_solution`后运行,`fixACStatus`在`judge_solution`过程中运行。 396 | 397 | ```cpp 398 | virtual bool enableSim(); 399 | ``` 400 | 401 | 该方法返回判题机是否启动判重功能的flag。 402 | 403 | 默认返回`false` 404 | 405 | 由于判题机支持的判重程序支持的语言有限,请查阅`SIM similarity`的支持语言后决定该方法是否重写。 406 | 407 | 建议C/C++/Java语言强制开启。 408 | 409 | ```cpp 410 | virtual void fixFlagWithVMIssue(char *work_dir, int &ACflg, int &topmemory,int mem_lmt); 411 | ``` 412 | 413 | 该方法用于修复由于VM问题导致的错误AC结果。 414 | 415 | 请注意,该方法和VM运行机制强相关,因此请确认该方法在判题机的运行顺序的关系并查阅判题机的Java实现对于该方法的使用后,再决定该方法如何重写。 416 | 417 | 一般不需要重写该方法。 418 | 419 | ```cpp 420 | virtual bool gotErrorWhileRunning(bool error); 421 | ``` 422 | 423 | 该方法用于判断用户程序运行过程检测到`error.out`文件有输出时,是否应该继续。 424 | 425 | 该方法传入的`error`代表`error.out`,即`stderr`是否有输出。 426 | 427 | 考虑到如C/C++语言,若用户程序输出错误,则说明用户程序已经无法继续运行,应该停止。 428 | 429 | 而其他某些语言中,错误输出可能出现在内核或者VM部分,这时也许可以继续运行。 430 | 431 | 因此请仔细考虑有错误输出时是否应该继续。 432 | 433 | 一般情况请重写该方法。 434 | 435 | ```cpp 436 | virtual bool isValidExitCode(int exitcode); 437 | ``` 438 | 439 | 该方法用于判断程序的返回值是否为合法返回值。 440 | 441 | 传入`exitcode`是用户进程结束时返回的code。 442 | 443 | 对于一般情况,返回`0`代表程序正常退出,而其他的数字代表不正常的结果。 444 | 445 | 而对于某些情况,VM可能会fork多个进程,或者程序自动使用多进程的模式执行,而退出的代码不为`0`。 446 | 447 | 因此请根据该语言的退出码决定如何重写该方法。 448 | 449 | 该方法提供缺省的判断条件。 450 | 451 | 一般不需要重写。 452 | 453 | 454 | ## 分布式负载均衡 455 | 由于CRUD API已充分和判题机、WebSocket服务解耦合,可通过设置反向代理实现负载均衡,进一步提升集群化后Express服务的性能。 456 | 457 | ## 微服务尝试 458 | 459 | 众所周知,Node.js针对计算密集型应用表现的性能远不如其他主流语言。为了解决一部分运算密集的任务,或保证应用高可用,微服务是一个值得考虑的方向。 460 | 461 | 目前基于Dubbo RPC的方案正在开发中。已经完成了初始阶段的跨语言调用与注册中心的配置。 462 | 463 | 有关Java微服务的内容,可查看[CUP-Online-Judge-Java-Backend](https://github.com/ryanlee2014/CUP-Online-Judge-Java-Backend) 464 | 465 | ## Dockerize 尝试 466 | 在2018年1月的时候曾经基于Docker+Node.js开发出第一代Docker Judger,这一版Docker Judger成功适配了Kotlin,但由于设计方案的缺陷,判题的速度远不如用原版非Docker的判题机。 467 | 468 | 经研究发现,Dockerize的正确方法并不是直接将用户的代码丢到沙箱中编译运行,而是应该作为一个docker化的服务在后台运行。 469 | 470 | 为了使容器中的判题机能够被外界感知,故设计CUP-Online-Judge-Judger-Daemon-Server与CUP-Online-Judge-Judger进行整合,通过编写Dockerfile将其封装成一个容器。 471 | 472 | 通过docker-compose配置好环境变量后启动。通过暴露端口5110作为Websocket与后端协同的同时,后端将原本的判题模块改为通信模块,将判题部分和WebSocket服务解耦合,提高了原本应用的扩展性。 473 | 474 | 通过docker-compose设置volumes,将判题机的配置文件和数据文件夹映射到判题机容器中,便于独立设置判题机参数。 475 | 476 | docker-compose文件可以在[CUP-Online-Judge-NG-Docker-Judger](https://github.com/ryanlee2014/CUP-Online-Judge-NG-Docker-Judger)找到 477 | 478 | ## Github Actions 持续集成 479 | CUP-Online-Judge-NG-FrontEnd使用了`Github Actions`进行全自动持续集成 480 | 481 | 根据设置,持续集成环境配置支持 482 | 483 | * 本地构建->持续集成发布 484 | * 远程构建->发布 485 | 486 | ### 本地构建->持续集成发布 487 | 0. 将根目录下`NEED_BUILD`文件内容改为`false` 488 | 1. 在本地开发环境运行`npm run modern` 489 | 2. 运行`git add dist`将`dist`目录下所有文件加入本次变更 490 | 3. push到Github环境,启动自动发布。 491 | 492 | ### 远程构建->发布 493 | 0. 将根目录下`NEED_BUILD`文件内容改为`true` 494 | 1. push到Github环境,启动自动发布。 495 | 496 | ### 发布目标 497 | 498 | 持续集成工作流会把`dist`文件夹内容发布到`CUP-Online-Judge-CDN`仓库中,并将根据打包过程中生成的版本发布`releases`供jsDelivr缓存 499 | 500 | 501 | ## CDN 502 | 考虑到本系统所有前端的文件经过打包后大小高达50MB,考虑在非必要时候使用CDN分担服务器的网络压力 503 | 504 | ### jsDelivr CDN 505 | 506 | [https://oj.cupacm.com](https://oj.cupacm.com) 507 | 508 | [https://www.cupacm.com](https://www.cupacm.com) 509 | 510 | 以上节点使用了jsDelivr作为CDN服务提供方,通过持续集成自动部署发布版本到CUP-Online-Judge-CDN仓库,即可简单通过控制index.html更改需要部署的前端版本 511 | 512 | ## 云化 513 | ### Datasource 514 | * MySQL 使用阿里云RDS MySQL服务器 515 | ### Middleware 516 | * 判题机注册节点 **待开发** 517 | ### Core 518 | * 前端 已完成 519 | * 后端 已完成 520 | * 判题机 待开发 521 | ## 线上已知问题 522 | 523 | 迁移至[CUP Online Judge系统问题整理复盘](/discuss/thread/17) 524 | ~~* WebSocket进程有CPU hang100%的现象~~ 525 | ~~解决方案:排查中~~ 526 | 527 | ~~* 提交事件响应抖动造成冗余提交~~ 528 | ~~解决方案: 前端去颤加锁,后端加计时器~~ 529 | 530 | ~~* 发布有较大不确定性,有不可预估的风险~~ 531 | ~~解决方案: 新增部署dev环境->ppe环境->prod环境发布链,新发布统一走SOP变更。~~ 532 | 533 | ## TypeScript改造 534 | > 动态一时爽,重构火葬场 535 | 536 | 为了杜绝以上情况的出现,现计划通过改造当前后端,使用TypeScript语言重构。 537 | 538 | **注: 使用TypeScript请务必抽象化规范化各种DO,减少不必要的`any`声明** 539 | 540 | 如果你也对重构计划感兴趣,欢迎访问 541 | * (后端)[CUP-Online-Judge-Express:typescript](https://github.com/CUP-ACM-Programming-Club/CUP-Online-Judge-Express/tree/typescript) 542 | * (前端)[CUP-Online-Judge-NG-FrontEnd](https://github.com/ryanlee2014/CUP-Online-Judge-NG-FrontEnd) 543 | 544 | 为我们贡献代码 545 | 546 | ## 集群化 547 | 目前后端功能逐渐丰富,接口的数量也逐渐增加,带来的性能开销也越来越大。在高并发及高任务提交的环境下,数据库查询队列负载过重,同时CPU占用率高导致判题效率严重下降,造成较为严重的后果。 548 | 549 | 在当天晚上通过使用Apache Bench对后端接口进行压测过程发现,服务器多个接口均存在不能耐受突发高并发请求的情况。包括但不限于: 550 | * 问题历史页面接口: `problemstatus` (响应时间: 4s) 551 | * 问题集接口: `problemset` (响应时间:1s) 552 | * 竞赛列表接口: `contest` (响应时间: 800ms) 553 | * ... 554 | 555 | 原缓存模型为: 556 | 557 | graph LR; 558 | 路由中间件--SQL查询-->MySQL缓存层; 559 | MySQL缓存层--SQL查询-->MySQL数据库; 560 | MySQL数据库-.结果.->MySQL缓存层; 561 | MySQL缓存层--缓存查询-->缓存; 562 | MySQL缓存层--缓存更新-->缓存; 563 | 缓存-.返回缓存 or null.->MySQL缓存层; 564 | MySQL缓存层-.返回结果.->路由中间件; 565 | 566 | 以上接口在面临高并发请求任务时,由于服务器本身缓存部分为MySQL任务结果,并没有对接口本身做缓存优化,因此存在以下缓存穿透的可能。 567 | 568 | 在高并发环境下,对于SQL查询语句存在重复的情况,而部分慢查询不能及时将数据存到缓存层中,造成缓存穿透,所有请求直接打到数据库,造成后端服务器掉底。 569 | 570 | 针对这种情况,可以考虑使用加锁进行优化。对于每个查询,先获得锁,然后进行查询缓存任务,完成后释放锁,保证后续任务能够直接命中缓存。 571 | 572 | 573 | graph LR; 574 | 路由中间件--SQL查询-->MySQL缓存层; 575 | MySQL缓存层--SQL查询-->MySQL数据库; 576 | MySQL缓存层--加锁/解锁-->MySQL缓存层 577 | MySQL数据库-.结果.->MySQL缓存层; 578 | MySQL缓存层--缓存查询-->缓存; 579 | MySQL缓存层--缓存更新-->缓存; 580 | 缓存-.返回缓存 or null.->MySQL缓存层; 581 | MySQL缓存层-.返回结果.->路由中间件; 582 | 583 | 584 | 以上模型能够解决慢查询击穿数据库的问题。但是如果并发量较大,初始未加缓存的情况下仅能有一个查询在同时进行,性能还没有达到最优。这时候我们可以考虑维护锁管理器,对于每个查询按照指定的规则生成锁的键值,对于每类查询,保证只会命中特定的锁,而不影响其他无关查询的性能。 585 | 586 | 587 | graph LR; 588 | 路由中间件--SQL查询-->MySQL缓存层; 589 | MySQL缓存层--SQL查询-->MySQL数据库; 590 | MySQL缓存层--加锁/解锁-->锁管理器; 591 | MySQL数据库-.结果.->MySQL缓存层; 592 | MySQL缓存层--缓存查询-->缓存; 593 | MySQL缓存层--缓存更新-->缓存; 594 | 缓存-.返回缓存 or null.->MySQL缓存层; 595 | MySQL缓存层-.返回结果.->路由中间件; 596 | > 597 | 598 | 通过以上几步优化,我们成功将原本并发量为1~~也就是完全没有并发的杂鱼~~提升为500rps上下。 599 | 600 | 但是500rps并不能让我们停止对性能提升的追求。通过使用`top`工具观察压测过程中的资源使用情况,我们发现对于单进程,`node`已经跑满单核CPU的所有计算资源。我们知道,`Node.js`使用单线程维护事件队列保证异步高并发的运行环境。而我们显然需要发挥一台计算机多核的威力,不能仅实现一核有难,多核围观这一情况。 601 | 602 | ### Cluster 603 | Node.js在官方库中提供了`Cluster`这一模块,使得Node应用能够以集群的形式运行在服务器上。 604 | 605 | 我们可以通过启动一个`Master`进程,fork与CPU数量相当的`Worker`进程,由`Master`进程获得TCP连接,使用RoundRobin算法平均分配给各个进程,实现负载均衡。 606 | 607 | graph TD; 608 | TCP-->A; 609 | A[Master进程]-->B[Worker 1]; 610 | A[Master进程]-->C[Worker 2]; 611 | A[Master进程]-->D[Worker 3]; 612 | A[Master进程]-->E[Worker 4]; 613 | A[Master进程]-->F[Worker 5]; 614 | A[Master进程]-->G[Worker 6]; 615 | A[Master进程]-->H[Worker 7]; 616 | A[Master进程]-->I[Worker 8]; 617 | 618 | 但是为什么我没有一开始就用这种模型开发呢? 619 | 因为在我们系统中**存在单例模式的组件,组件对变更事件敏感**。 620 | 以下组件是要求严格单例模式且需要即时响应请求的: 621 | 1. 判题模块 622 | 2. ConfigManager 623 | 3. 在线用户管理 624 | 4. WebSocket通信模块 625 | 626 | 其中(1)(3)(4)模块的抽象保证耦合,(2)模块要求即时同步更新。 627 | 628 | 对于前面这种情况,我采用应用切分的方式实现功能的解耦合,将(1)(3)(4)作为一个新服务切分出来,在其他服务掉底的情况仍能保持判题功能。 629 | 630 | graph TD; 631 | A[CUP Online Judge]-->B[CUP Online Judge Express]; 632 | A-->C[CUP Online Judge WebSocket] 633 | B-->HTTP 634 | HTTP-.->B 635 | C-->WebSocket 636 | WebSocket-.->C 637 | HTTP-->D[Client] 638 | WebSocket-->D 639 | 640 | 641 | 642 | 后面这种情况,使用进程间通信保证一致性。 643 | 644 | graph TD; 645 | U[Worker]--通知更新-->A[Master] 646 | A--广播-->B[Worker 2] 647 | A--广播-->C[Worker 3] 648 | A--广播-->D[Worker 4] 649 | A--广播-->E[Worker 5] 650 | 651 | 652 | 然后经过一番优化以后,上述几个瓶颈接口达到了惊人的2300rps,更不用说剩下一些不涉及数据存取的部分,性能提升相当显著。 653 | 654 | ### 热更新 655 | 考虑推线上发布的时候会短暂影响用户的使用体验,而在改造集群以后可以通过负载均衡分配任务给不同的Worker进程。因此可以通过现有的模型改造热更新方案。 656 | 657 | 通过IPC通信通知Master进程重启,Master进程将Worker进程时间Promise化,实现平滑进程过渡,保证用户在无感知情况下实现更新。 658 | 659 | 660 | graph LR; 661 | A[New Worker]--bootstrap-->B[Worker Set] 662 | B--Destroy-->C[Dead Worker] 663 | 664 | 665 | ## 为什么选用Node.js作为后端 666 | 667 | Node.js有以下优点: 668 | * 单线程 669 | * 协程 670 | * 高并发 671 | * I/O密集 672 | * 事件队列 673 | * 事件驱动型后端 674 | * ~~敏捷开发~~ 675 | * ~~前端后端一把梭~~ 676 | * NPM 677 | 678 | 缺点: 679 | * 不适用于计算密集型应用 680 | * 动态语言 681 | > 动态一时爽,重构火葬场 682 | (TypeScript重构希望~~在做了 在做了~~) 683 | * 不够完善的后端生态 684 | 685 | 本文档使用GitHub作为版本控制工具,访问本文档旧版本内容请前往下列链接查看: 686 | CUP-Online-Judge开发文档.md 687 | 688 | 阅读本文档需要了解以下预备知识: 689 | * Node.js(v~~10.0~~12+) 690 | * JavaScript(ES~~5~~~~6~~~~7~~10) 691 | * TypeScript 692 | * PHP(v7.0+)(**Deprecated***) 693 | * Linux 694 | * C++(17+)** 695 | * HTML(HTML5 is optional) 696 | * Vue.js 2+ 697 | * CSS3 698 | * SQL 699 | * Redis 700 | * Apache httpd(**Deprecated**) 701 | * Nginx 702 | 703 | *: PHP即将在`去PHP`计划中被删除。 704 | **: ThreadPool使用了C++17语法特性(Lambda表达式) 705 | ## 程序架构 706 | 707 | 本程序参照了HUSTOJ与SYZOJ的架构,采用了MySQL + Node.js + Linux C/C++ ~~+ PHP~~技术栈进行开发。 708 | 709 | 下列列出的本程序的项目已经开源。 710 | * 基于Node.js Express开发的后端 711 | * 基于简易沙箱的判题机 712 | * 基于Docker的判题机 713 | * ~~基于Semantic UI构建的、使用Vue.js作为框架的前端~~基于Vue-Cli 3.0构建的Vue.js单页应用程序 714 | * 使用Electron Vue开发的跨平台题目打包管理器 715 | * ~~基于Flat UI开发的PHP事务下的主题~~ 716 | 717 | 下列部分~~暂未开源,或者~~由于即将被替代/与其他开源项目的功能重复决定不做开源处理。 718 | * 基于Medoo编写的旧版的PHP事务部分 719 | * 基于Semantic UI与Bootstrap开发的主题 720 | * 管理员管理后台 721 | 722 | 关于本程序采用的开源软件,请访问[开放源代码声明](https://www.cupacm.com/opensource.php)查看。 723 | 724 | ## 写在开发前 725 | 726 | 为了更好的方便内部模块间的协同工作,请认真理解本程序的工作流程,即使它非常的混乱、冗余。 727 | 728 | 在为本程序进行开发时请时刻~~牢记**稳定**与**必要**是该程序功能增改的一大原则~~考虑以领域驱动设计(DDD)的角度对模组抽象化。 729 | 730 | 以下是本程序在用户登录后模块间通信的图示。 731 | ![](https://raw.githubusercontent.com/CUP-ACM-Programming-Club/CUP-Online-Judge-doc/master/pic/program_structure.png) 732 | 733 | 可以分为以下部分。 734 | ### ~~Node.js Runtime~~基于Node.js Express开发的后端 735 | 使用ExpressJS框架,主要功能: 736 | * 使用Socket.IO与用户进行双向通信 737 | * CRUD(**Deprecated:考虑到Node.js对运算密集部分的不友好,这部分将考虑使用微服务进行拆分**) 738 | * 维护判题队列 739 | * 用户权限管理 740 | * 题目文件部署 741 | * WebSocket转发 742 | * 信息广播 743 | 744 | #### 程序文件夹 745 | \- root 746 | \--- static 静态文件文件夹(js库/css库) 747 | \--- route 路由文件夹(API接口) 748 | \--- manager 领域设计贫血模型 749 | \--- orm 数据库对象关系映射模型(Sequelize.js) 750 | \--- test 单元测试 751 | \--- bin 启动文件夹(程序启动文件) 752 | \--- module 模块文件夹(被其他文件调用) 753 | \--- middleware 中间件文件夹(中间件) 754 | \--- views 模版文件夹(pug文件模板) 755 | \--- logs 日志文件夹(日志) 756 | 757 | #### 程序启动(Daemon) 758 | `npm start` 759 | 760 | #### 程序启动(Debug) 761 | `npm test` 762 | 763 | #### 程序重启(Daemon) 764 | `npm restart` 765 | 766 | ### C++ Judger 767 | 768 | 使用EasyWebsocket、HUSTOJ Judger进行重构、开发 769 | 主要功能: 770 | 判题: 771 | 772 | ## 一个提交的执行流程 773 | 774 | ### 客户端 775 | 1. 用户点击`提交`按钮 776 | 2. 代码、token被打包,委托Socket.IO模块推送到Node.js Runtime 777 | 3. Socket.IO模块接受`result`事件,调用挂在`window`上的`problemsubmitter`Vue实例,Vue实例执行相关操作,显示判题情况。 778 | 779 | ### 服务器 780 | ##### Node.js Runtime 781 | 1. (/bin/main.js)收到`submit`事件响应的数据,将数据移交`submitControl`模块,判断提交合法性并解包数据,存储至数据库中 782 | 2. 等待模块返回完成信号,建立socket连接与solution_id的映射,将判题任务移交到判题机 783 | 3. 判题机维护判题队列。 784 | - 若队列空闲则直接唤起空闲判题机移交判题任务 785 | - 若队列非空,则赋予高优先权移交队列,等待判题机结束后询问队列,入队判题 786 | 4. 通过WebSocket模块接收来自判题机的判题信息,通过查阅solution_id对应的socket连接,将数据转发给对应socket 787 | 5. WebSocket模块通过检测完成信号,释放相关资源 788 | 6. 判题机判题结束,检查队列任务,若队列非空,则获取队列头的任务进行判题,若队列为空,则判题机入空闲队列 789 | 790 | #### C++ Judger 791 | 1. 被Node.js Runtime唤醒,建立简易沙箱环境,~~获取MySQL连接~~目前开启`-no-mysql`模式,会变为使用Node.js通过WebSocket传送数据至后端 792 | 2. 初始化系统调用,载入数据文件 793 | 3. 判断提交类型 794 | - 测试运行:不进行答案比对,直接返回评测数据,程序结束 795 | - 正常提交:根据题目内容进行评测,根据数据库取出的数据决定判题方法为`文本对比`或`Special Judge` 796 | - 文本对比:对比标准输出与用户输出的内容,返回`Wrong Answer`/`Presentation Error`/`Accept`信息 797 | - Special Judge: 将输入文件、输出文件、用户输出、用户代码传入程序,根据程序退出时返回的值决定判题结果。理论上可以返回所有的判题结果。 798 | 799 | 4. 数据保存,更新用户数据与题目数据 800 | 5. 退出程序 801 | 802 | ## PHP与Node.js之间如何共享用户数据 803 | 804 | 在每次访问PHP页面时,`/include/db_info.inc.php`文件作为所有PHP文件的头文件被引入到PHP文件中,该文件包括了数据库相关的函数初始化以及缓存数据库的初始化。同时该文件会生成一个和用户`user_id`相关的`token`,并将`token`和`user_id`一同保存在cookie中,在页面进行XHR时cookie会一同发送到Node.js运行时环境 805 | 806 | 若访问的是Vue单页应用(即当前生效的页面),`generate_token`中间件会在`cookie`中添加一个`newToken`及`user_id`字段,并通过Redis为List: ${user_id}newToken添加一个新的token,PHP页面通过`/include/db_info.inc.php`进行登录鉴权。 807 | 808 | ## ConfigManager 809 | ConfigManager是一个系统参数及开关状态的统一管理模块,通过设置存储模块和日志模块进行可持久化及日志回滚操作。 810 | 811 | 模块支持使用**MySQL**及**Redis**进行持久化。 812 | 813 | 该模块目前在后端用于灰度发布及动态配置管理。 814 | 815 | ### Config 816 | Config是一个运行时根据管理员设置动态改变的key-value配置。开发者可以在任何需要动态配置的地方插入`ConfigManager.getConfig(key)`获得Config数据。需要注意的是`key`值是唯一的,`Config`表不能有重复key值存在。 817 | 818 | ### Switch 819 | Switch是一个运行时根据管理员设置的动态改变的开关。开关的值为$[0,100]$,其中开关值代表开关打开的概率。不难发现,0代表开关关闭,100代表开关打开,而中间态代表开关有一定概率是打开状态,而概率为$n$%。在每次调用`ConfigManager.isSwitchedOn(key)`的时候,系统会随机生成一个$[0,100]$的数与设置的数进行大小对比以获得开关状态。 820 | 显然,Switch是一种特殊的Config。 821 | 822 | **ConfigManager在系统中是一个单例模块** 823 | 824 | ## Node.js Router开发 825 | 826 | 要使前端能够调用接口,访问数据,就需要开发对应的Router响应时间。所有的Router文件均放置于文件夹`/router`下。编写Router的方式与Express.js官网的模板相似,只需导出数组 827 | ```javascript 828 | module.exports = ["router_path", middleware, Router] 829 | ``` 830 | 就能够被app.js自动在启动时载入。 831 | 832 | ### Interceptor 833 | 对于部分需要根据后台配置管理的路由,可以在暴露的数组中添加`Interceptor`中间件,从`/module/interceptor/middleware.js`中通过`InterceptorFactory`配置`validator`,使用`getInterceptorInstance`获得拦截器实例放入路由出参middleware中即可。 834 | 835 | `validator`: boolean Function(req, res), 传入`request`,`response`对象,根据返回值决定是否调用`next()`执行下一个中间件。 836 | 837 | 可以配合`ConfigManager`中的`switch`及`config`进行动态更改开闭状态。 838 | 839 | ## Node.js Module开发 840 | 841 | 所有的后台功能若是能够抽象的,应该开发成`Module`以供使用。开发的Module存放在`/module`文件夹下即可 842 | 843 | ## Node.js Socket.IO开发 844 | 845 | 该模块是后台程序诞生的原因~~显示在线人数~~,因此这里的代码是所有代码中历史最久远的部分。目前还没有整理方便置入中间件或绑定响应时间的接口。请等待版本更新。 846 | 847 | ## 前端开发 848 | 849 | 前端是本系统中一个非常重要的部分。考虑到服务器本身对CPU的依赖,于是所有的非保密数据的运算均由客户端(即用户浏览器)运算得出。 850 | 851 | 本平台界面采用Semantic UI进行构建,因此能够支持~~Semantic UI~~Fomantic UI最新文档下的所有模块及特效。 852 | 853 | 大多数的页面由最新版本的Vue.js驱动。由于`去PHP计划`还没有全面完成,部分页面仍将使用旧版的PHP模块驱动。 854 | 目前还使用PHP模块驱动的页面有(此处不考虑CUP Virtual Judge): 855 | - ~~竞赛及作业~~ 856 | - ~~管理员后台~~ 857 | - ~~编译错误页面~~ 858 | - ~~运行错误页面~~ 859 | - 注册页面* 860 | 861 | 删除在`3.0.0-alpha`版本中已重构的部分 862 | *: 正在重构中 863 | 864 | 以上的页面将在不久的将来被Vue页面替代,因此在这些页面上进行功能增删需要三思。 865 | 866 | 当前所有的前端请求均通过**AJAX**与后端保持通信,~~考虑到Semantic UI仍然基于jQuery进行开发,为了减少过多第三方库的引入,建议直接使用jQuery的接口进行XHR请求~~请直接在`CUP-Online-Judge-NG-Frontend`中使用`this.axios`进行AJAX操作。 867 | 868 | 有少数的功能不能够使用AJAX通信,而必须使用Socket.IO接口与后端通信。 869 | - 历史页面最新提交的更新 870 | - 题目提交页面评测题目 871 | - 黑板 872 | - 全局信息推送 873 | - 建立WebSocket时提交的环境数据 874 | - 在线用户情况 875 | - 评测队列 876 | 877 | ## Git 878 | ### 版本 879 | 开发版本格式为:`${major-version}.${maintain-version}.${bugfix-version/refactor-version/hotfix-version}[optional: ${alpha|beta|ppe}]` 880 | 881 | #### 如何针对代码的改变选择适合的版本号变化 882 | 当出现以下变化的,大版本增加: 883 | * 核心代码完全重构 884 | * 环境更改 885 | 886 | 当出现以下变化的,小版本增加: 887 | * 增加前/后端模块链 888 | * 修改前/后端模块链 889 | * 新增/修改Plugin 890 | * 新增非重要功能 891 | * 删除模块/UI更改 892 | 893 | 当出现以下变化时,修复版本增加: 894 | * 修复Bugs 895 | * 更新依赖 896 | * 更正typo 897 | * 新增/修改测试用例 898 | * 无感知的功能重构 899 | 900 | ## 发布 901 | **非线上紧急修复问题,一律禁止在高峰期进行任何发布** 902 | 903 | ### 后端发布 904 | 905 | #### WebSocket发布 906 | 该服务为单例应用,重启过程没有任何安全措施。请尽可能于低峰期发布。 907 | 未来将考虑接入主Cluster节点。 908 | 909 | #### 功能发布 910 | 该服务涉及除判题机及在线用户以外的所有服务,目前可采用安全的在线热部署进行重启。 911 | 912 | 热部署触发链接:[Hot-Reload](/api/admin/system/hot-reload/) 913 | 914 | 管理员通过以下链接Notify主进程进行重启作业。重启失败时,现有Worker将不会被替代。 915 | 916 | 同理,主进程相关逻辑为单例应用,重启过程有一定的安全措施,但也请务必在低峰期更改。 917 | 918 | Master重启流程 919 | 920 | 921 | graph TD 922 | A[Worker] --Notify--> B[Master] 923 | B --Fork--> C[new Worker] 924 | C --> D{isAlive?} 925 | D --N-->E[End] 926 | D --Y--> F[Fork new workers] 927 | F --> G[Update worker queue] 928 | G --> E 929 | 930 | --------------------------------------------------------------------------------