Compare commits

..
75 Commits
Author SHA1 Message Date
hanyixuanten 6fde967ca1 new file: P11681.cpp
Check cpp / md pair / Check md headers (push) Failing after 11m11s
2026-11-03 13:10:10 +00:00
hanyixuanten 9c18114c70 new file: P17827.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-11-03 12:19:41 +00:00
hanyixuanten a9ab81ba6b modified: .gitignore
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-11-03 11:28:28 +00:00
hanyixuanten 8453dca92e renamed: solutions/CSPS2026.md -> article/CSPS2026.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-11-03 11:17:34 +00:00
hanyixuanten 6852bea34c new file: solutions/CSPS2026.md
Check cpp / md pair / Check md headers (push) Successful in 5s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-11-03 11:15:53 +00:00
hanyixuanten c5eebf5252 new file: P10466.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-11-02 07:15:21 +00:00
hanyixuanten 8e1bfd9d9e new file: P17831.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-11-02 06:55:24 +00:00
hanyixuanten 4b50c059cd new file: P15823.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-30 05:58:14 +00:00
hanyixuanten ad876116a5 new file: solutions/CF1380D.md
Check cpp / md pair / Check md headers (push) Successful in 5s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-30 05:35:15 +00:00
hanyixuanten 209d5715a5 new file: CF1380D.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-30 05:14:06 +00:00
hanyixuanten bde443aa2e new file: solutions/P7458.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 4s
2026-10-29 14:58:45 +00:00
hanyixuanten 47e9acfb6a new file: P7458.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-29 14:58:30 +00:00
hanyixuanten ed0d5cf6e7 new file: P1505.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 12:30:27 +00:00
hanyixuanten 2e796fa148 new file: solutions/P4211.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 4s
2026-10-29 11:31:56 +00:00
hanyixuanten 2fd0baf3ab modified: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-29 11:19:22 +00:00
hanyixuanten cfd4039a68 new file: P4211.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-29 11:16:07 +00:00
hanyixuanten 399e5e031a new file: P13019.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 08:48:04 +00:00
hanyixuanten 1dedc9f79d new file: P2982.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 08:26:18 +00:00
hanyixuanten 3b54c62830 new file: P1967.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 07:28:18 +00:00
hanyixuanten 9a99b60552 new file: P4427.cpp
Check cpp / md pair / Check md headers (push) Successful in 6s
2026-10-29 06:46:16 +00:00
hanyixuanten 43d9b98755 fix
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 4s
2026-10-29 06:04:21 +00:00
hanyixuanten dc65d1feb2 fix
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-29 06:02:45 +00:00
hanyixuanten a92069d382 modified: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 3s
Sync solutions to Wiki / sync (push) Successful in 4s
2026-10-29 05:59:53 +00:00
hanyixuanten 54aa7ffc70 new file: solutions/P3384.md
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 05:58:29 +00:00
hanyixuanten 2b981db24d new file: P4281.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 05:44:38 +00:00
hanyixuanten 143cede06f new file: P3398.cpp
Check cpp / md pair / Check md headers (push) Successful in 12s
2026-10-29 05:32:11 +00:00
hanyixuanten 0698b03d94 new file: P3258.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-29 04:47:27 +00:00
hanyixuanten 6dd6508386 Merge pull request 'modified: P3128.cpp' (#1) from P3128/clpf into main
Check cpp / md pair / Check md headers (push) Successful in 4s
Reviewed-on: #1
2026-10-29 03:26:23 +00:00
hanyixuanten 9cb32e82c0 modified: P3128.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-29 03:25:27 +00:00
hanyixuanten 5b2f051c11 new file: P3128.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 03:10:50 +00:00
hanyixuanten 77c65b3f80 modified: P3379.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-29 02:28:40 +00:00
hanyixuanten c0f663fbe5 new file: P3384.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-29 02:05:11 +00:00
hanyixuanten 8c1dfb5d0c new file: P3811.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 13:13:01 +00:00
hanyixuanten c89b0d349f new file: P4779.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-28 13:03:42 +00:00
hanyixuanten 095dcf1eac new file: P3366.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 12:50:48 +00:00
hanyixuanten 1d59a2d3f1 new file: P3379.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 12:40:41 +00:00
hanyixuanten 6e8778f275 new file: P3373.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-28 11:35:01 +00:00
hanyixuanten f94e8e3dcd modified: P3374.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-28 11:08:59 +00:00
hanyixuanten 06fa6530a0 new file: P5788.cpp 2026-10-28 10:59:36 +00:00
hanyixuanten c622586277 new file: P1886.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 10:53:45 +00:00
hanyixuanten 6648c1c279 new file: P3375.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 10:29:58 +00:00
hanyixuanten 68db99a45c new file: P8306.cpp 2026-10-28 10:06:33 +00:00
hanyixuanten 68c1db108d new file: CF1614D2.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 09:45:48 +00:00
hanyixuanten 4bf4f072db new file: CF1614D1.cpp 2026-10-28 09:45:35 +00:00
hanyixuanten e8764c0ce7 new file: solutions/P7707.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-28 08:20:29 +00:00
hanyixuanten 3be07e8bb7 new file: solutions/AT_arc073_b.md
Check cpp / md pair / Check md headers (push) Successful in 3s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-28 08:11:16 +00:00
hanyixuanten 16f444ee63 new file: CF1681E.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 07:03:59 +00:00
hanyixuanten b93c6ac8a4 new file: AT_arc073_b.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-28 05:18:49 +00:00
hanyixuanten 16f5a6cb8a new file: P7707.cpp
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-27 13:10:30 +00:00
hanyixuanten 38f5b1dede modified: solutions/P17437.md
Check cpp / md pair / Check md headers (push) Successful in 5s
Sync solutions to Wiki / sync (push) Successful in 6s
2026-10-27 12:54:55 +00:00
hanyixuanten ec3c08099f modified: solutions/P17437.md
Check cpp / md pair / Check md headers (push) Successful in 6s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-27 12:41:49 +00:00
hanyixuanten 93d4222207 new file: solutions/P17437.md 2026-10-27 11:57:20 +00:00
hanyixuanten a05c373ce8 new file: solutions/P4342.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 6s
2026-10-27 10:37:17 +00:00
hanyixuanten d8022d46bb new file: solutions/P3205.md
Check cpp / md pair / Check md headers (push) Successful in 4s
Sync solutions to Wiki / sync (push) Successful in 5s
2026-10-27 10:31:19 +00:00
hanyixuanten acc3683532 modified: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 5s
Sync solutions to Wiki / sync (push) Successful in 7s
2026-10-27 10:21:44 +00:00
hanyixuanten e128f1fed0 modified: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-27 10:18:55 +00:00
hanyixuanten 75e1d51fb3 modified: use ubuntu-lastest
Check cpp / md pair / Check md headers (push) Successful in 32s
2026-10-27 10:10:28 +00:00
hanyixuanten ff116059e1 modified: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-27 10:08:14 +00:00
hanyixuanten 9da13916a7 new file: .gitea/workflows/sync-wiki.yml
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-27 10:06:39 +00:00
hanyixuanten f727064dc6 new file: solutions/P2607.md
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-27 10:02:40 +00:00
hanyixuanten e73f99c52a new file: P2607.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-27 09:56:13 +00:00
hanyixuanten fc3d90c48a new file: solutions/P2016.md
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-27 09:53:45 +00:00
hanyixuanten 197cb84114 new file: solutions/P1453.md
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-27 09:44:55 +00:00
hanyixuanten 8d7ad8aa70 new file: solutions/P3572.md
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-27 09:44:36 +00:00
hanyixuanten 1b451ba0e4 new file: solutions/CF1187E.md
Check cpp / md pair / Check md headers (push) Successful in 5s
2026-10-27 07:50:14 +00:00
hanyixuanten 199d501394 new file: CF1187E.cpp 2026-10-27 07:32:45 +00:00
hanyixuanten 4069c6a2e6 new file: P4342.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-27 07:09:07 +00:00
hanyixuanten d46ef4b715 new file: P3205.cpp
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-27 06:26:42 +00:00
hanyixuanten b58e73c8c1 new file: P2016.cpp
Check cpp / md pair / Check md headers (push) Successful in 4s
2026-10-27 05:50:22 +00:00
hanyixuanten 5777ed7135 modified: .gitea/workflows/md-check.yml
Check cpp / md pair / Check md headers (push) Successful in 2s
2026-10-27 03:37:29 +00:00
hanyixuanten e3f936f52a renamed: .github/workflows/md-check.yml -> .gitea/workflows/md-check.yml
Check cpp / md pair / Check md headers (push) Successful in 3s
2026-10-27 03:36:25 +00:00
hanyixuanten 6a997e8716 new file: P1453.cpp
Check cpp / md pair / Check md headers (push) Successful in 35s
2026-10-27 02:40:18 +00:00
hanyixuanten a06b6bd8d7 new file: P3572.cpp
Check cpp / md pair / Check md headers (push) Failing after 1s
2026-10-27 01:47:39 +00:00
hanyixuanten 4fb89722a1 new file: CF1312E.cpp
Check cpp / md pair / Check md headers (push) Failing after 0s
2026-10-26 12:32:30 +00:00
hanyixuanten cd0ec40cf0 new file: B3984.cpp 2026-10-26 12:03:25 +00:00
101 changed files with 6117 additions and 29 deletions

No files matched your search

File renamed without changes.
+141
View File
@@ -0,0 +1,141 @@
# .gitea/workflows/sync-wiki.yml
name: Sync solutions to Wiki
on:
push:
branches: [main]
paths:
- "solutions/**"
- ".gitea/workflows/sync-wiki.yml"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: wiki-sync
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Clone wiki repo
env:
WIKI_TOKEN: ${{ secrets.WIKI_TOKEN }}
SERVER_URL: ${{ github.server_url }}
run: |
set -euo pipefail
HOST="${SERVER_URL#https://}"
HOST="${HOST#http://}"
if ! git clone \
"https://oauth2:${WIKI_TOKEN}@${HOST}/${{ github.repository }}.wiki.git" \
wiki; then
echo "::error::Clone wiki failed. 检查: 1) wiki 是否已初始化 2) ENABLE_LOCAL_WIKI 是否开启 3) WIKI_TOKEN 是否有 write:repository 权限"
exit 1
fi
- name: Copy markdown files
shell: bash
run: |
set -euo pipefail
[ -d solutions ] || { echo "solutions/ not found"; exit 1; }
cd solutions
while IFS= read -r -d '' f; do
rel="${f#./}"
base="$(basename "$rel")"
# 不覆盖 wiki 的保留页面
case "$base" in
_Sidebar.md|_Footer.md|_Header.md) continue ;;
esac
# wiki 不支持子目录,把 a/b.md 展平为 a-b.md
target="../wiki/$(printf '%s' "$rel" | sed 's#/#-#g')"
cp "$f" "$target"
done < <(find . -type f -name '*.md' -print0)
cd ..
echo "---- wiki 目录内容 ----"
ls -la wiki
- name: Generate sidebar
shell: bash
run: |
set -euo pipefail
[ -d solutions ] || { echo "solutions/ not found"; exit 1; }
[ -d wiki ] || { echo "wiki/ not found"; exit 1; }
out=wiki/_Sidebar.md
# 收集 solutions 下所有 md(相对路径,去掉扩展名),按路径排序
# 用 -V 做自然排序:P1000 < P2000 < P10000,而不是字典序的 P10000 < P2000
mapfile -t items < <(
find solutions -type f -name '*.md' -printf '%P\n' \
| sed 's/\.md$//' \
| sort -V
)
{
echo "## 题解"
echo ""
echo "[🏠 返回仓库](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})"
echo ""
if [ "${#items[@]}" -eq 0 ]; then
echo "_暂无题解_"
else
current="__NONE__"
for rel in "${items[@]}"; do
base="$(basename "$rel")"
# 跳过 wiki 保留页面
case "$base" in
_Sidebar|_Footer|_Header) continue ;;
esac
dir="$(dirname "$rel")"
[[ "$dir" == "." ]] && dir=""
# 目录切换时输出分组标题
if [[ "$dir" != "$current" ]]; then
echo ""
if [[ -n "$dir" ]]; then
echo "### ${dir}"
else
echo "### 根目录"
fi
echo ""
current="$dir"
fi
# 与 Copy 步骤一致的展平规则:a/b -> a-b
flat="$(printf '%s' "$rel" | tr '/' '-')"
# Gitea wiki 链接语法:[[页面名|显示文本]]
echo "- [[${flat}|${base}]]"
done
fi
} > "$out"
echo "---- _Sidebar.md ----"
cat "$out"
- name: Commit & push
shell: bash
run: |
cd wiki
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.${{ github.server_url }}"
git add -A
if git diff --cached --quiet; then
echo "No changes to sync."
exit 0
fi
git commit -m "Sync solutions from ${GITHUB_REPOSITORY}@${GITHUB_SHA:0:7}"
git push
+4 -2
View File
@@ -6,6 +6,8 @@
!solutions/*.md
!clean.sh
!*.md
!.github
!.github/**
!.gitea
!.gitea/**
!.clang-format
!article
!article/*.md
+49
View File
@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
int n; // 100
int w; // 1e9
pair<int, int> obj[105];
int dp[105][105][305];
int w1;
int main()
{
// freopen("satchel.in", "r", stdin);
// freopen("satchel.out", "w", stdout);
scanf("%d%d", &n, &w);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &obj[i].first, &obj[i].second);
if (i == 1)
w1 = obj[1].first;
obj[i].first -= w1;
}
int maxS = 3 * n;
memset(dp, -1, sizeof(dp));
dp[0][0][0] = 0;
for (int i = 1; i <= n; ++i)
{
int d = obj[i].first;
int v = obj[i].second;
for (int k = 0; k <= i; ++k)
{
for (int s = 0; s <= maxS; ++s)
{
dp[i][k][s] = dp[i - 1][k][s];
if (k >= 1 && s >= d && dp[i - 1][k - 1][s - d] != -1)
dp[i][k][s] = max(dp[i][k][s], dp[i - 1][k - 1][s - d] + v);
}
}
}
int ans = 0;
for (int k = 0; k <= n; ++k)
for (int s = 0; s <= maxS; ++s)
{
if (dp[n][k][s] == -1)
continue;
if (1LL * k * w1 + s <= w)
ans = max(ans, dp[n][k][s]);
}
printf("%d\n", ans);
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
#include<bits/stdc++.h>
using namespace std;
long long x, y;
int main() {
scanf("%lld%lld", &x, &y);
printf("%lld\n", (long long) ceil((y - x) / (long double)2.0));
}
+69
View File
@@ -0,0 +1,69 @@
# B3984 [语言月赛 202406] 编程学习
## 题目描述
小 B 喜欢学习编程,他已经做了 $x$ 道题目。在接下来的一段时间,小 B 每天可以做 $1$ 道或 $2$ 道编程题。小 B 至少还需要多少天,才能做到 $y$ 道题?
## 输入格式
输入一行两个整数 $x,y$。
## 输出格式
输出一行一个整数,表示最小的天数。
## 输入输出样例 #1
### 输入 #1
```
3 6
```
### 输出 #1
```
2
```
## 输入输出样例 #2
### 输入 #2
```
4 10
```
### 输出 #2
```
3
```
## 输入输出样例 #3
### 输入 #3
```
4 135428
```
### 输出 #3
```
67712
```
## 说明/提示
### 数据规模与约定
- 对于 $20\%$ 的测试数据,$1 \leq x < y \leq 10$;
- 对于 $60\%$ 的测试数据,$1 \leq x < y \leq 10^6$;
- 对于 $100\%$ 的测试数据,$1 \leq x < y \leq 10^{18}$。
+45
View File
@@ -0,0 +1,45 @@
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
vector<int> fri[200005];
int siz[200005];
int f[200005];
int ans[200005];
void dfs(int now, int from) {
siz[now] = 1;
f[now] = 0;
for(int i:fri[now]) {
if(i == from) continue;
dfs(i, now);
f[now] += f[i];
siz[now] += siz[i];
}
f[now] += siz[now];
}
void pass(int now, int from) {
for(int i:fri[now]) {
if(i == from) continue;
ans[i] = ans[now] + n - siz[i] * 2;
pass(i, now);
}
}
signed main() {
scanf("%lld", &n);
for(int i = 1, u, v; i < n; ++i) {
scanf("%lld%lld", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs(1, -1);
ans[1] = f[1];
pass(1, -1);
int res = ans[1];
for(int i = 1; i <= n; ++i) res = max(res, ans[i]);
printf("%lld\n", res);
return 0;
}
/*
dp[i] 表示 i 子树中将 i 先染色的最大贡献
g u=g father u+n−siz u×2
*/
+79
View File
@@ -0,0 +1,79 @@
# CF1187E Tree Painting
## 题目描述
给定一棵包含 $n$ 个顶点的树(无向连通无环图)。你要在这棵树上玩一个游戏。
最开始所有顶点都是白色的。在游戏的第一回合,你选择一个顶点并将其染成黑色。之后的每一回合,你都可以选择一个与任意黑色顶点相邻的白色顶点,并将其染成黑色。
每当你选择一个顶点(包括第一回合),你获得的分数等于**染色前**包含该顶点的、仅由白色顶点组成的连通块的大小。游戏在所有顶点都被染成黑色时结束。
来看下面这个例子:
![](https://cdn.luogu.com.cn/upload/vjudge_pic/CF1187E/80d1bdfd9c05e38560143dd180baa827e846ec90.png)
顶点 $1$ 和 $4$ 已经被染成黑色。如果你选择顶点 $2$,你将获得 $4$ 分,因为包含顶点 $2, 3, 5, 6$ 的连通块的大小为 $4$。如果你选择顶点 $9$,你将获得 $3$ 分,因为包含顶点 $7, 8, 9$ 的连通块的大小为 $3$。
你的任务是最大化你获得的分数。
## 输入格式
第一行包含一个整数 $n$,表示树的顶点数($2 \le n \le 2 \times 10^5$)。
接下来的 $n-1$ 行,每行描述树的一条边。第 $i$ 条边由两个整数 $u_i$ 和 $v_i$ 表示,表示该边连接顶点 $u_i$ 和 $v_i$($1 \le u_i, v_i \le n$,$u_i \ne v_i$)。
保证给定的边构成一棵树。
## 输出格式
输出一个整数,表示如果你采取最优策略,最多可以获得多少分。
## 输入输出样例 #1
### 输入 #1
```
9
1 2
2 3
2 5
2 6
1 4
4 9
9 7
9 8
```
### 输出 #1
```
36
```
## 输入输出样例 #2
### 输入 #2
```
5
1 2
1 3
2 4
2 5
```
### 输出 #2
```
14
```
## 说明/提示
第一个示例的树如题目描述中的图片所示。
由 ChatGPT 4.1 翻译
+20
View File
@@ -0,0 +1,20 @@
#include<bits/stdc++.h>
using namespace std;
int n, a[510], dp[510][510], w[510][510];
int main(){
memset(dp, 0x7f, sizeof(dp));
scanf("%d" , &n);
for(int i = 1; i <= n; i++)
scanf("%d" , &a[i]) , dp[i][i] = 1 , w[i][i] = a[i];
for(int len = 2; len <= n; len++)
for(int l = 1; l + len - 1 <= n; l++){
int r = l + len - 1;
for(int mid = l; mid < r; mid++){
dp[l][r] = min(dp[l][r] , dp[l][mid] + dp[mid + 1][r]);
if(dp[l][mid] == dp[mid + 1][r] && dp[l][mid] == 1 && w[l][mid] == w[mid + 1][r])
dp[l][r] = 1 , w[l][r] = w[l][mid] + 1;
}
}
printf("%d" , dp[1][n]);
return 0;
}
+89
View File
@@ -0,0 +1,89 @@
# CF1312E Array Shrinking
## 题目描述
给你一个长度为 $n(1 \le n \le 500)$ 的数组 $a$,每次你可以进行以下两步操作:
1. 找到 $i \in [1, n)$,使得 $a_i = a_{i + 1}$;
2. 将 **它们** 替换为 $a_i + 1$。
每轮操作之后,显然数组的长度会减小 $1$,问剩余数组长度的最小值。
## 输入格式
第一行包含一个整数 $n(1 \le n \le 500)$,表示数组的原长。
第二行包含 $n$ 个整数 $a_1, a_2, \cdots a_n (1 \le a_i \le 1000)$,表示原数组 $a$。
## 输出格式
共一行仅一个整数 $num$,表示进行许多轮操作之后,$a$ 剩余长度的最小值。
### 样例解释
第一组样例中,最优操作之一为 $\text{4 3 2 2 3} \to \text{4 3 3 3} \to \text{4 4 3} \to \text{5 3}$
第二组样例中,最优操作之一为 $\text{3 3 4 4 4 3 3} \to \text{4 4 4 4 3 3} \to \text{4 4 4 4 4} \to \text{5 4 4 4} \to \text{5 5 4} \to \text{6 4}$
对于第三和第四组样例,你并不能进行任何操作。
## 输入输出样例 #1
### 输入 #1
```
5
4 3 2 2 3
```
### 输出 #1
```
2
```
## 输入输出样例 #2
### 输入 #2
```
7
3 3 4 4 4 3 3
```
### 输出 #2
```
2
```
## 输入输出样例 #3
### 输入 #3
```
3
1 3 5
```
### 输出 #3
```
3
```
## 输入输出样例 #4
### 输入 #4
```
1
1000
```
### 输出 #4
```
1
```
+79
View File
@@ -0,0 +1,79 @@
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
long long x, y;
int a[200005];
int b, preb;
int pos[200005];
bool k_is_better;
vector<int> mor;
long long del(int pre, int todo)
{
if (todo <= pre)
return -1;
if (todo == pre + 1)
return 0;
mor.clear();
for (int j = pre + 1; j <= todo - 1; ++j)
if (a[j] > max(a[todo], a[pre]))
mor.push_back(j);
if (!mor.empty() && (todo - pre - 1) < k) // 如果有大数且长度不足以运行一次 k 则不存在
return -1;
if (k_is_better)
{
return ((todo - pre - 1) % k) * y + ((todo - pre - 1) / k) * x; // 先用 y 移除多余的数,最后用多个 k 直接删完
}
else
{
if (!mor.empty())
return (todo - pre - 1 - k) * y + x; // 用一次k清除最大数,其余用y
else
return (todo - pre - 1) * y; // 全部用y
}
return 0;
}
int main()
{
scanf("%d%d%lld%d%lld", &n, &m, &x, &k, &y);
k_is_better = x < (k * y);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
pos[a[i]] = i;
}
long long ans = 0;
for (int i = 1; i <= m; ++i)
{
scanf("%d", &b);
int todo = pos[b], pre = 0;
if (i != 1)
pre = pos[preb];
long long d = del(pre, todo);
if (d == -1ll)
{
printf("-1\n");
return 0;
}
ans += d;
preb = b;
}
long long d = del(pos[b], n + 1);
if (d == -1ll)
{
printf("-1\n");
return 0;
}
printf("%lld\n", ans + d);
return 0;
}
/*
若 k 删除更优,先删到能被 k 整除的长度,则先 k 删除,最后运行多次 k 删除
len % k
若 1 删除更优,先用 k 删除删去大于两边的,最后用 1 删除
7 2
3 3 1
5 7 0 3 1 6 4
5 4
输出 5
*/
+80
View File
@@ -0,0 +1,80 @@
# CF1380D Berserk And Fireball
## 题目描述
有 $n$ 个战士站成一排,第 $i$ 个战士的战力是 $a_i$。所有战士的战力都是两两不同的。
你可以使用两种类型的咒语:
1. 火球术:你可以消耗 $x$ 点法力值来干掉连续的 $k$ 个战士(你必须干掉正好 $k$ 个,而不能干掉 $\le k$ 个)。
2. 狂暴术:你可以消耗 $y$ 点法力值,选择站在一起的两个战士使他们展开决斗,战力较弱的那个战士将会被干掉。
我们来举个例子,假设有 $7$ 个战士,其战力分别为 $[2,3,7,8,11,5,4]$,且此时的$k=3$($k$ 的定义详见火球术)。如果你对战力为 $8$ 与 $11$ 的两个战士施加狂暴术,剩下战士的战力将会变成 $[2,3,7,11,5,4]$(战力为 $8$ 的战士在决斗中战死)。然后如果我们对战力为 $[7,11,5]$ 的战士施加火球术,剩下战士的战力将会变成 $[2,3,4]$。
你想要组建一支军队,因此你想要将所有战士战力的序列从 $a_1,a_2,\cdots,a_n$ 变为 $b_1,b_2,\cdots,b_m$。试计算你所需的最少法力值。
## 输入格式
第一行两个整数 $n,m (1\le n,m\le 2\cdot 10^5)$ ——序列 $a$ 的长度和序列 $b$ 的长度。
第二行三个整数 $x,k,y (1\le x,y\le 10^9,1\le k\le n)$ ——火球术消耗的法力值,火球术的范围和狂暴术消耗的法力值。
第三行 $n$ 个整数 $a_1,a_2,…,a_n (1\le a_i\le n)$。保证 $a_i$ 是两两不同的。
第四行 $m$ 个整数 $b_1,b_2,…,b_m(1\le b_i\le n)$。保证 $b_i$ 是两两不同的。
## 输出格式
一行一个整数,即将序列从 $a_1,a_2,\cdots,a_n$ 变为 $b_1,b_2,\cdots,b_m$所需的最少法力值。如果无法完成变换,则输出$-1$。
## 输入输出样例 #1
### 输入 #1
```
5 2
5 2 3
3 1 4 5 2
3 5
```
### 输出 #1
```
8
```
## 输入输出样例 #2
### 输入 #2
```
4 4
5 1 4
4 3 1 2
2 4 3 1
```
### 输出 #2
```
-1
```
## 输入输出样例 #3
### 输入 #3
```
4 4
2 1 11
1 3 2 4
1 3 2 4
```
### 输出 #3
```
0
```
+45
View File
@@ -0,0 +1,45 @@
#include<bits/stdc++.h>
using namespace std;
int n;
const int maxa = 20000000;
vector<int> prime;
bool vis[20000005];
int cnt[20000005];
long long f[20000005];
int a[100005];
int main() {
for (int i = 2; i <= 20000000; ++i) {
if (!vis[i]) prime.push_back(i);
for (int p : prime) {
if (1ll * i * p > maxa) break;
vis[i * p] = true;
if (i % p == 0) break;
}
}
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j * j <= a[i]; ++j) {
if(a[i] % j == 0) {
cnt[j]++;
if(j * j != a[i]) ++cnt[a[i] / j];
}
}
}
for(int i = maxa; i >= 1; --i) {
f[i] = 1ll * i * cnt[i];
for(int j = 0; j < prime.size() && i * prime[j] <= maxa; ++j) {
f[i] = max(f[i], f[i * prime[j]] + 1ll * i * (cnt[i] - cnt[i * prime[j]]));
}
}
long long ans = 0;
for(int i = 1; i <= maxa; ++i) {
if(cnt[i] == n) {
ans = max(ans, f[i]);
}
}
printf("%lld\n", ans);
return 0;
}
+68
View File
@@ -0,0 +1,68 @@
# CF1614D1 Divan and Kostomuksha (easy version)
## 题目描述
这是该问题的简单版本。唯一的区别在于 $a_i$ 的最大值。
有一次在 Kostomuksha,Divan 发现了一个由正整数组成的数组 $a$。现在他想要重新排列 $a$ 的元素,使下列函数的值最大化:
$$
\sum_{i=1}^n \operatorname{gcd}(a_1,\, a_2,\, \dots,\, a_i),
$$
其中 $\operatorname{gcd}(x_1, x_2, \ldots, x_k)$ 表示整数 $x_1, x_2, \ldots, x_k$ 的[最大公约数](https://en.wikipedia.org/wiki/Greatest_common_divisor),且对于任意整数 $x$,有 $\operatorname{gcd}(x) = x$。
重新排列数组元素意味着可以任意改变数组中元素的顺序,也可以保持初始顺序不变。
当然,Divan 能解决这个问题。不过他觉得这个问题很有趣,所以决定分享给你。
## 输入格式
第一行包含一个整数 $n$($1 \leq n \leq 10^5$),表示数组 $a$ 的大小。
第二行包含 $n$ 个整数 $a_{1},\, a_{2},\, \dots,\, a_{n}$($1 \le a_{i} \le 5 \cdot 10^6$),表示数组 $a$。
## 输出格式
输出通过重新排列数组 $a$ 能得到的函数最大值。
## 输入输出样例 #1
### 输入 #1
```
6
2 3 1 2 6 2
```
### 输出 #1
```
14
```
## 输入输出样例 #2
### 输入 #2
```
10
5 7 10 3 1 10 100 3 42 54
```
### 输出 #2
```
131
```
## 说明/提示
在第一个样例中,将给定数组的元素按如下顺序排列是最优的:$[6,\, 2,\, 2,\, 2,\, 3,\, 1]$:
$$
\operatorname{gcd}(a_1) + \operatorname{gcd}(a_1,\, a_2) + \operatorname{gcd}(a_1,\, a_2,\, a_3) + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4)\\ + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4,\, a_5) + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4,\, a_5,\, a_6)\\= 6 + 2 + 2 + 2 + 1 + 1 = 14。
$$
可以证明无法得到更优的答案。
在第二个样例中,将给定数组的元素按如下顺序排列是最优的:$[100,\, 10,\, 10,\, 5,\, 1,\, 3,\, 3,\, 7,\, 42,\, 54]$。
由 ChatGPT 4.1 翻译
+45
View File
@@ -0,0 +1,45 @@
#include<bits/stdc++.h>
using namespace std;
int n;
const int maxa = 20000000;
vector<int> prime;
bool vis[20000005];
int cnt[20000005];
long long f[20000005];
int a[100005];
int main() {
for (int i = 2; i <= 20000000; ++i) {
if (!vis[i]) prime.push_back(i);
for (int p : prime) {
if (1ll * i * p > maxa) break;
vis[i * p] = true;
if (i % p == 0) break;
}
}
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j * j <= a[i]; ++j) {
if(a[i] % j == 0) {
cnt[j]++;
if(j * j != a[i]) ++cnt[a[i] / j];
}
}
}
for(int i = maxa; i >= 1; --i) {
f[i] = 1ll * i * cnt[i];
for(int j = 0; j < prime.size() && i * prime[j] <= maxa; ++j) {
f[i] = max(f[i], f[i * prime[j]] + 1ll * i * (cnt[i] - cnt[i * prime[j]]));
}
}
long long ans = 0;
for(int i = 1; i <= maxa; ++i) {
if(cnt[i] == n) {
ans = max(ans, f[i]);
}
}
printf("%lld\n", ans);
return 0;
}
+65
View File
@@ -0,0 +1,65 @@
# CF1614D2 Divan and Kostomuksha (hard version)
## 题目描述
这是该问题的困难版本。唯一的区别在于 $a_i$ 的最大值。
在 Kostomuksha,Divan 发现了一个由正整数组成的数组 $a$。现在他想要重新排列 $a$ 的元素,使下列函数的值最大:
$$ \sum_{i=1}^n \operatorname{gcd}(a_1,\, a_2,\, \dots,\, a_i), $$
其中 $\operatorname{gcd}(x_1, x_2, \ldots, x_k)$ 表示整数 $x_1, x_2, \ldots, x_k$ 的[最大公约数](https://en.wikipedia.org/wiki/Greatest_common_divisor),且对于任意整数 $x$,有 $\operatorname{gcd}(x) = x$。
重新排列数组元素意味着可以任意改变数组中元素的顺序,也可以保持初始顺序不变。
当然,Divan 能解决这个问题。但他觉得这个问题很有趣,所以决定把它分享给你。
## 输入格式
第一行包含一个整数 $n$($1 \leq n \leq 10^5$)——数组 $a$ 的大小。
第二行包含 $n$ 个整数 $a_1,\, a_2,\, \dots,\, a_n$($1 \le a_i \le 2 \cdot 10^7$)——数组 $a$。
## 输出格式
输出通过重新排列数组 $a$ 后可以获得的该函数的最大值。
## 输入输出样例 #1
### 输入 #1
```
6
2 3 1 2 6 2
```
### 输出 #1
```
14
```
## 输入输出样例 #2
### 输入 #2
```
10
5 7 10 3 1 10 100 3 42 54
```
### 输出 #2
```
131
```
## 说明/提示
在第一个样例中,最优的排列方式是 $[6,\, 2,\, 2,\, 2,\, 3,\, 1]$:
$\operatorname{gcd}(a_1) + \operatorname{gcd}(a_1,\, a_2) + \operatorname{gcd}(a_1,\, a_2,\, a_3) + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4) + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4,\, a_5) + \operatorname{gcd}(a_1,\, a_2,\, a_3,\, a_4,\, a_5,\, a_6) = 6 + 2 + 2 + 2 + 1 + 1 = 14$。
可以证明无法得到更优的答案。
在第二个样例中,最优的排列方式是 $[100,\, 10,\, 10,\, 5,\, 1,\, 3,\, 3,\, 7,\, 42,\, 54]$。
由 ChatGPT 4.1 翻译
+101
View File
@@ -0,0 +1,101 @@
# CF1681E Labyrinth Adventures
## 题目描述
有一个 $n\times n$ 的方格图,坐标编号类似平面直角坐标系,左下角为 $(1, 1)$。
这个方格图被分成了 $n$ 层,左下角 $(1, 1)$ 为第一层,随后每层都向外拓展一圈,如下图就是 $n=5$ 的时候的情况:
![](https://espresso.codeforces.com/003bbba1ff0347bde56714b878262c5fe414679d.png)
层与层之间有墙隔开,但每层都有两个门,分别分布在该层顶部和右侧,门是双向的。
现在给出这些门的坐标,有 $m$ 次询问,每次给定两个坐标 $(x_1, y_1)$ 和 $(x_2,y_2)$,请你回答两点之间的最短路。
## 输入格式
第一行输入 $n$。
接下来的 $n-1$ 行中,每行有四个整数 $d_{1,x}, d_{1,y}, d_{2,x}, d_{2,y}$,表示第 $i$ 层的两个门的坐标。前两个表示顶部门坐标,后两个表示右侧门坐标。
之后一行输入 $m$。
接下来的 $m$ 行中,每行有四个整数 $x_1,y_1,x_2,y_2$,表示每次询问中起点与终点的坐标。
## 输出格式
对每次询问,输出一行一个整数,表示两点之间的最短路长度。**长度的定义是移动的步数。**
## 输入输出样例 #1
### 输入 #1
```
2
1 1 1 1
10
1 1 1 1
1 1 1 2
1 1 2 1
1 1 2 2
1 2 1 2
1 2 2 1
1 2 2 2
2 1 2 1
2 1 2 2
2 2 2 2
```
### 输出 #1
```
0
1
1
2
0
2
1
0
1
0
```
## 输入输出样例 #2
### 输入 #2
```
4
1 1 1 1
2 1 2 2
3 2 1 3
5
2 4 4 3
4 4 3 3
1 2 3 3
2 2 4 4
1 4 2 3
```
### 输出 #2
```
3
4
3
6
2
```
## 说明/提示
#### 样例解释
下图是样例二的网格图,红色的是门。
![](https://espresso.codeforces.com/522ad0e61c0b740a60c9203c9178279992c8ab2e.png)
#### 数据范围
$1 \le n \le 10^5$;
$2 \le m \le 2 \times 10^5$。
+84
View File
@@ -0,0 +1,84 @@
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int INF = 1e18;
int n;
pair<pii, pii> doors[100005];
int m;
int dp[100005][20][2][2];
signed main()
{
scanf("%lld", &n);
for (int i = 1; i < n; ++i)
{
int xx1, yy1, xx2, yy2;
scanf("%lld%lld%lld%lld", &xx1, &yy1, &xx2, &yy2);
doors[i] = {{xx1, yy1}, {xx2, yy2}};
}
for(int i = 2; i < n; ++i) {
for (int a = 0; a < 2; ++a) {
pii out;
if (a == 0) out = {doors[i - 1].first.first + 1, doors[i - 1].first.second};
else out = {doors[i - 1].second.first, doors[i - 1].second.second + 1};
for (int b = 0; b < 2; ++b) {
pii dest = (b == 0) ? doors[i].first : doors[i].second;
dp[i - 1][0][a][b] = 1 + abs(out.first - dest.first) + abs(out.second - dest.second);
}
}
}
for(int i = 1; i <= 19; ++i) {
for(int j = 1; j + (1 << i) <= n - 1; ++j) {
dp[j][i][0][0] = min(dp[j][i - 1][0][0] + dp[j + (1 << (i - 1))][i - 1][0][0], dp[j][i - 1][0][1] + dp[j + (1 << (i - 1))][i - 1][1][0]);
dp[j][i][0][1] = min(dp[j][i - 1][0][0] + dp[j + (1 << (i - 1))][i - 1][0][1], dp[j][i - 1][0][1] + dp[j + (1 << (i - 1))][i - 1][1][1]);
dp[j][i][1][0] = min(dp[j][i - 1][1][0] + dp[j + (1 << (i - 1))][i - 1][0][0], dp[j][i - 1][1][1] + dp[j + (1 << (i - 1))][i - 1][1][0]);
dp[j][i][1][1] = min(dp[j][i - 1][1][0] + dp[j + (1 << (i - 1))][i - 1][0][1], dp[j][i - 1][1][1] + dp[j + (1 << (i - 1))][i - 1][1][1]);
}
}
scanf("%lld", &m);
while (m--)
{
pii s, e;
scanf("%lld%lld%lld%lld", &s.first, &s.second, &e.first, &e.second);
int sc = max(s.first, s.second);
int ec = max(e.first, e.second);
if(sc > ec) swap(sc, ec), swap(s, e);
else if(sc == ec) {
printf("%lld\n", abs(s.first - e.first) + abs(s.second - e.second));
continue;
}
ec--;
int ds[2], de[2];
ds[0] = abs(doors[sc].first.first - s.first) + abs(doors[sc].first.second - s.second);
ds[1] = abs(doors[sc].second.first - s.first) + abs(doors[sc].second.second - s.second);
for (int b = 0; b < 2; ++b) {
pii out;
if (b == 0) out = {doors[ec].first.first + 1, doors[ec].first.second};
else out = {doors[ec].second.first, doors[ec].second.second + 1};
de[b] = 1 + abs(out.first - e.first) + abs(out.second - e.second);
}
int M[2][2] = {{0, INF}, {INF, 0}};
int now = sc;
int len = ec - sc;
for (int i = 19; i >= 0; --i) {
if (len >= (1 << i)) {
int tmp[2][2];
for (int a = 0; a < 2; ++a)
for (int b = 0; b < 2; ++b) {
tmp[a][b] = INF;
for (int k = 0; k < 2; ++k)
tmp[a][b] = min(tmp[a][b], M[a][k] + dp[now][i][k][b]);
}
memcpy(M, tmp, sizeof(M));
now += (1 << i);
len -= (1 << i);
}
}
int ans = INF;
for (int a = 0; a < 2; ++a)
for (int b = 0; b < 2; ++b)
ans = min(ans, ds[a] + M[a][b] + de[b]);
printf("%lld\n", ans);
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include <bits/stdc++.h>
using namespace std;
set<int> s;
map<int, int> rev;
int n;
int tmp;
int main(void) {
scanf("%d%d", &n, &tmp), s.insert(tmp), rev[tmp] = 1;
for(int i = 2; i <= n; i++) {
scanf("%d", &tmp), s.insert(tmp), rev[tmp] = i;
auto lb = s.lower_bound(tmp), ub = s.upper_bound(tmp);
int minval = INT_MAX, ind = 0;
if(lb != s.begin()) {
--lb;
if(abs(tmp - *lb) < minval)
minval = abs(tmp - *lb), ind = rev[*lb];
}
if(ub != s.end())
if(abs(tmp - *ub) < minval)
minval = abs(tmp - *ub), ind = rev[*ub];
printf("%d %d\n", minval, ind);
}
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
# P10466 邻值查找
## 题目描述
给定一个长度为 $n$ 的序列 $A$,$A$ 中的数各不相同。
对于 $A$ 中的每一个数 $A_i$,求:
$\min_{1 \le j <i}|A_i-A_j|$
以及令上式取到最小值的 $j$(记为 $P_i$)。若最小值点不唯一,则选择使 $A_j$ 较小的那个。
## 输入格式
第一行输入整数 $n$,代表序列长度。
第二行输入 $n$ 个整数 $A_1 \sim A_n$,代表序列的具体数值,数值之间用空格隔开。
## 输出格式
输出共 $n-1$ 行,每行输出两个整数,数值之间用空格隔开。
分别表示当 $i$ 取 $2 \sim n$ 时,对应的 $\min_{1 \le j <i}|A_i-A_j|$ 和 $P_i$ 的值。
## 输入输出样例 #1
### 输入 #1
```
3
1 5 3
```
### 输出 #1
```
4 1
2 1
```
## 说明/提示
对于 $30\%$ 的数据 $n \le 100$。
对于 $70\%$ 的数据 $n \le 10^4$。
对于 $100\%$ 的数据 $n \le 10^5,|A_i| \le 10^9$。
+28
View File
@@ -0,0 +1,28 @@
#include<bits/stdc++.h>
using namespace std;
const int mod = 1145141923;
int n, m;
int a[1000005];
unordered_map<int, bool> ma;
int _0;
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if(a[i]) {
if(!ma[a[i]]) ma[a[i]] = 1;
else {
printf("0\n");
return 0;
}
} else {
_0++;
}
}
long long ans = 1;
for(int i = m - n + _0; i > m - n; --i) {
ans = (ans * i) % mod;
}
printf("%lld\n", ans);
return 0;
}
+73
View File
@@ -0,0 +1,73 @@
# P11681 [Algo Beat Contest 001 C] Creating a Queue
## 题目背景
| Problem | Score | Idea | Std | Data | Check | Solution |
| :---------------------------------: | :---: | :---------------------------------------------------: | :----------------------------------------------------------: | :---------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| $\text{C - Creating a Queue}$ | $400$ | [joe_zxq](https://www.luogu.com.cn/user/623577) | [fanchuanyu](https://www.luogu.com.cn/user/706256) & [joe_zxq](https://www.luogu.com.cn/user/623577) | [joe_zxq](https://www.luogu.com.cn/user/623577) | [remmymilkyway](https://www.luogu.com.cn/user/551981) | [Link](https://www.luogu.com.cn/article/7r5l2cag) by [joe_zxq](https://www.luogu.com.cn/user/623577) |
## 题目描述
给定一个长度为 $N$ 的非负整数序列 $A$。
现在你需要用 $1\sim M$ 之间的正整数替换所有序列 $A$ 中的 $0$,使得对于其中的任何一段长度大于等于 $2$ 的子数组,不能存在唯一众数。
> 子数组:在一个数组中,选择一些连续的元素组成的新数组。
>
> 唯一众数:众数指的是一个数字序列中出现次数最多的元素。如果一个数字序列众数只有一个,我们称这个序列有唯一众数。
求有多少种不同方案,答案对 $1145141923$ 取模。两种方案称为不同,当且仅当替换后的序列至少有一位上的数不同。
## 输入格式
第一行包含两个正整数 $N$ 和 $M$,表示序列的长度和替换数的最大限度。
第二行包含 $N$ 个非负整数,表示序列 $A$ 的元素。
## 输出格式
一行一个非负整数,表示方案的数量,答案对 $1145141923$ 取模。
## 输入输出样例 #1
### 输入 #1
```
2 3
1 0
```
### 输出 #1
```
2
```
## 输入输出样例 #2
### 输入 #2
```
4 1046
114 514 191 981
```
### 输出 #2
```
1
```
## 说明/提示
#### 样例解释 #1
有 $2$ 个满足条件的序列,分别为 $\{1,2\}$ 和 $\{1,3\}$。
#### 样例解释 #2
序列已经完全固定,本身就是一种合法的序列,于是答案为 $1$。
#### 数据范围
对于 $100\%$ 的数据,保证 $1 \le N \le 10^6$,$1 \le M \le 10^9$,$0 \le A_i \le M$。
+61
View File
@@ -0,0 +1,61 @@
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> chi[100005];
int fa[100005][25];
int minc[100005][25];
int q;
int a;
void pre(int now) {
for(int i = 1; i <= 20; ++i) {
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(int i : chi[now]) {
pre(i);
}
for(int i = 1; i <= 20; ++i) {
minc[now][i] = minc[minc[now][i - 1]][i - 1];
}
}
int main() {
scanf("%d%d", &n, &q);
fa[1][0] = 1;
for(int i = 2; i <= n; ++i) {
scanf("%d", &fa[i][0]);
chi[fa[i][0]].push_back(i);
}
for(int i = 1; i <= n; ++i) {
minc[i][0] = INT_MAX;
for(int j : chi[i]) {
minc[i][0] = min(minc[i][0], j);
}
if(minc[i][0] == INT_MAX) minc[i][0] = i;
}
pre(1);
while(q--) {
int s, k;
scanf("%d%d", &s, &k);
for(int i = 1; i <= k; ++i) {
scanf("%d", &a);
if(a > 0) {
for(int j = 20; j >= 0; --j) {
if(a >= (1 << j)) {
a -= (1 << j);
s = fa[s][j];
}
}
} else {
a = -a;
for(int j = 20; j >= 0; --j) {
if(a >= (1 << j)) {
a -= (1 << j);
s = minc[s][j];
}
}
}
}
// printf("111: %d\n", fa[5][0]);
printf("%d\n", s);
}
return 0;
}
+97
View File
@@ -0,0 +1,97 @@
# P13019 [GESP202506 八级] 树上旅行
## 题目背景
对应的选择、判断题:<https://ti.luogu.com.cn/problemset/1188>
## 题目描述
给定一棵有 $ n $ 个结点的 **有根树**,结点依次以 $1,2,\dots,n$ 编号,其中根结点的编号为 $1$。
小 A 计划在这棵有根树上进行 $q$ 次旅行。在第 $i$ 次旅行中,小 A 首先选定结点 $s_i$ 作为起点,并移动若干次。移动分为以下两种:
1. 移动至当前结点的父结点。特殊地,如果当前位于根结点,则不进行移动。
2. 移动至当前结点的所有子结点中**编号最小**的结点。特殊地,如果当前位于叶子结点,则不进行移动。
由于移动次数可能很大,对于第 $i$ 次旅行,旅行中的移动以 $k_i$ 个不为零的整数构成的序列 $a_{i,1}, a_{i,2}, \dots, a_{i,k_i}$ 表示。对 $a_{i,j}$,若 $a_{i,j} > 0$ 则代表进行 $a_{i,j}$ 次第一种移动;若 $a_{i,j} < 0$ 则代表进行 $-a_{i,j}$ 次第二种移动。根据给出的序列从左至右完成所有移动后,小 A 所在的结点即是旅行的**终点**。
给定每次旅行的起点与移动序列,请你求出旅行终点的结点编号。
## 输入格式
第一行,两个正整数 $n, q$,分别表示有根树的结点数量,以及旅行次数。
第二行,$n-1$ 个整数 $p_2, p_3, \dots, p_n$,其中 $p_i$ 表示结点 $i$ 的父结点编号。
接下来 $2q$ 行中的第 $2i-1$ 行($1 \leq i \leq q$)包含两个正整数 $s_i, k_i$,分别表示第 $i$ 次旅行的起点编号,以及移动序列的长度。第 $2i$ 行包含 $k_i$ 个整数 $a_{i,1}, a_{i,2}, \dots, a_{i,k_i}$,表示移动序列。
## 输出格式
输出共 $q$ 行,第 $i$ 行包含一个整数,表示第 $i$ 次旅行终点的结点编号。
## 输入输出样例 #1
### 输入 #1
```
5 4
1 1 2 2
3 3
1 -1 -1
2 5
1 -1 1 -1 1
5 8
1 1 1 -1 -1 -1 -1 -1
5 3
-1 -1 1
```
### 输出 #1
```
4
1
4
2
```
## 输入输出样例 #2
### 输入 #2
```
8 3
5 4 2 1 3 6 6
8 1
8
8 2
8 -8
8 3
8 -8 8
```
### 输出 #2
```
1
7
1
```
## 说明/提示
| 子任务编号 | 测试点占比 | $n$ | $q$ | $\sum k_i$ | 特殊性质 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| $1$ | $20\%$ | $\leq 100$ | $\leq 100$ | $\leq 1000$ | 保证 $a_{i,j}$ 为 $1$ 或 $-1$ |
| $2$ | $20\%$ | $\leq 10^4$ | $\leq 10^4$ | $\leq 4 \times 10^4$ | 仅包含第一种移动 |
| $3$ | $20\%$ | $\leq 10^4$ | $\leq 10^4$ | $\leq 4 \times 10^4$ | 仅包含第二种移动 |
| $4$ | $40\%$ | $\leq 10^5$ | $\leq 2 \times 10^4$ | $\leq 10^5$ | - |
对于所有测试点,保证:
- $1 \leq n \leq 10^5$
- $1 \leq q \leq 2 \times 10^4$
- $1 \leq p_i \leq n$
- $1 \leq s_i \leq n$
- $k_i \geq 1$ 且 $\sum k_i \leq 10^5$
- $1 \leq |a_{i,j}| \leq n$
+49
View File
@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
int p[100005];
vector<int> fri[100005];
long double k;
int bc[100005];
int find(int now) {
if(bc[now] == now) return now;
return bc[now] = find(bc[now]);
}
int dps[100005][2], dpt[100005][2];
void dfs(int now, bool s, int from) {
if(s) dps[now][1] = p[now];
else dpt[now][1] = p[now];
if(s && now == T) dps[now][1] = LLONG_MIN;
else if(!s && now == S) dpt[now][1] = LLONG_MIN;
for(int i:fri[now]) {
if(i == from) continue;
dfs(i, s, now);
if(s) dps[now][1] += dps[i][0], dps[now][0] += max(dps[i][0], dps[i][1]);
else dpt[now][1] += dpt[i][0], dpt[now][0] += max(dpt[i][0], dpt[i][1]);
}
}
signed main() {
int S, T;
scanf("%lld", &n);
for(int i = 0; i < n; ++i) {
scanf("%lld", &p[i]);
bc[i] = i;
}
for(int i = 0, u, v; i < n; ++i) {
scanf("%lld%lld", &u, &v);
if(find(u) == find(v)) {
S = u, T = v;
} else {
fri[u].push_back(v);
fri[v].push_back(u);
bc[find(u)] = find(v);
}
}
scanf("%llf", &k);
dfs(S, 1, -1);
dfs(T, 0, -1);
int ans = max(max(dps[S][0], dps[S][1]), max(dpt[T][1], dpt[T][0]));
printf("%.1llf\n", ans * k);
return 0;
}
+57
View File
@@ -0,0 +1,57 @@
# P1453 城市环路
## 题目描述
一座城市,往往会被人们划分为几个区域,例如住宅区、商业区、工业区等等。
B 市就被分为了以下的两个区域——城市中心和城市郊区。在这两个区域的中间是一条围绕 B 市的环路,环路之内便是 B 市中心。
整个城市可以看做一个 $n$ 个点,$n$ 条边的单圈图(保证图连通),唯一的环便是绕城的环路。保证环上任意两点有且只有 $2$ 条简单路径互通。图中的其它部分皆隶属城市郊区。
现在,有一位名叫 Jim 的同学想在 B 市开店,但是任意一条边的 $2$ 个点不能同时开店,每个点都有一定的人流量,第 $i$ 个点的人流量是 $p_i$,在该点开店的利润就等于 $p_i×k$,其中 $k$ 是一个常数。
Jim 想尽量多的赚取利润,请问他能获取的利润最大为多少?
## 输入格式
第一行一个整数 $n$,代表城市中点的个数。城市中的 $n$ 个点由 $0 \sim n-1$ 编号。
第二行有 $n$ 个整数,第 $(i + 1)$ 个整数表示第 $i$ 个点的人流量 $p_i$。
接下来 $n$ 行,每行有两个整数 $u, v$,代表存在一条连接 $u$ 和 $v$ 的道路。
最后一行有一个实数,代表常数 $k$。
## 输出格式
输出一行一个实数代表答案,结果保留一位小数。
## 输入输出样例 #1
### 输入 #1
```
4
1 2 1 5
0 1
0 2
1 2
1 3
2
```
### 输出 #1
```
12.0
```
## 说明/提示
#### 数据规模与约定
- 对于 $20\%$ 的数据,保证 $n \leq 100$。
- 另有 $20\%$ 的数据,保证环上的点不超过 $2000$ 个。
- 对于 $100\%$ 的数据,保证 $1 \leq n \leq 10^5$,$1 \leq p_i \leq 10^4$,$0 \leq u, v < n$,$0 \leq k \leq 10^4$,$k$ 的小数点后最多有 $6$ 位数字。
+156
View File
@@ -0,0 +1,156 @@
#include<bits/stdc++.h>
using namespace std;
int n;
vector<pair<int, int>> fri[200005];
pair<int, int> edge[200005];
int a[200005];
int fa[200005];
int dfn[200005];
int nowdfn;
int cnt[200005];
int son[200005];
int top[200005];
int dep[200005];
int m;
struct SEG{
int num[3][800005];
bool lt[800005];
void pu(int now, int op) {
if(op == 0) num[op][now] = num[op][now * 2] + num[op][now * 2 + 1];
else if(op == 1) num[op][now] = max(num[op][now * 2], num[op][now * 2 + 1]);
else num[op][now] = min(num[op][now * 2], num[op][now * 2 + 1]);
}
void pu(int now) {pu(now, 0), pu(now, 1), pu(now, 2);}
void dorev(int now) {
lt[now] ^= 1;
num[0][now] = -num[0][now];
int tmp = num[1][now];
num[1][now] = -num[2][now];
num[2][now] = -tmp;
}
void pd(int now, int nl, int nr) {
if(!lt[now]) return;
dorev(now * 2);
dorev(now * 2 + 1);
lt[now] = 0;
}
void edit(int now, int nl, int nr, int l, int nn) {
if(l < nl || l > nr) return;
if(nl == nr) {
num[0][now] = nn, num[1][now] = nn, num[2][now] = nn;
return;
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
edit(now * 2, nl, nmid, l, nn);
edit(now * 2 + 1, nmid + 1, nr, l, nn);
pu(now);
}
void rev(int now, int nl, int nr, int l, int r) {
if(nr < l || nl > r) return;
if(l <= nl && nr <= r) {
dorev(now);
return;
}
pd(now, nl, nr);
int nmid = (nl + nr) >> 1;
rev(now * 2, nl, nmid, l, r);
rev(now * 2 + 1, nmid + 1, nr, l, r);
pu(now);
}
int query(int now, int nl, int nr, int l, int r, int op) {
if(nr < l || nl > r) return (op==2)?INT_MAX:((op==1)?INT_MIN:0);
if(l <= nl && nr <= r) {
return num[op][now];
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
if(op == 0) return query(now * 2, nl, nmid, l, r, op) + query(now * 2 + 1, nmid + 1, nr, l, r, op);
else if(op == 1) return max(query(now * 2, nl, nmid, l, r, op), query(now * 2 + 1, nmid + 1, nr, l, r, op));
else return min(query(now * 2, nl, nmid, l, r, op), query(now * 2 + 1, nmid + 1, nr, l, r, op));
}
}seg;
void dfs1(int now, int from) {
cnt[now] = 1;
for(pair<int, int> i : fri[now]) {
if(i.second == from) continue;
a[i.second] = i.first; // 边权转点权
fa[i.second] = now;
dep[i.second] = dep[now] + 1;
dfs1(i.second, now);
cnt[now] += cnt[i.second];
if(cnt[i.second] > cnt[son[now]]) son[now] = i.second;
}
}
void dfs2(int now, int from) {
dfn[now] = ++nowdfn;
seg.edit(1, 1, n, nowdfn, a[now]);
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(pair<int, int> i : fri[now]) {
if(i.second == son[now] || i.second == fa[now]) continue;
dfs2(i.second, i.second);
}
}
void rev(int u, int v) {
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
seg.rev(1, 1, n, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
if(dfn[v] + 1 <= dfn[u])
seg.rev(1, 1, n, dfn[v] + 1, dfn[u]);
}
int f(int u, int v, int op) {
int ans = 0;
if(op == 1) ans = INT_MIN;
if(op == 2) ans = INT_MAX;
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
if(op == 0) ans += seg.query(1, 1, n, dfn[top[u]], dfn[u], 0);
else if(op == 1) ans = max(ans, seg.query(1, 1, n, dfn[top[u]], dfn[u], 1));
else ans = min(ans, seg.query(1, 1, n, dfn[top[u]], dfn[u], 2));
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
if(dfn[v] + 1 <= dfn[u]) {
if(op == 0) ans += seg.query(1, 1, n, dfn[v] + 1, dfn[u], 0);
else if(op == 1) ans = max(ans, seg.query(1, 1, n, dfn[v] + 1, dfn[u], 1));
else ans = min(ans, seg.query(1, 1, n, dfn[v] + 1, dfn[u], 2));
}
return ans;
}
int main() {
scanf("%d", &n);
for(int i = 1, u, v, w; i < n; ++i) {
scanf("%d%d%d", &u, &v, &w);
u++, v++;
fri[u].push_back({w, v});
fri[v].push_back({w, u});
edge[i] = {u, v};
}
dfs1(1, 0);
dfs2(1, 1);
scanf("%d", &m);
while(m--) {
char op[5];
int u, v;
scanf("%s%d%d", op, &u, &v);
if(op[0] == 'C') {
pair<int, int> ed = edge[u];
if(dep[ed.first] > dep[ed.second]) u = ed.first;
else u = ed.second;
seg.edit(1, 1, n, dfn[u], v);
} else if(op[0] == 'N') {
rev(u + 1, v + 1);
} else if(op[0] == 'S') {
printf("%d\n", f(u + 1, v + 1, 0));
} else if(op[1] == 'A') {
printf("%d\n", f(u + 1, v + 1, 1));
} else {
printf("%d\n", f(u + 1, v + 1, 2));
}
}
return 0;
}
+72
View File
@@ -0,0 +1,72 @@
# P1505 [国家集训队] 旅游
## 题目背景
Ray 乐忠于旅游,这次他来到了 T 城。T 城是一个水上城市,一共有 $n$ 个景点,有些景点之间会用一座桥连接。为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径。换句话说, T 城中只有 $n-1$ 座桥。
Ray 发现,有些桥上可以看到美丽的景色,让人心情愉悦,但有些桥狭窄泥泞,令人烦躁。于是,他给每座桥定义一个愉悦度 $w$,也就是说,Ray 经过这座桥会增加 $w$ 的愉悦度,这或许是正的也可能是负的。有时,Ray 看待同一座桥的心情也会发生改变。
现在,Ray 想让你帮他计算从 $u$ 景点到 $v$ 景点能获得的总愉悦度。有时,他还想知道某段路上最美丽的桥所提供的最大愉悦度,或是某段路上最糟糕的一座桥提供的最低愉悦度。
## 题目描述
给定一棵 $n$ 个节点的树,边带权,点的编号为 $0 \sim n-1$,需要支持五种操作:
- `C i w` 将输入的第 $i$ 条边权值改为 $w$;
- `N u v` 将 $u,v$ 节点之间的边权都变为相反数;
- `SUM u v` 询问 $u,v$ 节点之间边权和;
- `MAX u v` 询问 $u,v$ 节点之间边权最大值;
- `MIN u v` 询问 $u,v$ 节点之间边权最小值。
保证任意时刻所有边的权值都在 $[-1000,1000]$ 内。
## 输入格式
第一行一个正整数 $n$,表示节点个数。
接下来 $n-1$ 行,每行三个整数 $u,v,w$,表示 $u,v$ 之间有一条权值为 $w$ 的边,描述这棵树。
然后一行一个正整数 $m$,表示操作数。
接下来 $m$ 行,每行表示一个操作。
## 输出格式
对于每一个询问操作,输出一行一个整数表示答案。
## 输入输出样例 #1
### 输入 #1
```
3
0 1 1
1 2 2
8
SUM 0 2
MAX 0 2
N 0 1
SUM 0 2
MIN 0 2
C 1 3
SUM 0 2
MAX 0 2
```
### 输出 #1
```
3
2
1
-1
5
3
```
## 说明/提示
【数据范围】
对于 $100\%$ 的数据,$1\le n,m \le 2\times 10^5$。
2020.02.04 修正了一点数据的错误
2020.03.14 加入了一组 hack 数据
2020.11.26 加入了一组 hack 数据 By @_Leaving
+48
View File
@@ -0,0 +1,48 @@
#include<bits/stdc++.h>
using namespace std;
int n, p, q;
int a[2005];
int f[2005][2005];
bool check(int w) {
for(int i = 0; i <= p; ++i) {
for(int j = 1; j <= n; ++j) {
f[i][j] = 0x7f7f7f7f;
}
}
for(int i = 1; i <= n; ++i) {
int s = upper_bound(a + 1, a + n + 1, a[i] - w) - a - 1;
int t = upper_bound(a + 1, a + n + 1, a[i] - 2 * w) - a - 1;
f[0][i] = f[0][t] + 1;
for(int j = 1; j <= p; ++j) {
f[j][i] = min(f[j - 1][s], f[j][t] + 1);
}
}
return f[p][n] <= q;
}
int main() {
scanf("%d%d%d", &n, &p, &q);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
if (p + q >= n)
{ // 若总数足够多,一个盯一个即可,固以下计算 p + q < 2000
printf("1\n");
return 0;
}
sort(a + 1, a + n + 1);
int l = 1, r = 1e9;
while(l < r) {
int mid = l + (r - l) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}
/*
f i, k为仅用 i 个小区间处理完前 k 个点所用最少的大区间个数。
f{i, k} = min(f{i − 1, s}, f{i, t} + 1)
*/
+95
View File
@@ -0,0 +1,95 @@
# P15823 [JOI Open 2013] 观影 / Watching
## 题目描述
澳大利亚有许多有趣的文化,例如各种体育运动和种类繁多的动物。你正试图观看在布里斯班的一条道路上举办的许多活动。
这条道路被划分为 $1\,000\,000\,000$ 个区段。这些区段从西到东编号为 $1, 2, \dots, 1\,000\,000\,000$。你想观看 $N$ 场活动。第 $i$ 场活动将在区段 $A_i$ 上举办。
为了观看这些活动,你准备了 $P$ 台小型摄像机和 $Q$ 台大型摄像机。你可以选择一个正整数 $w$ 作为拍摄参数。然后,一台小型摄像机可以拍摄**最多**连续 $w$ 个区段,一台大型摄像机可以拍摄**最多**连续 $2w$ 个区段。同一个区段可以被多台摄像机拍摄。你想拍摄所有举办活动的区段。由于预计会有很多人前往活动现场,出于安全考虑,你必须固定摄像机的位置,并且在活动期间不允许移动它们。参数 $w$ 越大,拍摄的成本就越高。因此,你希望最小化 $w$ 的值。
### 任务
编写一个程序,根据给定的活动信息和摄像机数量,确定能够拍摄所有活动所在区段所需的最小 $w$ 值。
## 输入格式
从标准输入读取以下数据。
- 输入的第一行包含三个以空格分隔的整数 $N, P, Q$。其中 $N$ 是活动的数量,$P$ 是小型摄像机的数量,$Q$ 是大型摄像机的数量。
- 接下来的 $N$ 行中,第 $i$ 行($1 \le i \le N$)包含一个整数 $A_i$,表示第 $i$ 场活动举办的区段编号。
## 输出格式
向标准输出写入一个整数,表示能够拍摄所有活动所在区段所需的最小 $w$ 值。
## 输入输出样例 #1
### 输入 #1
```
3 1 1
2
11
17
```
### 输出 #1
```
4
```
## 输入输出样例 #2
### 输入 #2
```
13 3 2
33
66
99
10
83
68
19
83
93
53
15
66
75
```
### 输出 #2
```
9
```
## 说明/提示
### 样例解释
在第一个样例中,当你选择 $w = 4$ 时,就可以拍摄所有活动所在的区段。例如,你可以用一台小型摄像机拍摄从区段 1 到 3 的区域,用一台大型摄像机拍摄从区段 11 到 18 的区域。
### 约束条件
所有输入数据满足以下条件。
- $1 \le N \le 2000$。
- $1 \le P \le 100\,000$。
- $1 \le Q \le 100\,000$。
- $1 \le A_i \le 1\,000\,000\,000$($1 \le i \le N$)。
### 子任务
#### 子任务 1 [50 分]
- 满足 $N \le 100$。
#### 子任务 2 [50 分]
- 无额外约束。
翻译由 DeepSeek V3.2 完成
+24
View File
@@ -0,0 +1,24 @@
#include <bits/stdc++.h>
using namespace std;
int side(char c) {
if (c == 'A' || c == 'B' || c == 'C') return 0;
return 1;
}
int pos(char c) {
if (c == 'A') return 0;
if (c == 'B') return 1;
if (c == 'C') return 2;
if (c == 'D') return 0;
return 1; // 'F'
}
int main() {
string c1, s1, c2, s2;
cin >> c1 >> s1 >> c2 >> s2;
bool ans1 = (c1 == c2);
bool ans2 = ans1 && (s1.substr(0, 2) == s2.substr(0, 2));
char a = s1[2], b = s2[2];
bool ans3 = ans2 && (side(a) == side(b));
bool ans4 = ans3 && (abs(pos(a) - pos(b)) == 1);
cout << ans1 << ans2 << ans3 << ans4 << '\n';
return 0;
}
+137
View File
@@ -0,0 +1,137 @@
# P17827 [CSP-J 2026] 旅游 / travel(民间数据)
## 题目描述
小 R 和小 X 准备坐高铁出门旅游。他们买了两张二等座车票,车票上写着各自的车厢号和座位号。两人希望座位相邻,这样旅途中可以坐在一起。
他们乘坐的列车由 $17$ 节车厢组成,车厢编号是 `01` 到 `17`。其中,`01` 和 `16` 号车厢是一等座车厢,其余车厢是二等座车厢。两人买的都是二等座车票,座位都在二等座车厢中。每节二等座车厢共有 $18$ 排,每排有 $5$ 个座位。座位号由两位排号和一位大写字母组成,例如 `03C` 表示第 $3$ 排 C 座。每一排的座位按照从左到右的顺序排列,座位字母依次是 `A`、`B`、`C`、`D`、`F`,其中 C 座与 D 座之间是过道,即每排过道左侧的座位序列是 `A`、`B`、`C`,右侧是 `D`、`F`。以下是一节二等座车厢的座位示意图:
:::align{center}
![](https://cdn.luogu.com.cn/upload/image_hosting/hj5s80cz.webp)
:::
两个座位相邻,当且仅当在同一节车厢的同一排,位于过道的同一侧,且在该侧的座位序列中相邻。
你需要帮助小 R 依次判断:
- 两张车票对应的座位是否在同一节车厢;
- 两张车票对应的座位是否在同一节车厢的同一排;
- 两张车票对应的座位是否在同一节车厢的同一排,且位于过道的同一侧;
- 两张车票对应的座位是否相邻。
## 输入格式
从文件 `travel.in` 中读入数据。
输入的第一行包含四个字符串 $c_1,s_1,c_2,s_2$,分别表示小 R 的车票上的车厢号、座位号,以及小 X 的车票上的车厢号、座位号。
## 输出格式
输出到文件 `travel.out` 中。
输出一行一个长度为 $4$ 的 `01` 字符串,分别表示四个问题的答案,其中 `1` 代表是,`0` 代表否。
## 输入输出样例 #1
### 输入 #1
```
02 01A 02 01B
```
### 输出 #1
```
1111
```
## 输入输出样例 #2
### 输入 #2
```
02 01C 02 01A
```
### 输出 #2
```
1110
```
## 输入输出样例 #3
### 输入 #3
```
02 01C 02 01D
```
### 输出 #3
```
1100
```
## 输入输出样例 #4
### 输入 #4
```
02 02B 02 01A
```
### 输出 #4
```
1000
```
## 输入输出样例 #5
### 输入 #5
```
02 01A 03 01A
```
### 输出 #5
```
0000
```
## 说明/提示
### 样例 1 解释
`02` 号车厢是二等座车厢。`01A` 和 `01B` 在同一节车厢、同一排、过道的同一侧,并且相邻,因此输出 `1111`。
### 样例 2 解释
两个座位在同一节车厢、同一排、过道的同一侧,但 `01C` 与 `01A` 中间隔着 `01B`,不相邻,因此输出 `1110`。
### 数据范围
对于所有测试数据,均有:
- $c_1,c_2$ 是 `01` 到 `17` 之间除 `01` 和 `16` 外的两位车厢号;
- $s_1,s_2$ 是对应车厢中的合法座位号,即前两位是 `01` 到 `18` 之间的排号,末位是 `A`、`B`、`C`、`D`、`F` 中的一个字母;
- 两张车票对应的不是同一个座位,即 $c_1\ne c_2$ 或 $s_1\ne s_2$。
::cute-table{tuack}
| 测试点编号 | 特殊性质 |
| :-: | :-: |
| $1\sim 4$ | A, B, C |
| $5\sim 10$ | A, B |
| $11\sim 14$ | A |
| $15\sim 17$ | D |
| $18\sim 20$ | 无 |
特殊性质 A:两个座位在同一节车厢,即 $c_1=c_2$。
特殊性质 B:两个座位在同一排,即 $s_1$ 的前两位与 $s_2$ 的前两位相同。
特殊性质 C:两个座位在过道的同一侧。
特殊性质 D:小 R 的座位是 D 座,小 X 的座位是 F 座,即 $s_1$ 的末位字符是 `D`,$s_2$ 的末位字符是 `F`。
+44
View File
@@ -0,0 +1,44 @@
#include<bits/stdc++.h>
using namespace std;
int n;
long long m;
long long a[500005];
long long qsum[500005];
bool checkb(int mid, long long now, int tot) {
return mid * now - (qsum[tot] - qsum[tot - mid]) <= m;
}
long long check(long long now, int tot) {
long long nowm = m;
long long ans = 0;
int l = 1, r = tot, mid = (l + r) >> 1;
while(l < r) {
mid = (l + r) >> 1;
if(checkb(mid, now, tot)) {
l = mid + 1;
} else {
r = mid;
}
}
while(!checkb(mid, now, tot) && mid >= 1) mid--;
while(checkb(mid + 1, now, tot) && mid + 1 <= tot) mid++;
return mid;
}
int main() {
scanf("%d%lld", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) qsum[i] = qsum[i - 1] + a[i];
long long ans = 0;
for(int i = 1; i <= n; ++i) {
if(a[i + 1] == a[i]) continue;
ans = max(ans, check(a[i], i));
// printf("%d %lld\n", i, check(a[i], i));
}
printf("%lld\n", ans);
return 0;
}
/*
g++ crystal/crystal.cpp -o crystal/crystal -std=c++14 -O2 -static -Wall && cp crystal/crystal ~ && chmod +x ~/crystal && ~/crystal
O(n log n)
100pts
*/
+95
View File
@@ -0,0 +1,95 @@
# P17831 [CSP-S 2026] 能量水晶 / crystal(民间数据)
## 题目描述
小 R 有 $n$ 颗能量水晶,其中第 $i$ $(1 \le i \le n)$ 颗的能量值为整数 $a_i$。
小 R 想用其中若干颗能量水晶合成一颗大水晶,用于合成的水晶必须具有相同的能量值。小 R 希望参与合成的水晶尽可能多。他可以进行**至多** $m$ 次操作。每次操作可以任选一颗水晶,将其能量值增加 $1$;同一颗水晶可以被重复选择。
你需要帮助小 R 求出,进行至多 $m$ 次操作后,他最多能用多少颗能量水晶合成一颗大水晶。
## 输入格式
从文件 `crystal.in` 中读入数据。
输入的第一行包含一个正整数 $n$ 和一个非负整数 $m$,分别表示能量水晶的数量和最多可以进行的操作次数。
输入的第二行包含 $n$ 个整数 $a_1,a_2,\ldots,a_n$,依次表示每颗能量水晶的能量值。
## 输出格式
输出到文件 `crystal.out` 中。
输出一行一个正整数,表示进行至多 $m$ 次操作后,小 R 最多能用多少颗能量水晶合成一颗大水晶。
## 输入输出样例 #1
### 输入 #1
```
5 5
1 2 4 8 9
```
### 输出 #1
```
3
```
## 输入输出样例 #2
### 输入 #2
```
5 8
-3 -1 2 2 7
```
### 输出 #2
```
4
```
## 说明/提示
### 样例 1 解释
小 R 有 $5$ 颗能量水晶,能量值分别为 $1,2,4,8,9$。他可以对能量值为 $1$ 和 $2$ 的水晶分别操作 $3$ 次和 $2$ 次,使原来能量值为 $1,2,4$ 的三颗水晶的能量值都变成 $4$,共进行 $5$ 次操作。因此小 R 可以用 $3$ 颗能量水晶合成一颗大水晶。
### 样例 3
见题目目录下的 `crystal/3.in` 与 `crystal/3.ans`。
该样例满足测试点 $8,9$ 的约束条件。
### 样例 4
见题目目录下的 `crystal/4.in` 与 `crystal/4.ans`。
该样例满足测试点 $10 \sim 12$ 的约束条件。
### 数据范围
对于所有测试数据,均有:
- $1 \le n \le 5 \times 10^5$,$0 \le m \le 10^{18}$;
- 对于所有 $i$ $(1 \le i \le n)$,均有 $|a_i| \le 10^{12}$。
::cute-table{tuack}
| 测试点编号 | $n \le$ | $m \le$ | $\max_{i=1}^{n} \lvert a_i \rvert \le$ | 特殊性质 |
| :-: | :-: | :-: | :-: | :-: |
| $1,2$ | $5$ | $10$ | $20$ | 无 |
| $3 \sim 5$ | $16$ | $1000$ | $20$ | 无 |
| $6,7$ | $5000$ | $10^6$ | $100$ | 无 |
| $8,9$ | $5000$ | $10^9$ | $10^5$ | 无 |
| $10 \sim 12$ | $5 \times 10^5$ | $10^8$ | $100$ | 无 |
| $13 \sim 15$ | $5 \times 10^5$ | $10^{18}$ | $10^{12}$ | A |
| $16 \sim 18$ | $5 \times 10^5$ | $10^{18}$ | $10^{12}$ | B |
| $19,20$ | $5 \times 10^5$ | $10^{18}$ | $10^{12}$ | 无 |
特殊性质 A:对于所有 $i$ $(1 \le i \le n)$,均有 $a_i=i$。
特殊性质 B:$a_1,a_2,\ldots,a_n$ 构成等差数列。
+40
View File
@@ -0,0 +1,40 @@
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[1000005];
pair<int, int> minq[4000005];
pair<int, int> maxq[4000005];
int lq = 1, rq = 0;
int lqq = 1, rqq = 0;
void push(pair<int, int> now) {
while(lq <= rq && maxq[lq].second <= now.second - k) ++lq;
while(lq <= rq && maxq[rq].first < now.first) --rq;
maxq[++rq] = now;
while(lqq <= rqq && minq[lqq].second <= now.second - k) ++lqq;
while(lqq <= rqq && minq[rqq].first > now.first) --rqq;
minq[++rqq] = now;
}
int maxx[1000005];
int minn[1000005];
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= k; ++i) {
push(make_pair(a[i], i));
}
maxx[1] = maxq[lq].first;
minn[1] = minq[lqq].first;
for(int l = 2; l + k - 1 <= n; ++l) {
int r = l + k - 1;
push(make_pair(a[r], r));
maxx[l] = maxq[lq].first;
minn[l] = minq[lqq].first;
}
for(int i = 1; i + k - 1 <= n; ++i) printf("%d ", minn[i]);
printf("\n");
for(int i = 1; i + k - 1 <= n; ++i) printf("%d ", maxx[i]);
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
# P1886 【模板】单调队列 / 滑动窗口
## 题目描述
有一个长为 $n$ 的序列 $a$,以及一个大小为 $k$ 的窗口。现在这个窗口从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最小值和最大值。
例如,对于序列 $[1,3,-1,-3,5,3,6,7]$ 以及 $k = 3$,有如下过程:
$$\def\arraystretch{1.2}
\begin{array}{|c|c|c|}\hline
\textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline
\verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline
\verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline
\verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline
\verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline
\verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline
\verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline
\end{array}
$$
## 输入格式
输入一共有两行,第一行有两个正整数 $n,k$;\
第二行有 $n$ 个整数,表示序列 $a$。
## 输出格式
输出共两行,第一行为每次窗口滑动的最小值;
第二行为每次窗口滑动的最大值。
## 输入输出样例 #1
### 输入 #1
```
8 3
1 3 -1 -3 5 3 6 7
```
### 输出 #1
```
-1 -3 -3 -3 3 3
3 3 5 5 6 7
```
## 说明/提示
【数据范围】
对于 $50\%$ 的数据,$1 \le n \le 10^5$;
对于 $100\%$ 的数据,$1\le k \le n \le 10^6$,$a_i \in [-2^{31},2^{31})$。
+128
View File
@@ -0,0 +1,128 @@
#include <bits/stdc++.h>
using namespace std;
int n, m, q;
int a[10005];
vector<pair<int, pair<int, int>>> edge;
vector<pair<int, int>> fri[10005];
struct BCJ {
int fa[10005];
int find(int x) {
if (fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
} b;
struct SEG {
int num[10005 * 4];
void init(int now, int nl, int nr) {
if (nl == nr) {
num[now] = a[nl];
return;
}
int mid = (nl + nr) >> 1;
init(now * 2, nl, mid);
init(now * 2 + 1, mid + 1, nr);
num[now] = min(num[now * 2], num[now * 2 + 1]);
}
int query(int now, int nl, int nr, int l, int r) {
if (nr < l || nl > r) return INT_MAX;
if (l <= nl && nr <= r) return num[now];
int mid = (nl + nr) >> 1;
return min(query(now * 2, nl, mid, l, r), query(now * 2 + 1, mid + 1, nr, l, r));
}
} seg;
int fa[10005];
pair<int, int> son[10005];
int cnt[10005];
int top[10005];
int dfn[10005];
int dep[10005];
int nowdfn;
void dfs1(int now, int from) {
cnt[now] = 1;
for (auto i : fri[now]) {
int v = i.second;
int w = i.first;
if (v == from) continue;
fa[v] = now;
dep[v] = dep[now] + 1;
dfs1(v, now);
cnt[now] += cnt[v];
if (cnt[son[now].first] < cnt[v]) {
son[now].first = v;
son[now].second = w;
}
}
}
void dfs2(int now, int from, int w) {
dfn[now] = ++nowdfn;
top[now] = from;
a[dfn[now]] = w;
if (son[now].first) {
dfs2(son[now].first, from, son[now].second);
}
for (auto i : fri[now]) {
int v = i.second;
int ww = i.first;
if (v == fa[now] || v == son[now].first) continue;
dfs2(v, v, ww);
}
}
int pt(int x, int y) {
if (x == y) return INT_MAX;
int ans = INT_MAX;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ans = min(ans, seg.query(1, 1, n, dfn[top[x]], dfn[x]));
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
if (dfn[y] + 1 <= dfn[x]) {
ans = min(ans, seg.query(1, 1, n, dfn[y] + 1, dfn[x]));
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
b.fa[i] = i;
}
for (int i = 1, u, v, w; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
edge.push_back({w, {u, v}});
}
sort(edge.begin(), edge.end(),
[](const pair<int, pair<int, int>> &x,
const pair<int, pair<int, int>> &y) {
return x.first > y.first;
});
for (auto i : edge) {
int w = i.first;
int u = i.second.first;
int v = i.second.second;
if (b.find(u) != b.find(v)) {
fri[u].push_back({w, v});
fri[v].push_back({w, u});
b.fa[b.find(u)] = b.find(v);
}
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) {
dfs1(i, 0);
dfs2(i, i, INT_MAX);
}
}
seg.init(1, 1, n);
scanf("%d", &q);
while (q--) {
int x, y;
scanf("%d%d", &x, &y);
if (b.find(x) != b.find(y)) {
printf("-1\n");
continue;
}
int ans = pt(x, y);
printf("%d\n", ans == INT_MAX ? -1 : ans);
}
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
# P1967 [NOIP 2013 提高组] 货车运输
## 题目背景
NOIP2013 提高组 D1T3
## 题目描述
A 国有 $n$ 座城市,编号从 $1$ 到 $n$,城市之间有 $m$ 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 $q$ 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
## 输入格式
第一行有两个用一个空格隔开的整数 $n,m$,表示 A 国有 $n$ 座城市和 $m$ 条道路。
接下来 $m$ 行每行三个整数 $x, y, z$,每两个整数之间用一个空格隔开,表示从 $x$ 号城市到 $y$ 号城市有一条限重为 $z$ 的道路。
注意:$x \neq y$,两座城市之间可能有多条道路。
接下来一行有一个整数 $q$,表示有 $q$ 辆货车需要运货。
接下来 $q$ 行,每行两个整数 $x,y$,之间用一个空格隔开,表示一辆货车需要从 $x$ 城市运输货物到 $y$ 城市,保证 $x \neq y$。
## 输出格式
共有 $q$ 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 $-1$。
## 输入输出样例 #1
### 输入 #1
```
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
```
### 输出 #1
```
3
-1
3
```
## 说明/提示
对于 $30\%$ 的数据,$1 \le n < 1000$,$1 \le m < 10,000$,$1\le q< 1000$;
对于 $60\%$ 的数据,$1 \le n < 1000$,$1 \le m < 5\times 10^4$,$1 \le q< 1000$;
对于 $100\%$ 的数据,$1 \le n < 10^4$,$1 \le m < 5\times 10^4$,$1 \le q< 3\times 10^4 $,$0 \le z \le 10^5$。
+29
View File
@@ -0,0 +1,29 @@
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> fri[1505];
int dp[1505][2];
void dfs(int now, int from) {
dp[now][1] = 1, dp[now][0] = 0;
for(int i:fri[now]) {
if(i == from) continue;
dfs(i, now);
dp[now][0] += dp[i][1];
dp[now][1] += min(dp[i][0], dp[i][1]);
}
}
int main() {
scanf("%d", &n);
for(int i = 0, k, now; i < n; ++i) {
scanf("%d%d", &now, &k);
while(k--) {
int r;
scanf("%d", &r);
fri[now].push_back(r);
fri[r].push_back(now);
}
}
dfs(0, -1);
printf("%d\n", min(dp[0][0], dp[0][1]));
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
# P2016 [SEERC 2000] 战略游戏
## 题目背景
Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速通关游戏的办法,现在他有个问题。
## 题目描述
他要建立一个古城堡,城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。
注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。
请你编一程序,给定一棵树,帮 Bob 计算出他需要放置最少的士兵。
## 输入格式
第一行一个整数 $n$,表示树中结点的数目。
第二行至第 $n+1$ 行,每行描述每个结点信息,依次为:一个整数 $i$,代表该结点标号,一个自然数 $k$,代表后面有 $k$ 条无向边与结点 $i$ 相连。接下来 $k$ 个整数,分别是每条边的另一个结点标号 $r_1,r_2,\cdots,r_k$,表示 $i$ 与这些点间各有一条无向边相连。
对于一个 $n$ 个结点的树,结点标号在 $0$ 到 $n-1$ 之间,在输入数据中每条边只出现一次。保证输入是一棵树。
## 输出格式
输出文件仅包含一个整数,为所求的最少的士兵数目。
## 输入输出样例 #1
### 输入 #1
```
4
0 1 1
1 2 2 3
2 0
3 0
```
### 输出 #1
```
1
```
## 说明/提示
#### 数据规模与约定
对于全部的测试点,保证 $1 \leq n \leq 1500$。
+49
View File
@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
int p[1000005];
vector<int> fri[1000005];
int bc[1000005];
vector<pair<int, int>> ST;
int find(int now) {
if(bc[now] == now) return now;
return bc[now] = find(bc[now]);
}
int dps[1000005][2], dpt[1000005][2];
void dfs(int now, bool s, int from, int st) {
if(s) dps[now][1] = p[now];
else dpt[now][1] = p[now];
if(s && now == st) dps[now][1] = LLONG_MIN;
else if(!s && now == st) dpt[now][1] = LLONG_MIN;
for(int i:fri[now]) {
if(i == from) continue;
dfs(i, s, now, st);
if(s) dps[now][1] += dps[i][0], dps[now][0] += max(dps[i][0], dps[i][1]);
else dpt[now][1] += dpt[i][0], dpt[now][0] += max(dpt[i][0], dpt[i][1]);
}
}
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; ++i) {
bc[i] = i;
}
for(int i = 1, v; i <= n; ++i) {
scanf("%lld%lld", &p[i], &v);
if(find(i) == find(v)) {
ST.push_back({i, v});
} else {
fri[i].push_back(v);
fri[v].push_back(i);
bc[find(i)] = find(v);
}
}
int ans = 0;
for(auto [s, t]:ST){
dfs(s, 1, -1, s);
dfs(t, 0, -1, t);
ans += max(max(dps[s][0], dps[s][1]), max(dpt[t][1], dpt[t][0]));
}
printf("%lld\n", ans);
return 0;
}
+53
View File
@@ -0,0 +1,53 @@
# P2607 [ZJOI2008] 骑士
## 题目描述
Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照 $1$ 至 $n$ 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
## 输入格式
第一行包含一个整数 $n$,描述骑士团的人数。
接下来 $n$ 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
## 输出格式
应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。
## 输入输出样例 #1
### 输入 #1
```
3
10 2
20 3
30 1
```
### 输出 #1
```
30
```
## 说明/提示
#### 数据规模与约定
对于 $30\%$ 的测试数据,满足 $n \le 10$;
对于 $60\%$ 的测试数据,满足 $n \le 100$;
对于 $80\%$ 的测试数据,满足 $n \le 10 ^4$。
对于 $100\%$ 的测试数据,满足 $1\le n \le 10^6$,每名骑士的战斗力都是不大于 $10^6$ 的正整数。
+64
View File
@@ -0,0 +1,64 @@
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
vector<int> fri[100005];
int p[100005];
int dfn[100005];
int cnt[100005];
int nowdfn;
struct SEG{
int num[400005], lt[400005];
void pd(int now, int nl, int nr) {
int nmid = (nl + nr) >> 1;
num[now * 2] += lt[now] * (nmid - nl + 1);
num[now * 2 + 1] += lt[now] * (nr - nmid);
lt[now * 2] += lt[now];
lt[now * 2 + 1] += lt[now];
lt[now] = 0;
}
void pu(int now) {num[now] = num[now * 2] + num[now * 2 + 1];}
void add(int now, int nl, int nr, int l, int r, int nn) {
if(nl > r || nr < l) return;
if(l <= nl && nr <= r) {
num[now] += nn * (nr - nl + 1);
lt[now] += nn;
return;
}
int nmid = (nl + nr) >> 1;
add(now * 2, nl, nmid, l, r, nn);
add(now * 2 + 1, nmid + 1, nr, l, r, nn);
pu(now);
}
int query(int now, int nl, int nr, int l, int r) {
if(nl > r || nr < l) return 0;
if(l <= nl && nr <= r) return num[now];
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
return query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r);
}
}seg;
void dfs(int now, int from) {
cnt[now] = 1;
dfn[now] = ++nowdfn;
for(int i : fri[now]) {
if(i == from) continue;
dfs(i, now);
cnt[now] += cnt[i];
}
}
signed main() {
scanf("%lld", &n);
for(int i = 1, u, v; i < n; ++i) {
scanf("%lld%lld", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs(1, 0);
for(int i = 1; i <= n; ++i) {
scanf("%lld", &p[i]);
printf("%lld\n", seg.query(1, 1, n, dfn[p[i]], dfn[p[i]]));
seg.add(1, 1, n, dfn[p[i]], dfn[p[i]] + cnt[p[i]] - 1, 1);
}
return 0;
}
+105
View File
@@ -0,0 +1,105 @@
# P2982 [USACO10FEB] Slowing down G
## 题目描述
每天,Farmer John 的 $N$ $(1 \le N \le 100{,}000)$ 头奶牛(方便地编号为 $1..N$)都会从牛棚走向各自的私人牧场。牧场形成一棵树,其中牛棚位于牧场 $1$。恰好有 $N-1$ 条无向路径连接这些牧场;直接相连的牧场之间恰好有一条路径。路径 $i$ 连接牧场 $A_i$ 和 $B_i$ $(1 \le A_i \le N, 1 \le B_i \le N)$。
奶牛 $i$ 拥有一个私人牧场 $P_i$ $(1 \le P_i \le N)$。牛棚的小门一次只能让一头奶牛出去,耐心的奶牛们会等待前一头奶牛到达她的私人牧场。首先,奶牛 $1$ 离开并走向牧场 $P_1$。接着奶牛 $2$ 离开并前往牧场 $P_2$,依此类推。
当奶牛 $i$ 走向 $P_i$ 时,她可能会经过已经有奶牛在进食的牧场。当一头奶牛出现在某个牧场时,奶牛 $i$ 在穿过该牧场时会比平时走得更慢,以避免打扰她的朋友。
```cpp
考虑以下牧场网络,其中括号中的数字表示牧场的主人。
1 (3)
/ \
(1) 4 3 (5)
/ \
(2) 2 5 (4)
首先,奶牛 1 走向她的牧场:
1 (3)
/ \
[1] 4* 3 (5)
/ \
(2) 2 5 (4)
当奶牛 2 走向她的牧场时,她首先进入牛棚的牧场,即牧场 1。
然后她绕过牧场 4 中的奶牛 1,最终到达自己的牧场。
1 (3)
/ \
[1] 4* 3 (5)
/ \
[2] 2* 5 (4)
奶牛 3 根本没走远——她在牛棚的牧场 #1 里闲逛。
1* [3]
/ \
[1] 4* 3 (5)
/ \
[2] 2* 5 (4)
奶牛 4 在前往牧场 5 的路上,必须在牧场 1 和 4 放慢速度:
1* [3]
/ \
[1] 4* 3 (5)
/ \
[2] 2* 5* [4]
奶牛 5 因为在牧场 1 遇到奶牛 3 而放慢速度,然后进入自己的私人牧场:
1* [3]
/ \
[1] 4* 3*[5]
/ \
[2] 2* 5* [4]
```
FJ 想知道每头奶牛需要放慢多少次速度。
## 输入格式
- 第 1 行:一个整数 $N$。
- 第 $2..N$ 行:第 $i+1$ 行包含两个以空格分隔的整数 $A_i$ 和 $B_i$。
- 第 $N+1..N+N$ 行:第 $N+i$ 行包含一个整数 $P_i$。
## 输出格式
- 第 $1..N$ 行:第 $i$ 行包含奶牛 $i$ 需要放慢速度的次数。
## 输入输出样例 #1
### 输入 #1
```
5
1 4
5 4
1 3
2 4
4
2
1
5
3
```
### 输出 #1
```
0
1
0
2
1
```
## 说明/提示
数据范围:$1 \leq A_i, B_i, P_i \leq N \leq 10^5$。
+97
View File
@@ -0,0 +1,97 @@
#include<bits/stdc++.h>
using namespace std;
int n, k;
vector<int> fri[100005];
int cnt[100005];
int fa[100005];
int son[100005];
int dfn[100005];
int top[100005];
int dep[100005];
int nowdfn;
struct SEG{
int num[400005], lt[400005];
void pu(int now) {
num[now] = num[now * 2] + num[now * 2 + 1];
}
void pd(int now, int nl, int nr) {
int nmid = (nl + nr) >> 1;
int l = lt[now];
num[now * 2] += l * (nmid - nl + 1);
num[now * 2 + 1] += l * (nr - nmid);
lt[now * 2] += l;
lt[now * 2 + 1] += l;
lt[now] = 0;
}
void add(int now, int nl, int nr, int l, int r, int nn) {
if(nl > r || nr < l) return;
if(l <= nl && nr <= r) {
num[now] += nn;
lt[now] += nn;
return;
}
int nmid = (nl + nr) >> 1;
add(now * 2, nl, nmid, l, r, nn);
add(now * 2 + 1, nmid + 1, nr, l, r, nn);
pu(now);
}
int query(int now, int nl, int nr, int l, int r) {
if(nl > r || nr < l) return 0;
if(l <= nl && nr <= r) {
return num[now];
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
return query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r);
}
}seg;
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
dfn[now] = ++nowdfn;
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
void add(int a, int b) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
seg.add(1, 1, n, dfn[top[a]], dfn[a], 1);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
seg.add(1, 1, n, dfn[b], dfn[a], 1);
}
int main() {
scanf("%d%d", &n, &k);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(1, -1);
dfs2(1, 1);
while(k--) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
}
int ans = 0;
for(int i = 1; i <= n; ++i) {
ans = max(ans, seg.query(1, 1, n, i, i));
}
printf("%d\n", ans);
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
# P3128 [USACO15DEC] Max Flow P
## 题目描述
Farmer John 在他的谷仓中安装了 $N-1$ 条管道,用于在 $N$ 个牛棚之间运输牛奶($2 \leq N \leq 50,000$),牛棚方便地编号为 $1 \ldots N$。每条管道连接一对牛棚,所有牛棚通过这些管道相互连接。
FJ 正在 $K$ 对牛棚之间泵送牛奶($1 \leq K \leq 100,000$)。对于第 $i$ 对牛棚,你被告知两个牛棚 $s_i$ 和 $t_i$,这是牛奶以单位速率泵送的路径的端点。FJ 担心某些牛棚可能会因为过多的牛奶通过它们而不堪重负,因为一个牛棚可能会作为许多泵送路径的中转站。请帮助他确定通过任何一个牛棚的最大牛奶量。如果牛奶沿着从 $s_i$ 到 $t_i$ 的路径泵送,那么它将被计入端点牛棚 $s_i$ 和 $t_i$,以及它们之间路径上的所有牛棚。
## 输入格式
输入的第一行包含 $N$ 和 $K$。
接下来的 $N-1$ 行每行包含两个整数 $x$ 和 $y$($x \ne y$),描述连接牛棚 $x$ 和 $y$ 的管道。
接下来的 $K$ 行每行包含两个整数 $s$ 和 $t$,描述牛奶泵送路径的端点牛棚。
## 输出格式
输出一个整数,表示通过谷仓中任何一个牛棚的最大牛奶量。
## 输入输出样例 #1
### 输入 #1
```
5 10
3 4
1 5
4 2
5 4
5 4
5 4
3 5
4 3
4 3
1 3
3 5
5 4
1 5
3 4
```
### 输出 #1
```
9
```
## 说明/提示
$2 \le N \le 5 \times 10^4,1 \le K \le 10^5$。
+24
View File
@@ -0,0 +1,24 @@
#include<bits/stdc++.h>
using namespace std;
const int mod = 19650827;
int n;
int a[1005];
int f[1005][1005];
int g[1005][1005];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
f[i][i] = 1;
// g[i][i] = 1;
}
for(int len = 1; len < n; ++len) {
for(int l = 1; l + len <= n; ++l) {
int r = l + len;
f[l][r] = (f[l + 1][r] * (a[l] < a[l + 1]) + g[l + 1][r] * (a[l] < a[r])) % mod;
g[l][r] = (f[l][r - 1] * (a[l] < a[r]) + g[l][r - 1] * (a[r - 1] < a[r])) % mod;
}
}
printf("%d\n", (f[1][n] + g[1][n]) % mod);
return 0;
}
+61
View File
@@ -0,0 +1,61 @@
# P3205 [HNOI2010] 合唱队
## 题目描述
为了在即将到来的晚会上有更好的演出效果,作为 AAA 合唱队负责人的小 A 需要将合唱队的人根据他们的身高排出一个队形。假定合唱队一共 $n$ 个人,第 $i$ 个人的身高为 $h_i$ 米($1000 \le h_i \le 2000$),并已知任何两个人的身高都不同。假定最终排出的队形是 $n$ 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终排出的队形中:
- 第一个人直接插入空的当前队形中。
- 对从第二个人开始的每个人,如果他比前面那个人高($h$ 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮($h$ 较小),那么将他插入当前队形的最左边。
当 $n$ 个人全部插入当前队形后便获得最终排出的队形。
例如,有 $6$ 个人站成一个初始队形,身高依次为 $1850, 1900, 1700, 1650, 1800, 1750$,
那么小 A 会按以下步骤获得最终排出的队形:
- $1850$。
- $1850, 1900$,因为 $1900 > 1850$。
- $1700, 1850, 1900$,因为 $1700 < 1900$。
- $1650, 1700, 1850, 1900$,因为 $1650 < 1700$。
- $1650, 1700, 1850, 1900, 1800$,因为 $1800 > 1650$。
- $1750, 1650, 1700, 1850, 1900, 1800$,因为 $1750 < 1800$。
因此,最终排出的队形是 $1750, 1650, 1700, 1850, 1900, 1800$。
小 A 心中有一个理想队形,他想知道多少种初始队形可以获得理想的队形。
请求出答案对 $19650827$ 取模的值。
## 输入格式
第一行一个整数 $n$。
第二行 $n$ 个整数,表示小 A 心中的理想队形。
## 输出格式
输出一行一个整数,表示答案 $\bmod 19650827$ 的值。
## 输入输出样例 #1
### 输入 #1
```
4
1701 1702 1703 1704
```
### 输出 #1
```
8
```
## 说明/提示
对于 $30\%$ 的数据,$n \le 100$。
对于 $100\%$ 的数据,$n \le 1000$,$1000 \le h_i \le 2000$。
+99
View File
@@ -0,0 +1,99 @@
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[300005];
int fa[300005];
int dfn[300005];
int son[300005];
int dep[300005];
int top[300005];
int cnt[300005];
vector<int> fri[300005];
int dfnnow;
struct SEG{
int num[1200005], lt[1200005];
void pu(int now) {
num[now] = num[now * 2] + num[now * 2 + 1];
}
void pd(int now, int nl, int nr) {
int nmid = (nl + nr) >> 1, l = lt[now];
num[now * 2] += l * (nmid - nl + 1);
num[now * 2 + 1] += l * (nr - nmid);
lt[now * 2] += l;
lt[now * 2 + 1] += l;
lt[now] = 0;
}
void add(int now, int nl, int nr, int l, int r, int nn) {
if(nr < l || nl > r) return;
if(l <= nl && nr <= r) {
num[now] += (nr - nl + 1) * nn;
lt[now] += nn;
return;
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
add(now * 2, nl, nmid, l, r, nn);
add(now * 2 + 1, nmid + 1, nr, l, r, nn);
pu(now);
}
int query(int now, int nl, int nr, int l, int r) {
if(nr < l || nl > r) return 0;
if(l <= nl && nr <= r) {
return num[now];
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
return query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r);
}
}seg;
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
top[now] = from;
dfn[now] = ++dfnnow;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
void edit(int a, int b, int nn = 1) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
seg.add(1, 1, n, dfn[top[a]], dfn[a], nn);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
seg.add(1, 1, n, dfn[b], dfn[a], nn);
}
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
}
for(int i = 1, u, v; i < n; ++i) {
scanf("%lld%lld", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(1, -1);
dfs2(1, 1);
for(int i = 1; i < n; ++i) {
edit(a[i], a[i + 1]);
edit(a[i + 1], a[i + 1], -1);
}
for(int i = 1; i <= n; ++i) {
printf("%lld\n", seg.query(1, 1, n, dfn[i], dfn[i]));
}
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
# P3258 [JLOI2014] 松鼠的新家
## 题目描述
松鼠的新家是一棵树,前几天刚刚装修了新家,新家有 $n$ 个房间,并且有 $n-1$ 根树枝连接,每个房间都可以相互到达,且两个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。
松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去 $a_1$,再去 $a_2$,……,最后到 $a_n$,去参观新家。可是这样会导致重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。
维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。
因为松鼠参观指南上的最后一个房间 $a_n$ 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。
## 输入格式
第一行一个正整数 $n$,表示房间个数。第二行 $n$ 个正整数,依次描述 $a_1, a_2,\cdots,a_n$。
接下来 $n-1$ 行,每行两个正整数 $x,y$,表示标号 $x$ 和 $y$ 的两个房间之间有树枝相连。
## 输出格式
一共 $n$ 行,第 $i$ 行输出标号为 $i$ 的房间至少需要放多少个糖果,才能让维尼有糖果吃。
## 输入输出样例 #1
### 输入 #1
```
5
1 4 5 3 2
1 2
2 4
2 3
4 5
```
### 输出 #1
```
1
2
1
2
1
```
## 说明/提示
对于全部的数据,$2 \le n \le 3 \times 10^5$,$1 \le a_i \le n$。
+34
View File
@@ -0,0 +1,34 @@
#include<bits/stdc++.h>
using namespace std;
int n, m;
vector<pair<int, pair<int, int>>> fri;
int fa[5005];
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) fa[i] = i;
for(int i = 1, u, v, w; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
fri.push_back({w, {u, v}});
}
sort(fri.begin(), fri.end());
long long ans = 0;
for(auto i : fri) {
if(find(i.second.first) != find(i.second.second)) {
ans += i.first;
fa[find(i.second.first)] = find(i.second.second);
}
}
int to = find(1);
for(int i = 2; i <= n; ++i) {
if(find(i) != to) {
printf("orz\n");
return 0;
}
}
printf("%lld\n", ans);
return 0;
}
+53
View File
@@ -0,0 +1,53 @@
# P3366 【模板】最小生成树
## 题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 `orz`。
## 输入格式
第一行包含两个整数 $N,M$,表示该图共有 $N$ 个结点和 $M$ 条无向边。
接下来 $M$ 行每行包含三个整数 $X_i,Y_i,Z_i$,表示有一条长度为 $Z_i$ 的无向边连接结点 $X_i,Y_i$。
## 输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 `orz`。
## 输入输出样例 #1
### 输入 #1
```
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
```
### 输出 #1
```
7
```
## 说明/提示
数据规模:
对于 $20\%$ 的数据,$N\le 5$,$M\le 20$。
对于 $40\%$ 的数据,$N\le 50$,$M\le 2500$。
对于 $70\%$ 的数据,$N\le 500$,$M\le 10^4$。
对于 $100\%$ 的数据:$1\le N\le 5000$,$1\le M\le 2\times 10^5$,$1\le Z_i \le 10^4$,$1\le X_i,Y_i\le N$。
样例解释:
![](https://cdn.luogu.com.cn/upload/pic/2259.png)
所以最小生成树的总边权为 $2+2+3=7$。
+94
View File
@@ -0,0 +1,94 @@
#include<bits/stdc++.h>
using namespace std;
struct node {
long long sum;
long long lt1;
long long lt2 = 1;
} seg[400005];
int n, q;
long long m;
int a[100005];
void pu(int now) {
seg[now].sum = (seg[now * 2].sum + seg[now * 2 + 1].sum) % m;
}
void apply(int now, int len, long long mul, long long add) {
seg[now].sum = (seg[now].sum * mul % m + add * len % m) % m;
seg[now].lt2 = seg[now].lt2 * mul % m;
seg[now].lt1 = (seg[now].lt1 * mul % m + add) % m;
}
void pd(int now, int nl, int nr) {
if (seg[now].lt1 == 0 && seg[now].lt2 == 1) return;
int mid = (nl + nr) >> 1;
apply(now * 2, mid - nl + 1, seg[now].lt2, seg[now].lt1);
apply(now * 2 + 1, nr - mid, seg[now].lt2, seg[now].lt1);
seg[now].lt1 = 0;
seg[now].lt2 = 1;
}
void init(int now, int nl, int nr) {
if (nl == nr) {
seg[now].sum = a[nl] % m;
return;
}
int mid = (nl + nr) >> 1;
init(now * 2, nl, mid);
init(now * 2 + 1, mid + 1, nr);
pu(now);
}
void add(int now, int nl, int nr, int l, int r, long long nn) {
if (nl > r || nr < l) return;
if (l <= nl && nr <= r) {
apply(now, nr - nl + 1, 1, nn % m);
return;
}
int mid = (nl + nr) >> 1;
pd(now, nl, nr);
add(now * 2, nl, mid, l, r, nn);
add(now * 2 + 1, mid + 1, nr, l, r, nn);
pu(now);
}
void mul(int now, int nl, int nr, int l, int r, long long nn) {
if (nl > r || nr < l) return;
if (l <= nl && nr <= r) {
apply(now, nr - nl + 1, nn % m, 0);
return;
}
int mid = (nl + nr) >> 1;
pd(now, nl, nr);
mul(now * 2, nl, mid, l, r, nn);
mul(now * 2 + 1, mid + 1, nr, l, r, nn);
pu(now);
}
long long query(int now, int nl, int nr, int l, int r) {
if (nl > r || nr < l) return 0;
if (l <= nl && nr <= r) {
return seg[now].sum % m;
}
int mid = (nl + nr) >> 1;
pd(now, nl, nr);
return (query(now * 2, nl, mid, l, r) + query(now * 2 + 1, mid + 1, nr, l, r)) % m;
}
int main() {
scanf("%d%d%lld", &n, &q, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
init(1, 1, n);
while (q--) {
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if (op == 1) {
long long k;
scanf("%lld", &k);
mul(1, 1, n, x, y, k);
} else if (op == 2) {
long long k;
scanf("%lld", &k);
add(1, 1, n, x, y, k);
} else {
printf("%lld\n", query(1, 1, n, x, y));
}
}
return 0;
}
+66
View File
@@ -0,0 +1,66 @@
# P3373 【模板】线段树 2
## 题目描述
如题,已知一个数列 $a$,你需要进行下面三种操作:
- 将某区间每一个数乘上 $x$;
- 将某区间每一个数加上 $x$;
- 求出某区间每一个数的和。
## 输入格式
第一行包含三个整数 $n,q,m$,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 $n$ 个用空格分隔的整数,其中第 $i$ 个数字表示数列第 $i$ 项的初始值 $a_i$。
接下来 $q$ 行每行包含若干个整数,表示一个操作,具体如下:
操作 $1$: 格式:`1 x y k` 含义:将区间 $[x,y]$ 内每个数乘上 $k$。
操作 $2$: 格式:`2 x y k` 含义:将区间 $[x,y]$ 内每个数加上 $k$。
操作 $3$: 格式:`3 x y` 含义:输出区间 $[x,y]$ 内每个数的和对 $m$ 取模所得的结果。
## 输出格式
输出包含若干行整数,即为所有操作 $3$ 的结果。
## 输入输出样例 #1
### 输入 #1
```
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
```
### 输出 #1
```
17
2
```
## 说明/提示
【数据范围】
对于 $30\%$ 的数据:$n \le 8$,$q \le 10$。
对于 $70\%$ 的数据:$n \le 10^3 $,$q \le 10^4$。
对于 $100\%$ 的数据:$1 \le n \le 10^5$,$1 \le q \le 10^5,1\le a_i,k\le 10^4$。
除样例外,$m = 571373$。
(数据已经过加强 ^\_^)
样例说明:
![](https://cdn.luogu.com.cn/upload/pic/2255.png)
故输出应为 $17$、$2$($40 \bmod 38 = 2$)。
+21 -27
View File
@@ -1,34 +1,28 @@
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long tree[500005];
long long a[500005];
int lowbit(int n){
return n&(-n);
inline int lowbit(int x) {return x & (-x);};
int n, m;
int a[500005];
int num[500005];
void add(int now, int nn) {
for(; now <= n; now += lowbit(now)) num[now] += nn;
}
void modify(int x,long long k)
{
for(;x<=n;x+=lowbit(x))
tree[x]+=k;
int query(int now) {
int res = 0;
for(; now; now -= lowbit(now)) res += num[now];
return res;
}
long long query(int x){
int ans;
for(;x;x-=lowbit(x))
ans=ans+tree[x];
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
modify(i,a[i]);
}
while(m--){
int op,x;
long long y;
scanf("%d%d%lld",&op,&x,&y);
if(op==1) modify(x,y);
else printf("%lld\n",query(y)-query(x-1));
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]), add(i, a[i]);
while(m--) {
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if(op == 1) {
add(x, y);
} else{
printf("%d\n", query(y) - query(x - 1));
}
}
return 0;
}
+63
View File
@@ -0,0 +1,63 @@
# P3374 【模板】树状数组 1
## 题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某一个数加上 $x$;
- 求出某区间每一个数的和。
## 输入格式
第一行包含两个正整数 $n,m$,分别表示该数列数字的个数和操作的总个数。
第二行包含 $n$ 个用空格分隔的整数,其中第 $i$ 个数字表示数列第 $i$ 项的初始值。
接下来 $m$ 行每行包含 $3$ 个整数,表示一个操作,具体如下:
- `1 x k` 含义:将第 $x$ 个数加上 $k$;
- `2 x y` 含义:输出区间 $[x,y]$ 内每个数的和。
## 输出格式
输出包含若干行整数,即为所有操作 $2$ 的结果。
## 输入输出样例 #1
### 输入 #1
```
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
```
### 输出 #1
```
14
16
```
## 说明/提示
【数据范围】
对于 $30\%$ 的数据,$1 \le n \le 8$,$1\le m \le 10$;
对于 $70\%$ 的数据,$1\le n,m \le 10^4$;
对于 $100\%$ 的数据,$1\le n,m \le 5\times 10^5$,$1\le x\le y\le n$,$-2^{31}\le k<2^{31}$。
数据保证对于任意时刻,$a$ 的任意子区间(包括长度为 $1$ 和 $n$ 的子区间)和均在 $[-2^{31}, 2^{31})$ 范围内。
样例说明:
![](https://cdn.luogu.com.cn/upload/pic/2256.png)
故输出结果 $14$ 和 $16$。
+34
View File
@@ -0,0 +1,34 @@
#include<bits/stdc++.h>
using namespace std;
char s1[1000006], s2[1000006];
int n1, n2;
int nxt[1000006];
void gnext() {
for(int i = 2; i <= n2; ++i) {
int j = nxt[i - 1];
while(j > 0 && s2[j + 1] != s2[i]) j = nxt[j];
if(s2[j + 1] == s2[i]) j++;
nxt[i] = j;
}
}
void f() {
int j = 0;
for(int i = 1; i <= n1; ++i) {
while(j > 0 && s2[j + 1] != s1[i]) j = nxt[j];
if(s2[j + 1] == s1[i]) j++;
if(j == n2) {
printf("%d\n", i - n2 + 1);
j = nxt[j];
}
}
}
int main() {
scanf("%s%s", s1 + 1, s2 + 1);
n1 = strlen(s1 + 1), n2 = strlen(s2 + 1);
gnext();
f();
for(int i = 1; i <= n2; ++i) {
printf("%d ", nxt[i]);
}
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
# P3375 【模板】KMP
## 题目描述
给出两个字符串 $s_1$ 和 $s_2$,若 $s_1$ 的区间 $[l, r]$ 子串与 $s_2$ 完全相同,则称 $s_2$ 在 $s_1$ 中出现了,其出现位置为 $l$。
现在请你求出 $s_2$ 在 $s_1$ 中所有出现的位置。
定义一个字符串 $s$ 的 border 为 $s$ 的一个**非 $s$ 本身**的子串 $t$,满足 $t$ 既是 $s$ 的前缀,又是 $s$ 的后缀。
对于 $s_2$,你还需要求出对于其每个前缀 $s'$ 的最长 border $t'$ 的长度。
## 输入格式
第一行为一个字符串,即为 $s_1$。
第二行为一个字符串,即为 $s_2$。
## 输出格式
首先输出若干行,每行一个整数,**按从小到大的顺序**输出 $s_2$ 在 $s_1$ 中出现的位置。
最后一行输出 $|s_2|$ 个整数,第 $i$ 个整数表示 $s_2$ 的长度为 $i$ 的前缀的最长 border 长度。
## 输入输出样例 #1
### 输入 #1
```
ABABABC
ABA
```
### 输出 #1
```
1
3
0 0 1
```
## 说明/提示
### 样例 1 解释
![](https://cdn.luogu.com.cn/upload/pic/2257.png)。
对于 $s_2$ 长度为 $3$ 的前缀 `ABA`,字符串 `A` 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 $1$。
### 数据规模与约定
**本题采用多测试点捆绑测试,共有 4 个子任务**。
- Subtask 0(30 points):$|s_1| \leq 15$,$|s_2| \leq 5$。
- Subtask 1(40 points):$|s_1| \leq 10^4$,$|s_2| \leq 10^2$。
- Subtask 2(30 points):无特殊约定。
- Subtask 3(0 points):Hack。
对于全部的测试点,保证 $1 \leq |s_1|,|s_2| \leq 10^6$,$s_1, s_2$ 中均只含大写英文字母。
+55
View File
@@ -0,0 +1,55 @@
#include<bits/stdc++.h>
using namespace std;
int n, m, s;
vector<int> fri[500005];
int dep[500005];
int fa[500005];
int son[500005];
int cnt[500005];
int dfn[500005];
int top[500005];
int nowdfn;
void dfs1(int now, int from) {
dfn[now] = ++nowdfn;
cnt[now] = 1;
dep[now] = dep[fa[now]] + 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
int lca(int a, int b) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
return b;
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(s, -1);
dfs2(s, s);
while(m--) {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}
+75
View File
@@ -0,0 +1,75 @@
# P3379 【模板】最近公共祖先(LCA)
## 题目描述
如题,给定一棵有根多叉树,请求出指定两个点之间最近的公共祖先。
## 输入格式
第一行包含三个正整数 $N,M,S$,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 $N-1$ 行每行包含两个正整数 $x, y$,表示 $x$ 结点和 $y$ 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 $M$ 行每行包含两个正整数 $a, b$,表示询问 $a$ 结点和 $b$ 结点的最近公共祖先。
## 输出格式
输出包含 $M$ 行,每行包含一个正整数,依次为每一个询问的结果。
## 输入输出样例 #1
### 输入 #1
```
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
```
### 输出 #1
```
4
4
1
4
4
```
## 说明/提示
对于 $30\%$ 的数据,$N\leq 10$,$M\leq 10$。
对于 $70\%$ 的数据,$N\leq 10000$,$M\leq 10000$。
对于 $100\%$ 的数据,$1 \leq N,M\leq 5\times10^5$,$1 \leq x, y,a ,b \leq N$,**不保证** $a \neq b$。
样例说明:
该树结构如下:
![](https://cdn.luogu.com.cn/upload/pic/2282.png)
第一次询问:$2, 4$ 的最近公共祖先,故为 $4$。
第二次询问:$3, 2$ 的最近公共祖先,故为 $4$。
第三次询问:$3, 5$ 的最近公共祖先,故为 $1$。
第四次询问:$1, 2$ 的最近公共祖先,故为 $4$。
第五次询问:$4, 5$ 的最近公共祖先,故为 $4$。
故输出依次为 $4, 4, 1, 4, 4$。
2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。
+125
View File
@@ -0,0 +1,125 @@
#include<bits/stdc++.h>
using namespace std;
int n, m, r, p;
int a[100005];
vector<int> fri[100005];
int dep[100005];
int cnt[100005];
int fa[100005];
int son[100005];
int top[100005];
int dfn[100005];
int nowdfn = 0;
struct seg{
int num[400005], lt[400005];
void pu(int now) {
num[now] = (num[now * 2] + num[now * 2 + 1]) % p;
}
void pd(int now, int nl, int nr) {
int l = lt[now], nmid = (nl + nr) >> 1;
num[now * 2] = (1ll * num[now * 2] + l * (nmid - nl + 1)) % p;
num[now * 2 + 1] = (1ll * num[now * 2 + 1] + l * (nr - nmid)) % p;
lt[now * 2] = (lt[now * 2] + l) % p;
lt[now * 2 + 1] = (lt[now * 2 + 1] + l) % p;
lt[now] = 0;
}
void add(int now, int nl, int nr, int l, int r, int nn) {
if(nl > r || nr < l) return;
if(l <= nl && nr <= r) {
num[now] = (1ll * (nn) * (nr - nl + 1) + num[now]) % p;
lt[now] = (lt[now] + nn) % p;
return;
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
add(now * 2, nl, nmid, l, r, nn);
add(now * 2 + 1, nmid + 1, nr, l, r, nn);
pu(now);
}
int query(int now, int nl, int nr, int l, int r) {
if(nl > r || nr < l) return 0;
if(l <= nl && nr <= r) {
return num[now] % p;
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
return (query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r)) % p;
}
}seg;
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[i] > cnt[son[now]]) son[now] = i;
}
}
void dfs2(int now, int from) {
dfn[now] = ++nowdfn;
top[now] = from;
// bit.add(nowdfn, nowdfn, a[now]);
seg.add(1, 1, n, nowdfn, nowdfn, a[now]);
if(son[now])
dfs2(son[now], top[now]);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
void pathadd(int x, int y, int nn) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
seg.add(1, 1, n, dfn[top[x]], dfn[x], nn);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x, y);
seg.add(1, 1, n, dfn[y], dfn[x], nn);
}
int query(int x, int y) {
int ans = 0;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
ans = (ans + seg.query(1, 1, n, dfn[top[x]], dfn[x])) % p;
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x, y);
return (ans + seg.query(1, 1, n, dfn[y], dfn[x])) % p;
}
void adds(int x, int nn) {
seg.add(1, 1, n, dfn[x], dfn[x] + cnt[x] - 1, nn);
}
int querys(int x) {
return seg.query(1, 1, n, dfn[x], dfn[x] + cnt[x] - 1) % p;
}
int main() {
scanf("%d%d%d%d", &n, &m, &r, &p);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(r, -1);
dfs2(r, r);
while(m--) {
int op, x, y, z;
scanf("%d", &op);
if(op == 1) {
scanf("%d%d%d", &x, &y, &z);
pathadd(x, y, z);
} else if(op ==2) {
scanf("%d%d", &x, &y);
printf("%d\n", query(x, y));
} else if(op == 3) {
scanf("%d%d", &x, &z);
adds(x, z);
} else {
scanf("%d", &x);
printf("%d\n", querys(x));
}
}
return 0;
}
+74
View File
@@ -0,0 +1,74 @@
# P3384 【模板】重链剖分 / 树链剖分
## 题目描述
如题,已知一棵包含 $N$ 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
- `1 x y z`,表示将树从 $x$ 到 $y$ 结点最短路径上所有节点的值都加上 $z$。
- `2 x y`,表示求树从 $x$ 到 $y$ 结点最短路径上所有节点的值之和。
- `3 x z`,表示将以 $x$ 为根节点的子树内所有节点值都加上 $z$。
- `4 x`,表示求以 $x$ 为根节点的子树内所有节点值之和。
## 输入格式
第一行包含 $4$ 个正整数 $N,M,R,P$,分别表示树的结点个数、操作个数、根节点序号和取模数(**即所有的输出结果均对此取模**)。
接下来一行包含 $N$ 个非负整数,分别依次表示各个节点上初始的数值。
接下来 $N-1$ 行每行包含两个整数 $x,y$,表示点 $x$ 和点 $y$ 之间连有一条边(保证无环且连通)。
接下来 $M$ 行每行包含若干个正整数,每行表示一个操作。
## 输出格式
输出包含若干行,分别依次表示每个操作 $2$ 或操作 $4$ 所得的结果(**对 $P$ 取模**)。
## 输入输出样例 #1
### 输入 #1
```
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
```
### 输出 #1
```
2
21
```
## 说明/提示
**【数据规模】**
对于 $30\%$ 的数据: $1 \leq N \leq 10$,$1 \leq M \leq 10$;
对于 $70\%$ 的数据: $1 \leq N \leq {10}^3$,$1 \leq M \leq {10}^3$;
对于 $100\%$ 的数据: $1\le N \leq {10}^5$,$1\le M \leq {10}^5$,$1\le R\le N$,$1\le P \le 2^{30}$。所有输入的数均在 `int` 范围内。
**【样例说明】**
树的结构如下:
![](https://cdn.luogu.com.cn/upload/pic/2319.png)
各个操作如下:
![](https://cdn.luogu.com.cn/upload/pic/2320.png)
故输出应依次为 $2$ 和 $21$。
+73
View File
@@ -0,0 +1,73 @@
#include<bits/stdc++.h>
using namespace std;
int n, q;
vector<int> fri[100005];
int fa[100005];
int dep[100005];
int top[100005];
int son[100005];
int cnt[100005];
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
int lca(int a, int b) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
return b;
}
int main() {
scanf("%d%d", &n, &q);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
while(q--) {
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
int lca1 = lca(a, b);
int lca2 = lca(c, d);
// lca1 在 [c, d] 上或 lca2 在 [a, b] 上
if(dep[lca2] >= dep[lca1]) {
if(lca(a, lca2) == lca2 || lca(b, lca2) == lca2) {
printf("Y\n");
} else {
printf("N\n");
}
} else {
if(lca(c, lca1) == lca1 || lca(d, lca1) == lca1) {
printf("Y\n");
} else {
printf("N\n");
}
}
}
return 0;
}
/*
- deep[x]>=deep[LCA(s,t)]
- LCA(s,x)=x或LCA(t,x)=x;
*/
+60
View File
@@ -0,0 +1,60 @@
# P3398 仓鼠找 sugar
## 题目描述
小仓鼠的和他的基(mei)友(zi)sugar 住在地下洞穴中,每个节点的编号为 $1\sim n$。地下洞穴是一个树形结构。这一天小仓鼠打算从从他的卧室($a$)到餐厅($b$),而他的基友同时要从他的卧室($c$)到图书馆($d$)。他们都会走最短路径。现在小仓鼠希望知道,有没有可能在某个地方,可以碰到他的基友?
小仓鼠那么弱,还要天天被 zzq 大爷虐,请你快来救救他吧!
## 输入格式
第一行两个正整数 $n$ 和 $q$,表示这棵树节点的个数和询问的个数。
接下来 $n-1$ 行,每行两个正整数 $u$ 和 $v$,表示节点 $u$ 到节点 $v$ 之间有一条边。
接下来 $q$ 行,每行四个正整数 $a$、$b$、$c$ 和 $d$,表示节点编号,也就是一次询问,其意义如上。
## 输出格式
对于每个询问,如果有公共点,输出大写字母 `Y`;否则输出`N`。
## 输入输出样例 #1
### 输入 #1
```
5 5
2 5
4 2
1 3
1 4
5 1 5 1
2 2 1 4
4 1 3 4
3 1 1 5
3 5 1 4
```
### 输出 #1
```
Y
N
Y
Y
Y
```
## 说明/提示
**本题时限 1s,内存限制 128M,因新评测机速度较为接近 NOIP 评测机速度,请注意常数问题带来的影响。**
$20\%$ 的数据 $n, q\le200$。
$40\%$ 的数据 $n, q\le 2\times10^3$。
$70\%$ 的数据 $n, q\le 5\times10^4$。
$100\%$ 的数据 $1\le n, q\le10^5$。
+25
View File
@@ -0,0 +1,25 @@
#include <bits/stdc++.h>
using namespace std;
int n, k, q, d[1000005], f[1000005];
typedef pair<int, int> pii;
pair<int, int> qq[4000005];
int lq = 1, rq = 0;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &d[i]);
scanf("%d", &q);
while(q--){
scanf("%d", &k);
lq = 1, rq = 1;
qq[1] = {0, 1};
for(int i = 2; i <= n; ++i) {
while(lq <= rq && qq[lq].second < i - k) ++lq;
f[i] = qq[lq].first + (d[qq[lq].second] <= d[i]);
while(lq <= rq && (qq[rq].first > f[i] || (qq[rq].first == f[i] && d[qq[rq].second] <= d[i]))) --rq;
qq[++rq] = {f[i], i};
}
printf("%d\n", f[n]);
}
}
+52
View File
@@ -0,0 +1,52 @@
# P3572 [POI 2014] PTA-Little Bird
## 题目描述
有 $n$ 棵树排成一排,第 $i$ 棵树的高度是 $d_i$。
有 $q$ 只鸟要从第 $1$ 棵树到第 $n$ 棵树。
当第 $i$ 只鸟在第 $j$ 棵树时,它可以飞到第 $j+1, j+2, \cdots, j+k_i$ 棵树。
如果一只鸟飞到一颗高度大于等于当前树的树,那么它的劳累值会增加 $1$,否则不会。
由于这些鸟已经体力不支,所以它们想要最小化劳累值。
## 输入格式
第一行输入 $n$。
第二行 $n$ 个数,第 $i$ 个数表示 $d_i$。
第三行输入 $q$。
接下来 $q$ 行,每一行一个整数,第 $i$ 行的整数为 $k_i$。
## 输出格式
共 $q$ 行,每一行输出第 $i$ 只鸟的最小劳累值。
## 输入输出样例 #1
### 输入 #1
```
9
4 6 3 6 3 7 2 6 5
2
2
5
```
### 输出 #1
```
2
1
```
## 说明/提示
$1 \le n \le 10^6$,$1 \le d_i \le 10^9$,$1 \le q \le 25$,$1 \le k_i \le n - 1$。
+36
View File
@@ -0,0 +1,36 @@
#include<bits/stdc++.h>
using namespace std;
long long n, p;
long long mul[3000005];
long long revs[3000005];
long long rev[3000005];
long long qpow(long long a, long long b) {
a %= p;
long long res = 1;
while(b) {
if(b & 1) {
res = (res * a) % p;
}
a = (a * a) % p;
b >>= 1;
}
return res;
}
int main() {
scanf("%lld%lld", &n, &p);
mul[0] = 1;
for(int i = 1; i <= n; ++i) {
mul[i] = (mul[i - 1] * i) % p;
}
revs[n] = qpow(mul[n], p - 2);
for(int i = n - 1; i >= 1; --i) {
revs[i] = ((i + 1) * revs[i + 1]) % p;
}
for(int i = 1; i <= n; ++i) {
rev[i] = (mul[i - 1] * revs[i]) % p;
}
for(int i = 1; i <= n; ++i) {
printf("%lld\n", rev[i]);
}
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
# P3811 【模板】模意义下的乘法逆元
## 题目背景
这是一道模板题。
## 题目描述
给定正整数 $n,p$,求 $[1,n]$ 中所有整数在模 $p$ 意义下的乘法逆元。
$a$ 模 $p$ 的乘法逆元定义为 $ax\equiv1\pmod p$ 的解。
## 输入格式
一行两个正整数 $n,p$。
## 输出格式
输出 $n$ 行,其中第 $i$ 行表示 $i$ 在模 $p$ 下的乘法逆元。
## 输入输出样例 #1
### 输入 #1
```
10 13
```
### 输出 #1
```
1
7
9
10
8
11
2
5
3
4
```
## 说明/提示
所有数据满足 $ 1 \leq n \leq 3 \times 10 ^ 6$,$n < p < 20000528 $。
输入保证 $ p $ 为质数。
+122
View File
@@ -0,0 +1,122 @@
#include<bits/stdc++.h>
const int mod = 201314;
using namespace std;
int n, m;
vector<int> chi[50005];
int fa[50005];
int cnt[50005];
int son[50005];
int top[50005];
int dep[50005];
int dfn[50005];
int nowdfn;
pair<int, int> query[100005];
struct SEG{
int num[400005], lt[400005];
void pu(int now) {num[now] = (1ll * num[now * 2] + num[now * 2 + 1]) % mod;}
void pd(int now, int nl, int nr) {
int nmid = (nl + nr) >> 1;
num[now * 2] = (num[now * 2] + 1ll * (nmid - nl + 1) * lt[now]) % mod;
num[now * 2 + 1] = (num[now * 2 + 1] + 1ll * (nr - nmid) * lt[now]) % mod;
lt[now * 2] = (lt[now * 2] + lt[now]) % mod;
lt[now * 2 + 1] = (lt[now * 2 + 1] + lt[now]) % mod;
lt[now] = 0;
}
void add(int now, int nl, int nr, int l, int r, int nn) {
if(nl > r || nr < l) return;
if(l <= nl && nr <= r) {
num[now] = (num[now] + 1ll * (nr - nl + 1) * nn) % mod;
lt[now] = (lt[now] + 1ll * nn) % mod;
return;
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
add(now * 2, nl, nmid, l, r, nn);
add(now * 2 + 1, nmid + 1, nr, l, r, nn);
pu(now);
}
int query(int now, int nl, int nr, int l, int r) {
if(nl > r || nr < l) return 0;
if(l <= nl && nr <= r) {
return num[now];
}
int nmid = (nl + nr) >> 1;
pd(now, nl, nr);
return (1ll * query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r)) % mod;
}
}seg;
void dfs1(int now) {
cnt[now] = 1;
for(int i : chi[now]) {
dep[i] = dep[now] + 1;
dfs1(i);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
top[now] = from;
dfn[now] = ++nowdfn;
if(son[now]) dfs2(son[now], from);
for(int i : chi[now]) {
if(i == son[now]) continue;
dfs2(i, i);
}
}
void addpath(int a, int b = 1) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
seg.add(1, 1, n, dfn[top[a]], dfn[a], 1);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
seg.add(1, 1, n, dfn[b], dfn[a], 1);
}
int querypath(int a, int b = 1) {
int ans = 0;
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
ans = (ans + 1ll * seg.query(1, 1, n, dfn[top[a]], dfn[a])) % mod;
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
return (ans + 1ll * seg.query(1, 1, n, dfn[b], dfn[a])) % mod;
}
int sx[100005];
int ans[100005];
int main() {
scanf("%d%d", &n, &m);
for(int i = 2; i <= n; ++i) {
scanf("%d", &fa[i]);
fa[i]++;
chi[fa[i]].push_back(i);
}
dfs1(1);
dfs2(1, 1);
for(int i = 1, l, r, z; i <= m; ++i) {
scanf("%d%d%d", &l, &r, &z);
l++, r++, z++;
sx[i] = i;
sx[i + m] = i + m;
query[2 * i - 1] = {l - 1, z};
query[2 * i] = {r, z};
}
sort(sx + 1, sx + 2 * m + 1, [](int x, int y){return query[x] < query[y];});
int cur = 0;
for(int i = 1; i <= 2 * m; ++i) {
int u = query[sx[i]].first;
int w = query[sx[i]].second;
while(cur < u) {
++cur;
addpath(cur);
}
ans[sx[i]] = querypath(w);
}
for(int i = 1; i <= m; ++i) {
printf("%d\n", (ans[2 * i] - ans[2 * i - 1] + mod) % mod);
}
return 0;
}
/*
从根到 [l, r] 中每个点的路径上的点均 + 1,最否求 z 到根的路径权值和
*/
+54
View File
@@ -0,0 +1,54 @@
# P4211 [LNOI2014] LCA
## 题目描述
给出一个 $n$ 个节点的有根树(编号为 $0$ 到 $n-1$,根节点为 $0$)。
一个点的深度定义为这个节点到根的距离 $+1$。
设 $dep[i]$ 表示点 $i$ 的深度,$\operatorname{LCA}(i, j)$ 表示 $i$ 与 $j$ 的最近公共祖先。
有 $m$ 次询问,每次询问给出 $l, r, z$,求 $\sum_{i=l}^r dep[\operatorname{LCA}(i,z)]$ 。
## 输入格式
第一行 $2$ 个整数,$n, m$。
接下来 $n-1$ 行,分别表示点 $1$ 到点 $n-1$ 的父节点编号。
接下来 $m$ 行,每行 $3$ 个整数,$l, r, z$。
## 输出格式
输出 $m$ 行,每行表示一个询问的答案。每个答案对 $201314$ 取模输出。
## 输入输出样例 #1
### 输入 #1
```
5 2
0
0
1
1
1 4 3
1 4 2
```
### 输出 #1
```
8
5
```
## 说明/提示
### 数据范围及约定
- 对于 $20\%$ 的数据,$n\le 10000,m\le 10000$;
- 对于 $40\%$ 的数据,$n\le 20000,m\le 20000$;
- 对于 $60\%$ 的数据,$n\le 30000,m\le 30000$;
- 对于 $80\%$ 的数据,$n\le 40000,m\le 40000$;
- 对于 $100\%$ 的数据,$1\le n\le 50000,1\le m\le 50000$。
+60
View File
@@ -0,0 +1,60 @@
#include<bits/stdc++.h>
using namespace std;
int n, m;
vector<int> fri[500005];
int fa[500005];
int dep[500005];
int top[500005];
int son[500005];
int cnt[500005];
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[son[now]] < cnt[i]) son[now] = i;
}
}
void dfs2(int now, int from) {
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
int lca(int a, int b) {
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
return b;
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
while(m--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
int l1 = lca(x, y), l2 = lca(x, z), l3 = lca(y, z);
int mind = min({dep[l1], dep[l2], dep[l3]});
int maxd = max({dep[l1], dep[l2], dep[l3]});
if(l1 == l2)
printf("%d %d\n", l3, dep[x] + dep[y] + dep[z] - 2 * mind - maxd);
else if(l1 == l3)
printf("%d %d\n", l2, dep[x] + dep[y] + dep[z] - 2 * mind - maxd);
else
printf("%d %d\n", l1, dep[x] + dep[y] + dep[z] - 2 * mind - maxd);
}
return 0;
}
+53
View File
@@ -0,0 +1,53 @@
# P4281 [AHOI2008] 紧急集合 / 聚会
## 题目描述
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有 $n$ 个等待点,有 $n-1$ 条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。
参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在 $n$ 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。
小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
## 输入格式
第一行两个正整数 $n$ 和 $m$,分别表示等待点的个数(等待点也从 $1$ 到 $n$ 进行编号)和获奖所需要完成集合的次数。
随后 $n-1$ 行,每行两个正整数 $a,b$,表示编号为 $a$ 和编号为 $b$ 的等待点之间有一条路。
随后 $m$ 行,每行用三个正整数 $x,y,z$,表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。
## 输出格式
输出共 $m$ 行,每行两个用空格隔开的整数 $p,c$。其中第 $i$ 行表示第 $i$ 次集合点选择在编号为 $p$ 的等待点,集合总共的花费是 $c$ 个游戏币。
## 输入输出样例 #1
### 输入 #1
```
6 4
1 2
2 3
2 4
4 5
5 6
4 5 6
6 3 1
2 4 4
6 6 6
```
### 输出 #1
```
5 2
2 5
4 1
6 0
```
## 说明/提示
对于 $40\%$ 的数据,$n\leq2\times10^3$,$m\leq2\times 10^3$。
对于 $100\%$ 的数据,$1\leq x,y,z\leq n\leq 5\times10^5$,$1\leq m\leq 5\times 10^5$。
+61
View File
@@ -0,0 +1,61 @@
#include <bits/stdc++.h>
using namespace std;
int n;
char op[105];
long long a[105];
long long f[105][105];
long long g[105][105];
void getc(char &c) {
c = getchar_unlocked();
while(c != 't' && c != 'x') c = getchar_unlocked();
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
getc(op[i]);
scanf("%lld", &a[i]);
a[i + n] = a[i];
op[i + n] = op[i];
}
for (int i = 1; i <= 2 * n; ++i) {
f[i][i] = a[i];
g[i][i] = a[i];
}
for (int len = 2; len <= n; ++len) {
for (int l = 1; l + len - 1 <= 2 * n; ++l) {
int r = l + len - 1;
f[l][r] = LLONG_MIN;
g[l][r] = LLONG_MAX;
for (int k = l; k < r; ++k) {
char o = op[k + 1];
if (o == 't') {
f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r]);
g[l][r] = min(g[l][r], g[l][k] + g[k + 1][r]);
} else {
long long v1 = f[l][k] * f[k + 1][r];
long long v2 = f[l][k] * g[k + 1][r];
long long v3 = g[l][k] * f[k + 1][r];
long long v4 = g[l][k] * g[k + 1][r];
f[l][r] = max({f[l][r], v1, v2, v3, v4});
g[l][r] = min({g[l][r], v1, v2, v3, v4});
}
}
}
}
long long ans = LLONG_MIN;
vector<int> anss;
for (int l = 1; l <= n; ++l) {
int r = l + n - 1;
// printf("%lld\n", f[l][r]);
if (f[l][r] > ans) {
ans = f[l][r];
anss.clear();
anss.push_back(l);
} else if(f[l][r] == ans) {
anss.push_back(l);
}
}
printf("%lld\n", ans);
for(int i:anss) printf("%d ", i);
return 0;
}
+60
View File
@@ -0,0 +1,60 @@
# P4342 [IOI 1998] Polygon
## 题目描述
**题目可能有些许修改,但大意一致。**
Polygon 是一个玩家在一个有 $n$ 个顶点的多边形上玩的游戏,如图所示,其中 $n = 4$。每个顶点用整数标记,每个边用符号 `+`(加)或符号 `*`(乘积)标记。
![](https://cdn.luogu.org/upload/pic/16086.png)
第一步,删除其中一条边。随后每一步:
选择一条边连接的两个顶点 $V_1$ 和 $V_2$,用边上的运算符计算 $V_1$ 和 $V_2$ 得到的结果来替换这两个顶点。
游戏结束时,只有一个顶点,没有多余的边。
如图所示,玩家先移除编号为 $3$ 的边。之后,玩家选择计算编号为 $1$ 的边,然后计算编号为 $4$ 的边,最后,计算编号为 $2$ 的边。结果是 $0$。
边的编号规则为:第 $i$ 条边连接编号为 $i-1$ 和编号为 $i$ 的点。特别地,第 $1$ 条边连接编号为 $1$ 和编号为 $n$ 的点
![](https://cdn.luogu.org/upload/pic/16088.png)
(译者注:这里每条边的运算符旁边的数字为边的编号,不拿来计算)
最终剩余的点的数值即为得到的分数。
编写一个程序,给定一个多边形,计算最高可能的分数。
## 输入格式
输入描述一个有 $n$ 个顶点的多边形,它包含两行。第一行是数字 $n$,为总边数。
第二行描述这个多边形,一共有 $n$ 组,每组中第一个字符为边 $i$ 的计算符号(`t` 代表相加,`x` 代表相乘),第二个代表顶点 $i$ 上的数字。多边形首尾相连。
## 输出格式
第一行,输出最高的分数。在第二行,对于每一条边,输出这条边当且仅当在删除这条边后,能够通过操作得到所有可能情况中的最高分,以严格升序输出。
## 输入输出样例 #1
### 输入 #1
```
4
t -7 t 4 x 2 x 5
```
### 输出 #1
```
33
1 2
```
## 说明/提示
保证 $3 \le n \le 50$。
对于任何一系列的操作,顶点数字都在 $[-32768,32767]$ 的范围内。
+85
View File
@@ -0,0 +1,85 @@
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n, m, k;
int up;
vector<int> fri[300005];
int fa[3000005];
int cnt[300005];
int son[300005];
int dfn[300005];
int rev[300005];
int top[300005];
int dep[300005];
int nowdfn;
struct SEG{
int num[51][1200005];
void init(int now, int nl, int nr) {
if(nl == nr) {
num[0][now] = 1;
for(int i = 1; i <= 50; ++i)
num[i][now] = (1ll * num[i - 1][now] * dep[rev[nl]]) % mod;
return;
}
int nmid = (nl + nr) >> 1;
init(now * 2, nl, nmid);
init(now * 2 + 1, nmid + 1, nr);
for(int i = 1; i <= 50; ++i) num[i][now] = (1ll * num[i][now * 2] + num[i][now * 2 + 1]) % mod;
}
int query(int now, int nl, int nr, int l, int r) {
if(nl > r || nr < l) return 0;
if(l <= nl && nr <= r) return num[k][now];
int nmid = (nl + nr) >> 1;
return (1ll * query(now * 2, nl, nmid, l, r) + query(now * 2 + 1, nmid + 1, nr, l, r)) % mod;
}
}seg;
void dfs1(int now, int from) {
cnt[now] = 1;
for(int i : fri[now]) {
if(i == from) continue;
fa[i] = now;
dep[i] = dep[now] + 1;
dfs1(i, now);
cnt[now] += cnt[i];
if(cnt[i] > cnt[son[now]]) son[now] = i;
}
}
void dfs2(int now, int from) {
dfn[now] = ++nowdfn;
rev[nowdfn] = now;
top[now] = from;
if(son[now]) dfs2(son[now], from);
for(int i : fri[now]) {
if(i == fa[now] || i == son[now]) continue;
dfs2(i, i);
}
}
int query(int a, int b) {
int ans = 0;
while(top[a] != top[b]) {
if(dep[top[a]] < dep[top[b]]) swap(a, b);
ans = (ans + 1ll * seg.query(1, 1, n, dfn[top[a]], dfn[a])) % mod;
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
ans = (ans + 1ll * seg.query(1, 1, n, dfn[b], dfn[a])) % mod;
return ans;
}
int main() {
scanf("%d", &n);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
fri[u].push_back(v);
fri[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
seg.init(1, 1, n);
scanf("%d", &m);
for(up = 1; up <= m; ++up) {
int i, j;
scanf("%d%d%d", &i, &j, &k);
printf("%d\n", query(i, j));
}
return 0;
}
+67
View File
@@ -0,0 +1,67 @@
# P4427 [BJOI2018] 求和
## 题目描述
master 对树上的求和非常感兴趣。他生成了一棵有根树,并且希望多次询问这棵树上一段路径上所有节点深度的 $k$ 次方和,而且每次的 $k$ 可能是不同的。此处节点深度的定义是这个节点到根的路径上的边数。他把这个问题交给了 pupil,但 pupil 并不会这么复杂的操作,你能帮他解决吗?
## 输入格式
第一行包含一个正整数 $n$,表示树的节点数。
之后 $n-1$ 行每行两个空格隔开的正整数 $i, j$,表示树上的一条连接点 $i$ 和点 $j$ 的边。
之后一行一个正整数 $m$,表示询问的数量。
之后每行三个空格隔开的正整数 $i, j, k$,表示询问从点 $i$ 到点 $j$ 的路径上所有节点深度的 $k$ 次方和。由于这个结果可能非常大,输出其对 $998244353$ 取模的结果。
树的节点从 $1$ 开始标号,其中 $1$ 号节点为树的根。
## 输出格式
对于每组数据输出一行一个正整数表示取模后的结果。
## 输入输出样例 #1
### 输入 #1
```
5
1 2
1 3
2 4
2 5
2
1 4 5
5 4 45
```
### 输出 #1
```
33
503245989
```
## 说明/提示
### 样例解释
以下用 $d (i)$ 表示第 $i$ 个节点的深度。
对于样例中的树,有 $d (1) = 0, d (2) = 1, d (3) = 1, d (4) = 2, d (5) = 2$。
因此第一个询问答案为 $(2^5 + 1^5 + 0^5) \bmod 998244353 = 33$,第二个询问答案为$(2^{45} + 1^{45} + 2^{45}) \bmod 998244353 = 503245989$。
### 数据范围
对于 $30\%$ 的数据,$1 \leq n,m \leq 100$。
对于 $60\%$ 的数据,$1 \leq n,m \leq 1000$。
对于 $100\%$ 的数据,$1 \leq n,m \leq 300000$,$1 \leq k \leq 50$。
另外存在 5 个不计分的 hack 数据。
### 提示
数据规模较大,请注意使用较快速的输入输出方式。
+32
View File
@@ -0,0 +1,32 @@
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m, s;
int d[100005];
vector<pair<int, int>> fri[100005];
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
signed main() {
scanf("%lld%lld%lld", &n, &m, &s);
memset(d, 0x7f, sizeof d);
d[s] = 0;
for(int i = 1, u, v, w; i <= m; ++i) {
scanf("%lld%lld%lld", &u, &v, &w);
fri[u].push_back({w, v});
}
q.push({0, s});
while(!q.empty()) {
pair<int, int> now = q.top();
q.pop();
if (now.first != d[now.second]) continue;
for(pair<int, int> to:fri[now.second]) {
if(d[to.second] > d[now.second] + to.first) {
d[to.second] = d[now.second] + to.first;
q.push({d[to.second], to.second});
}
}
}
for(int i = 1; i <= n; ++i) {
printf("%lld ", d[i]);
}
return 0;
}
+70
View File
@@ -0,0 +1,70 @@
# P4779 【模板】单源最短路径(标准版)
## 题目背景
2018 年 7 月 19 日,某位同学在 [NOI Day 1 T1 归程](https://www.luogu.com.cn/problem/P4768) 一题里非常熟练地使用了一个广为人知的算法求最短路。
然后呢?
$100 \rightarrow 60$;
$\text{Ag} \rightarrow \text{Cu}$;
最终,他因此没能与理想的大学达成契约。
小 F 衷心祝愿大家不再重蹈覆辙。
## 题目描述
给定一个 $n$ 个点,$m$ 条有向边的带非负权图,请你计算从 $s$ 出发,到每个点的距离。
数据保证你能从 $s$ 出发到任意点。
## 输入格式
第一行为三个正整数 $n, m, s$。
第二行起 $m$ 行,每行三个非负整数 $u_i, v_i, w_i$,表示从 $u_i$ 到 $v_i$ 有一条权值为 $w_i$ 的有向边。
## 输出格式
输出一行 $n$ 个空格分隔的非负整数,表示 $s$ 到每个点的距离。
## 输入输出样例 #1
### 输入 #1
```
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
```
### 输出 #1
```
0 2 4 3
```
## 说明/提示
样例解释请参考 [数据随机的模板题](https://www.luogu.com.cn/problem/P3371)。
$1 \leq n \leq 10^5$;
$1 \leq m \leq 2\times 10^5$;
$s = 1$;
$1 \leq u_i, v_i\leq n$;
$0 \leq w_i \leq 10 ^ 9$,
$0 \leq \sum w_i \leq 10 ^ 9$。
本题数据可能会持续更新,但不会重测,望周知。
2018.09.04 数据更新 from @zzq
+19
View File
@@ -0,0 +1,19 @@
#include<bits/stdc++.h>
using namespace std;
int n;
int a[3000005];
int stk[3000005];
int top = 0;
int ans[3000005];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for(int i = n; i >= 1; --i) {
while(top > 0 && a[stk[top]] <= a[i]) top--;
stk[++top] = i;
ans[i] = stk[top - 1];
}
for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
}
+52
View File
@@ -0,0 +1,52 @@
# P5788 【模板】单调栈
## 题目背景
模板题,无背景。
2019.12.12 更新数据,放宽时限,现在不再卡常了。
## 题目描述
给出项数为 $n$ 的整数数列 $a_{1 \dots n}$。
定义函数 $f(i)$ 代表数列中第 $i$ 个元素之后第一个大于 $a_i$ 的元素的**下标**,即 $f(i)=\min_{i<j\leq n, a_j > a_i} \{j\}$。若不存在,则 $f(i)=0$。
试求出 $f(1\dots n)$。
## 输入格式
第一行一个正整数 $n$。
第二行 $n$ 个正整数 $a_{1\dots n}$。
## 输出格式
一行 $n$ 个整数表示 $f(1), f(2), \dots, f(n)$ 的值。
## 输入输出样例 #1
### 输入 #1
```
5
1 4 2 3 5
```
### 输出 #1
```
2 5 4 5 0
```
## 说明/提示
【数据规模与约定】
对于 $30\%$ 的数据,$n\leq 100$;
对于 $60\%$ 的数据,$n\leq 5 \times 10^3$ ;
对于 $100\%$ 的数据,$1 \le n\leq 3\times 10^6$,$1\leq a_i\leq 10^9$。
+33
View File
@@ -0,0 +1,33 @@
#include<bits/stdc++.h>
using namespace std;
long long a;
int minans = INT_MAX;
int n;
void dfs(int now, int pre_k, long long now_a) {
if(now > 6 || now >= minans) {
return;
}
if(now_a == (1ll << n) - 1) {
minans = min(minans, now);
}
for(int k = pre_k; k <= n; ++k) {
long long new_a = now_a | (now_a >> k);
if(new_a > now_a) dfs(now + 1, k, new_a);
}
}
int main() {
char s[45];
scanf("%s", s + 1);
if(s[1] == '0') {
printf("-1\n");
return 0;
}
n = strlen(s + 1);
for(int i = 1; i <= n; ++i) {
a *= 2;
if(s[i] == '1') a += 1;
}
dfs(0, 1, a);
printf("%d\n", minans);
return 0;
}
+55
View File
@@ -0,0 +1,55 @@
# P7458 [CERC2018] Clockwork ||ange
## 题目描述
**译自[ [CERC2018]](https://contest.felk.cvut.cz/18cerc/) [Clockwork ||ange](https://contest.felk.cvut.cz/18cerc/solved/clockwork.pdf)**
据百度百科,兔是哺乳类兔形目兔科下属所有的属的总称。毋庸置疑的是,这段文字不是无聊的,因为它们是原始的,并且组织良好。我们农场的兔子生活在有保护的畜栏里,畜栏的边界装饰有精美的花卉装饰品。畜栏里生长着数十个可爱的橙色胡萝卜。兔子繁殖很快(这很正常,每年都有成千上万的兔子出生),我们的导师希望不费力地把它们安置在畜栏里。
畜栏规则地形成一排。在第一个繁殖季开始时,一些畜栏里可能没有兔子。每个繁殖季结束时,都会进行一次精心策划过的兔子搬迁。搬迁遵循一条简单的公式,这个公式取决于一个正整数 $K$,不同的繁殖季 $K$ 值不一定相同。对于所有畜栏,搬迁工作同时进行。在每个畜栏中,大约有一半的兔子被移到这个畜栏右边第 $K$ 个畜栏里。目标畜栏里原来有没有兔子都没有关系。
如果畜栏离这一排最后一个畜栏太近(即离最后一个畜栏的距离小于 $K$ ),那么这个畜栏中的所有兔子都留在原畜栏,不会进行搬迁。
每个畜栏都可以容纳无限只兔子,并且非空的畜栏中总有足够多能够成功繁殖的兔子。
给定第一个繁殖季开始时畜栏被占用的情况。请求出使得所有畜栏中都有兔子的最少搬迁次数。
## 输入格式
输入仅有一行,包含一个长度为 $b$ 的字符串,每个字符表示一个畜栏。字符只有 $0$(空畜栏)和 $1$(有兔子的畜栏)两种。第一个字符表示这一排的第一个畜栏。
## 输出格式
输出使得所有畜栏中都有兔子的最少搬迁次数。如果没有任何一种搬迁方案使得每个畜栏中都有兔子,输出 $-1$。
## 输入输出样例 #1
### 输入 #1
```
1010
```
### 输出 #1
```
1
```
## 输入输出样例 #2
### 输入 #2
```
100110000
```
### 输出 #2
```
3
```
## 说明/提示
$1≤b≤40$
+99
View File
@@ -0,0 +1,99 @@
#include <bits/stdc++.h>
using namespace std;
struct node {int l, r, res = 0, h;};
const int N = 1e6 + 7;
int n, m, k, h[N], t[N];
vector<node> tr[N * 4];
bool operator<(const node &a, const node &b) { return a.h < b.h; }
node operator+(const node &a, const node &b)
{
if (!a.res)
return b;
if (!b.res)
return a;
node ret;
ret.h = max(a.h, b.h), ret.l = a.l, ret.r = b.r;
ret.res = a.res + b.res - ((a.r == b.l)?1:0);
return ret;
}
void pu(int p)
{
int t1 = 0, t2 = 0;
while (t1 < (int)tr[p << 1].size() || t2 < (int)tr[p << 1 | 1].size())
{
if (t2 == (int)tr[p << 1 | 1].size() || (t1 < (int)tr[p << 1].size() && tr[p << 1][t1] < tr[p << 1 | 1][t2]))
{
if (t2)
tr[p].push_back(tr[p << 1][t1++] + tr[p << 1 | 1][t2 - 1]);
else
tr[p].push_back(tr[p << 1][t1++]);
}
else
{
if (t1)
tr[p].push_back(tr[p << 1][t1 - 1] + tr[p << 1 | 1][t2++]);
else
tr[p].push_back(tr[p << 1 | 1][t2++]);
}
}
}
void add(int p, int l, int r, int pos, int h, int t)
{
if (l == r)
{
tr[p].push_back({t, t, 1, h});
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
add(p << 1, l, mid, pos, h, t);
else
add(p << 1 | 1, mid + 1, r, pos, h, t);
if (r == pos)
pu(p);
}
node qu(int p, int l, int r, int lq, int rq, int h)
{
if (lq <= l && rq >= r)
{
if (tr[p][0].h > h)
return node{0, 0, 0, 0};
int pos = upper_bound(tr[p].begin(), tr[p].end(), node{0, 0, 0, h}) - tr[p].begin() - 1;
return tr[p][pos];
}
int mid = (l + r) >> 1;
if (rq <= mid)
return qu(p << 1, l, mid, lq, rq, h);
if (lq > mid)
return qu(p << 1 | 1, mid + 1, r, lq, rq, h);
return qu(p << 1, l, mid, lq, rq, h) + qu(p << 1 | 1, mid + 1, r, lq, rq, h);
}
signed main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
scanf("%d", &h[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &t[i]);
for (int i = 1; i <= n; i++)
add(1, 1, n + m, i, h[i], t[i]);
int lst = 0, cnt = n;
int l, r, x, y;
for(int i = 1; i <= m; ++i)
{
int op;
scanf("%d", &op);
if (op == 1)
{
scanf("%d%d%d", &l, &r, &x), l ^= k * lst, r ^= k * lst, x ^= k * lst;
lst = qu(1, 1, n + m, l, r, x).res;
printf("%d\n", lst);
}
else
{
scanf("%d%d", &x, &y), x ^= k * lst, y ^= k * lst;
add(1, 1, n + m, ++cnt, x, y);
}
}
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
# P7707 「Wdsr-2.7」百花齐放的太阳花田
## 题目背景
> 春天到来,花儿盛开……只是这次开花比起以往多上许多,甚至所有季节的花都开了。
$$\kern{1pt}\tag*{\small\text{——东方花映冢}}$$
今年的太阳花田,因为各种各样花朵的开放而显得格外热闹。尽管这是一场有关于花的异变,但是太阳花田的主人风见幽香却格外享受由鲜花带来的五彩缤纷的花田——毕竟,向来冷清的花田因为各种妖精的活跃而变得热闹了起来。
幽香非常喜欢这些花朵。每朵花都有它自己的种类,也代表了一种性格。比如向日葵,比如蒲公英,比如天人菊……
奇怪的是,有些花朵可能会属于同一种类,但由于相关因素(比如年龄、营养等),使得每种花都有一个高度。
幽香非常感兴趣,因为这些花朵高高低低形形色色,使得太阳花田呈现出了别样的景色。兴致之余,幽香选取了一些花朵,希望你帮助她解决一个问题。
## 题目描述
一开始,幽香选择了太阳花田当中的 $n$ 朵花朵。每朵花有一个高度 $h_i$ ,以及它属于的类别 $t_i$ 。它们排成了一列,并且被标号为 $1,2,3\cdots n$ 。幽香有 $m$ 次操作,分为两种:
- $\colorbox{f0f0f0}{\verb!1 l r x!}$ :考虑区间 $[l,r]$ 内的所有花朵。我们取出其中所有高度不超过 $x$ 的花朵(即 $h_i\le x$ 的花朵),**依次排列**(即不改变花朵在原序列中的顺序),可以根据它们的种类划分为若干段(比如,$\{\underline{1,}\ \underline{2,2},\underline{1,1},\underline {3,}\ \underline{4,4,4}\}$ 就能分为 $5$ 段)。幽香希望你告诉她一共有多少段。
- $\colorbox{f0f0f0}{\verb!2 x y!}$ :幽香挑选了一朵高度为 $x$ ,种类为 $y$ 的花朵,拼接在末尾。
**强制在线。**
## 输入格式
- 第一行三个整数 $n,m,k$ 。$n,m$ 含义如上,而 $k$ 为**强制在线参数**。
- 接下来两行,第一行是 $n$ 个整数,表示 $\{h_1,h_2\cdots h_n\}$ ;第三行 $n$ 个整数,表示序列 $t_1,t_2\cdots t_n\}$ 。含义如题所示。
- 接下来 $m$ 行,每行首先一个整数 $op$ ,表示本次操作的种类。
- 如果 $op = 1$,接下来三个整数 $l',r',x'$,描述一次查询。
- 如果 $op = 2$,接下来两个整数 $x',y'$,表示一次修改。
- 在所有操作中,真正的 $l,r,x,y$ 都是输入的 $l',r',x',y'$ 异或上 $k \times last$ 后的值。其中, $last$ 表示最后一次查询的答案,初始为 $0$。
## 输出格式
- 共有若干行。对于每个操作 $1$ ,每行输出一个整数,表示答案。
## 输入输出样例 #1
### 输入 #1
```
10 10 0
6 8 5 9 6 10 2 4 8 9
2 4 3 3 4 1 2 3 3 2
2 2 8
1 1 4 4
2 1 6
2 10 2
1 7 9 7
2 10 8
2 8 4
2 3 10
1 5 16 5
1 4 14 7
```
### 输出 #1
```
0
2
5
5
```
## 输入输出样例 #2
### 输入 #2
```
10 20 0
2 19 13 20 7 19 17 8 15 12
1 2 4 3 5 3 2 4 2 2
2 9 3
2 17 3
1 1 9 15
1 1 12 3
2 15 5
1 6 13 6
2 7 1
2 20 3
2 12 1
2 10 1
1 5 9 4
1 1 7 15
2 12 4
1 12 12 14
2 7 5
1 3 18 6
1 7 7 1
2 8 5
1 6 8 10
1 14 18 4
```
### 输出 #2
```
5
1
0
0
3
0
0
0
1
0
```
## 说明/提示
$n'$ 表示进行所有操作后的序列长度。
$$
\def\arraystretch{1.5}
\begin{array}{|c|c|c|c|}\hline
\bf{Subtask} & \bm{n',m} & \textsf{\textbf{特殊性质}} & \textsf{\textbf{分值}} \cr\hline
1 & 1\le n',m \le 10^3 & \text{无} & 10 \cr\hline
2 & 1\le n',m \le 3\times 10^5 & k=0 & 20\cr\hline
3 & 1\le n',m \le 3\times 10^5 & l=1 & 25\cr\hline
4 & 1\le n',m \le 10^5 & \text{无} & 15\cr\hline
5 & 1\le n',m \le 3\times 10^5 & \text{无} & 20\cr\hline
6 & \text{无特殊限制} & \text{时限 3s,空限 300MB} & 10\cr\hline
\end{array}$$
- 对于 $100\%$ 的数据:
$1 \le n',m \le 5 \times 10 ^ 5$。
$1 \le h_i,t_i,x,y \le 10^9$,$1 \le l \le r \le n'$。
$1 \le op \le 2$,$0 \le k \le 10^3$。
提示:请注意常数优化。
+65
View File
@@ -0,0 +1,65 @@
#include <bits/stdc++.h>
using namespace std;
int t;
int n, q;
struct node{
vector<node*> child;
char c;
int cnt = 0;
int childsize = 0;
};
char s[3000005];
int len;
void add(node *now, int i) {
now -> childsize++;
if(i == len) {
now -> cnt++;
return;
}
// if(now -> c == ' ') {
bool flag = 0;
for(auto c : now -> child) {
if(c -> c == s[i]) {
add(c, i + 1);
flag = 1;
break;
}
}
if(!flag) {
node *nc = new node;
nc -> c = s[i];
now->child.push_back(nc);
add(nc, i + 1);
}
// }
}
int query(node *now, int i) {
if(i == len) {
return now -> childsize;
}
for(auto c : now -> child) {
if(c -> c == s[i]) {
return query(c, i + 1);
}
}
return 0;
}
int main() {
scanf("%d", &t);
while(t--) {
node *root = new node;
// root -> c=' ';
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
len = strlen(s + 1) + 1;
add(root, 1);
}
for(int i = 1; i <= q; ++i) {
scanf("%s", s + 1);
len = strlen(s + 1) + 1;
printf("%d\n", query(root, 1));
}
}
return 0;
}
+71
View File
@@ -0,0 +1,71 @@
# P8306 【模板】字典树 / Trie
## 题目描述
给定 $n$ 个模式串 $s_1, s_2, \dots, s_n$ 和 $q$ 次询问,每次询问给定一个文本串 $t_i$,请回答 $s_1 \sim s_n$ 中有多少个字符串 $s_j$ 满足 $t_i$ 是 $s_j$ 的**前缀**。
一个字符串 $t$ 是 $s$ 的前缀当且仅当从 $s$ 的末尾删去若干个(可以为 $0$ 个)连续的字符后与 $t$ 相同。
输入的字符串大小写敏感。例如,字符串 `Fusu` 和字符串 `fusu` 不同。
## 输入格式
**本题单测试点内有多组测试数据**。
输入的第一行是一个整数,表示数据组数 $T$。
对于每组数据,格式如下:
第一行是两个整数,分别表示模式串的个数 $n$ 和询问的个数 $q$。
接下来 $n$ 行,每行一个字符串,表示一个模式串。
接下来 $q$ 行,每行一个字符串,表示一次询问。
## 输出格式
按照输入的顺序依次输出各测试数据的答案。
对于每次询问,输出一行一个整数表示答案。
## 输入输出样例 #1
### 输入 #1
```
3
3 3
fusufusu
fusu
anguei
fusu
anguei
kkksc
5 2
fusu
Fusu
AFakeFusu
afakefusu
fusuisnotfake
Fusu
fusu
1 1
998244353
9
```
### 输出 #1
```
2
1
0
1
2
1
```
## 说明/提示
### 数据规模与约定
对于全部的测试点,保证 $1 \leq T, n, q\leq 10^5$,且输入字符串的总长度不超过 $3 \times 10^6$。输入的字符串只含大小写字母和数字,且不含空串。
### 说明
std 的 IO 使用的是关闭同步后的 cin/cout,本题不卡常。
+70
View File
@@ -0,0 +1,70 @@
# CSPS 2026 游记
华二紫竹,$2026-10-31T14:30+08:00 \sim 2026-10-31T18:30+08:00$。
## timeline (git commit history):
- 14:39 init (带 diff.sh)
- 14:58 T1 45 pts
- 15:26 T1 100 pts
- 15:34 为 T1 写了 gen
- 16:04 T2 20pts(实际 36 pts)
- 16:15 T2 60 pts(实际 68 pts)
- 16:19 T2 开 long long
- 16:22 T1 对拍发现有一个变量没开 long long(感谢 diff.sh)
- 16:50 为 T2 写了 gen(然而 gen 的数据并不保证图连通 这个考场上导致好多次误判)
- 17:07 因为 T2 gen 的构造的数据问题导致不必要的 long long 和特判
- 17:59 依旧卡 T2,T3 T4 毫无推进
- (约)18:20 发现 T2 并无问题,尝试写 T3 T4 暴力,然而时间不够
## T1
最开始以为答案是关于最终相等的数单调的,想写二分,发现第二个样例证伪了这一结论。发现最终相同的数一定是 $a_i (i \in [1, n])$。
先写了个 [45pts 的暴力](https://git.vblg.top/hanyixuanten/csps2026/src/commit/bf08016dee210d518b187b7730a4f8f27ae47f24/crystal/crystal.cpp)。此时考试已开始 28 min。
考虑到使用二分 + 前缀和优化[寻找 ans 的过程](https://git.vblg.top/hanyixuanten/csps2026/src/commit/bf08016dee210d518b187b7730a4f8f27ae47f24/crystal/crystal.cpp#L12-L13)。但是因为二分出来的答案一直多 1 或少 1,就出现了[这几行代码(笑哭)](https://git.vblg.top/hanyixuanten/csps2026/src/commit/8b21377d1fa3208ef8d27b7592e91d1dbf8a40fc/crystal/crystal.cpp#L22-L23)。
然而,最开始的时候 [checkb 函数的 now 没开 long long](https://git.vblg.top/hanyixuanten/csps2026/src/commit/8b21377d1fa3208ef8d27b7592e91d1dbf8a40fc/crystal/crystal.cpp#L7),差点 100->65,diff.sh 立大功。
附 [gen.cpp](https://git.vblg.top/hanyixuanten/csps2026/src/commit/c9a42541c404d2b6395e49b9c35e4eb71109327b/crystal/gen.cpp)。
## T2
其实考试开始 34 min 就想出了[“500 条线路各视为图上的一个点,分别和地铁站连线”](https://git.vblg.top/hanyixuanten/csps2026/src/commit/64eda003fc35794e1805274785a058db89a21eeb/subway/subway.cpp#L49),但是由于无负边权,自然的想到了 dij,后续又加入了预处理优化,16:15 写完后发现并不能通过最后两个大样例(3s)。然而并没有想到 $O(n^3)$ 的 Floyd 在这种情况下更快。(写这篇记录的时候还没有正式出成绩,若 CCF 的评测机发力则可能多点分。)
附 [gen.cpp](https://git.vblg.top/hanyixuanten/csps2026/src/commit/e4991898d0b1585aa9d86a0737da057bd388de58/subway/gen.cpp)。注意这个 gen.cpp 生成的图不保证连通,所以如果代码输出了 inf 或是负数一般是图不连通导致,可忽略。
## T3 T4
由于死磕 T2 连暴力都没有写,血亏 20-60pts。
## 总结
1. 对拍、diff.sh 立大功。
2. 由于随机数只会用 time(0) 作为种子,导致对拍效率大大下降,连续拍了半个小时才发现 T1 的问题。
3. 过度的死磕 T2 导致 T3 T4 暴力没写。
4. 对算法复杂度 (dij) 有错误估测,忽视冷门算法 (Floyd),导致 T2 100 -> 68。
附 diff.sh(linux,注意对拍代码均无需开文件读写):
```sh
#!/bin/bash
g++ sol1.cpp -o sol1 -O2 -std=c++14 -static
g++ sol2.cpp -o sol2 -O2 -std=c++14 -static
g++ gen.cpp -o gen -O3 -std=c++14 -static
for i in {1..10000}; do
gen > in
sol1 < in > a
sol2 < in > b
if ! diff -q a b > /dev/null; then
echo wa
cp in wa.in
cp a wa1.out
cp b wa2.out
break
fi
echo success $i
done
```
+47
View File
@@ -0,0 +1,47 @@
# AT_arc073_b [ABC060D] Simple Knapsack 题解
注意到 $W$ 的值很大,若是直接计算会 TLE/MLE/RE。
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
int n; // 100
int w; // 1e9
pair<int, int> obj[105];
unordered_map<int, int> f[105];
int sumw[105];
int main()
{
freopen("satchel.in", "r", stdin);
freopen("satchel.out", "w", stdout);
scanf("%d%d", &n, &w);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &obj[i].first, &obj[i].second);
}
sort(obj + 1, obj + n + 1, [](pii x, pii y)
{ return x.first > y.first; });
for (int i = 1; i <= n; ++i)
sumw[i] = sumw[i - 1] + obj[i].first;
for (int i = 1; i <= n; ++i)
{
for (int j = obj[i].first; j <= min(w, sumw[i]); ++j)
{
f[i][j] = max(f[i - 1][min(j, sumw[i - 1])], f[i - 1][j - obj[i].first] + obj[i].second);
}
}
printf("%d\n", f[n][min(w, sumw[n])]);
return 0;
}
```
令 $base = w_1, d_i = w_i - base$。由题,$0 \le d_i \le 3$。若选择 $k$ 个物品,总重量为 $\sum d_i + k \times base$,其中 $0 \le \sum d_i \le 300$。
考虑用 $f_{i, j}$ 记录选择 $i$ 个物品,$\sum d_i = j$ 的最大价值。对于每个物品:
$$f_{i + 1, j + d_i} = \max(f_{i, j} + v_i)$$
最后枚举 $i, j$,若 $i \times base + j \le W$ 则更新答案。
[code](../AT_arc073_b.cpp)
+23
View File
@@ -0,0 +1,23 @@
# CF1187E Tree Painting 题解
题意:树上 $01$ 染色,一个点染上色获得的分数等于这个点原本所在的连通块的大小。
考虑树形 dp,则 $dp_{fa} = size_{fa} + \sum_{u \in child_{fa}} dp_u$
,其中 $size_{fa}$ 表示以 $fa$ 为根的子树的大小。
注意到总答案和根的选择有关系,朴素做法是直接运行 $n$ 次树形 dp,复杂度为 $O(n^2)$,无法通过。
考虑使用换根 dp,考虑确定以 $fa$ 为根时的答案,如何转移到以 $u (u \in child_{fa})$ 为根的答案。
$$
\begin{aligned}
\because ans_{root} =& size_{root} + \sum_{u \in child_{root}} dp_u \\
\therefore ans_{u} =& (f_u - size_u) + (ans_{root} - f_u - size_u) + n \\
&(u\text{的孩子的贡献}) (root\text{其他孩子的贡献}) \\
=& ans_u - 2\times size_u + n
\end{aligned}
$$
因此,只需要以 $1$ 为根节点计算一次,随后进行答案转移即可。
[CF1187E.cpp](../CF1187E.cpp)
+106
View File
@@ -0,0 +1,106 @@
# CF1380D Berserk And Fireball 题解
> 考场上没有看见 $a_i$ 两两不同,卡了半天。
下文用“$k$ 删除”代表“火球术”,用“单点删除”代表“狂暴术”。
先判断是运行一次 $k$ 删除更优或者运行 $k$ 次单点删除更优。
由于 $a_i$ 两两不同,显然可以使用一个 `map` 保存 $a_i$ 所在的位置 $index_{a_i}$。
对于每对 $b_{i - 1}, b_i$,得到区间 $[index_{b_{i - 1}}, index_{b_i}]$ (其中 $index_0 = 0$),设 $len = index_{b_i} - index_{b_{i - 1}} - 1$。然后分类讨论:
- 若 $k$ 删除更优,可以先运行 $len \% k$ 次单点删除,然后运行 $\lfloor \frac{len}{k} \rfloor$ 次 $k$ 删除。
- 若单点删除更优:
- 若区间内有数 $a_i > \max{(a_{index_{b_{i - 1}}}, a_{index_{b_i}})}$,则必须运行一次 $k$ 删除来移除这个数,因此先运行 $len - k$ 次单点删除,最后运行一次 $k$ 删除。
- 否则,可以直接运行 $len$ 次单点删除。
由此我们就可以得到无解条件:区间内有大于两边的数且区间长度 $< k$,或 $index_{b_{i - 1}} > index_{b_i}$。
注意不要忘了处理最后一个区间。
::::success[code]
```cpp
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
long long x, y;
int a[200005];
int b, preb;
int pos[200005];
bool k_is_better;
vector<int> mor;
long long del(int pre, int todo)
{
if (todo <= pre)
return -1;
if (todo == pre + 1)
return 0;
mor.clear();
for (int j = pre + 1; j <= todo - 1; ++j)
if (a[j] > max(a[todo], a[pre]))
mor.push_back(j);
if (!mor.empty() && (todo - pre - 1) < k) // 如果有大数且长度不足以运行一次 k 则不存在
return -1;
if (k_is_better)
{
return ((todo - pre - 1) % k) * y + ((todo - pre - 1) / k) * x; // 先用 y 移除多余的数,最后用多个 k 直接删完
}
else
{
if (!mor.empty())
return (todo - pre - 1 - k) * y + x; // 用一次k清除最大数,其余用y
else
return (todo - pre - 1) * y; // 全部用y
}
return 0;
}
int main()
{
scanf("%d%d%lld%d%lld", &n, &m, &x, &k, &y);
k_is_better = x < (k * y);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
pos[a[i]] = i;
}
long long ans = 0;
for (int i = 1; i <= m; ++i)
{
scanf("%d", &b);
int todo = pos[b], pre = 0;
if (i != 1)
pre = pos[preb];
long long d = del(pre, todo);
if (d == -1ll)
{
printf("-1\n");
return 0;
}
ans += d;
preb = b;
}
long long d = del(pos[b], n + 1);
if (d == -1ll)
{
printf("-1\n");
return 0;
}
printf("%lld\n", ans + d);
return 0;
}
/*
若 k 删除更优,先删到能被 k 整除的长度,则先 k 删除,最后运行多次 k 删除
len % k
若 1 删除更优,先用 k 删除删去大于两边的,最后用 1 删除
7 2
3 3 1
5 7 0 3 1 6 4
5 4
输出 5
*/
```
::::
+7
View File
@@ -0,0 +1,7 @@
# P1453 城市环路 题解
题意可转化为有一棵树,每个节点有权值,一个边两端的点不能同时去,同时 $S, T$ 也不能同时取(断开环上的两个点)。
因此可转化两次为[P1352 没有上司的舞会](https://www.luogu.com.cn/problem/P1352#),设 $f_{i, 01}$ 以 $i$ 为根的子树,是否选择本节点的收益,只需[以 $S$ 为根时令 $f_{T, 1} = -\inf$,以 $T$ 为根时令 $f_{S, 1} = -\inf$ 即可。](../P1453.cpp#L17-L18)
[code](../P1453.cpp)
+245
View File
@@ -0,0 +1,245 @@
# P17437 「LWLV Round 1」Young 题解
> 根本没有这样的青题……题解区都看不懂……
题意:给定一棵节点带权值的树,断掉两条边,得到三个树,求三个树权值中位数最大是多少。
这里我们令 $1$ 为根,边 $id$ 表示连接 $id$ 与其父节点的边,$sum_i$ 表示以 $i$ 为根的子树权值和。
## 枚举第一刀
先枚举第一条断边 $u$,会将树分为 $u$ 的子树和剩下部分 $A, B$,第二刀只能落在 $A$ 或 $B$ 内部。问题变成:**固定第一刀后,在某一侧内部再切一刀,使三块中位数最大。**
## 命题:对于全局最优的两条切边,总存在其中一条边作为第一刀,使得最优三块的中位数可以由 dmin/dmax 或 umin/umax 四类候选之一达到。
::::error[错误命题]
先反驳一下一个结论:第二刀切出的一定是 $A$ 或 $B$ 的最大或最小子树。
带入数据:
```plaintext
6
0 1 3 2 3 1
1 2
2 3
3 4
4 5
5 6
```
发现第一刀切 $1, 2$ 时,$dmax=9, dmin=1$。但是实际上这种情况下最优解为 $a = 4$ 时,$median = 4$
但是按照这个命题实现的答案却是正确的,原因就是[本命题](#命题对于全局最优的两条切边总存在其中一条边作为第一刀使得最优三块的中位数可以由-dmindmax-或-uminumax-四类候选之一达到)
::::
::::success[证明]
设全局最优中位数为 $y$。任何切法的中位数都不可能超过 $y$,所以只要证明:总有一种候选切法能使中位数达到 $y$。
分两类看最优切法。
**两个切点互不为祖先。**
三块是两棵互不相交的子树和剩下的补集。若中位块是某棵子树,就把这棵子树作为第一刀,另一侧用最小或最大可切值切出另一棵子树,使另外两块分别落在 $y$ 的两边,中位数仍是 $y$。若中位块是补集,就取权值不小于 $y$ 的那棵子树作第一刀,再在另一侧切出剩下的子树,同样能让中位数达到 $y$。
**两个切点有祖先关系。**
三块是祖先外侧、中间夹层、后代子树。若中位块是后代,就取后代边作第一刀,在另一侧切出祖先外侧,用最小或最大可切值把中间夹层推到 $y$ 的另一边。若中位块是祖先外侧,就取祖先边作第一刀,在子树侧切出后代,同理。若中位块是中间夹层,则外侧和后代一个不大于 $y$、一个不小于 $y$,此时无论取祖先边还是后代边作第一刀,再在另一侧取合适的极值,都能让三块中至少两块不小于 $y$、至少两块不大于 $y$,中位数仍为 $y$。
所以,全局最优中位数一定能在“枚举第一刀,另一侧取 $dmin/dmax$ 或 $umin/umax$”这四类候选中出现。最终枚举第一刀并检查四类候选即可。
::::
## 预处理子树最大、最小可切值
若第二刀在 $A$ 内部,则需要用到子树的最大、最小可切值。
记录 $dmax_i, dmin_i$ 表示以 $i$ 为根的子树的能切出的最大/最小权值子树,则结果为以一个儿子节点为根的子树的权值和或者以一个儿子节点为根的子树能切出的最大/最小权值子树。
$$
\begin{aligned}
dmax_i =& \max(\max(s_u, dmax_u), u \in child_i) \\
dmin_i =& \min(\min(s_u, dmin_u), u \in child_i)
\end{aligned}
$$
## 预处理“补集方向”最小、最大可切值
若第二刀切在 $A$ 的补集 $B$ 的方向,则需要“补集方向”的最小、最大可切值。
令 $umax_i, umin_i$ 为补集方向的最大最小可切值。这个要从上往下递推。
根节点补集为空:$umin_1=\inf, umax_1=-\inf$。
对于节点 $i$ 的儿子 $v$,$v$ 的补集由三部分组成。
1. $i$ 的补集。
2. $i$ 与父节点之间的边切出来的那一大块(权值 $s_1 - s_u, u\ne 1$)。
3. $i$ 的其他儿子的子树方向。
$$
\begin{aligned}
umin_v =& \min(umin_u, (s_1 - s_u), \min_{x\in child_u, x\ne v}\min(S_x, dmin_x))\\
umax_v =& \max (umax_u, (s_1 - s_u), \max_{x\in child_u, x\ne v}\max(S_x, dmax_x))
\end{aligned}
$$
### 线性求“其他儿子”的极值(换根dp)
如果对每个儿子 $v$ 都重新扫一遍所有兄弟,复杂度会退化成 $O(n^2)$。
对每个节点 $u$,预处理它所有儿子分支的候选值:
$$
c_x = \min(s_x, dmin_x), C_x = \max(s_x, dmax_x).
$$
记录这些候选的:
- 最小值 $min1$ 与次小值 $min2$,以及最小值来源儿子 $minId$。
- 最大值 $max1$ 与次大值 $max2$,以及最大值来源儿子 $maxId$。
计算儿子 $v$ 时:
- 若 $v\ne minId$,兄弟方向最小候选就是 $min1$,否则用 $min2$。
- 若 $v\ne maxId$,兄弟方向最大候选就是 $max1$,否则用 $max2$。
再与 $umin_u, umax_u$ 以及 $s_1 - s_u$ 合并,就得到 $umin_v, umax_v$。
这样每个节点只扫一遍儿子,整体 $O(n)$。
---
## 最终求答案
先预处理上述的 $umax, umin, dmax, dmin$。
枚举第一刀的位置 $v$,切成 $A, B$ 两块,分两种情况:第二刀在 $A$ 中或第二刀在 $B$ 中。
### 第二刀在 $A$ 内部
第二刀切出的 $a \in \{dmax_v, dmin_v\}$。
三块分别为 $a, A - a, B$,答案为 $median(a, A - a, B)$。
### 第二刀切在 $B$ 内部。
第二刀切出的 $b \in \{umin_v, umax_v\}$
三块分别为 $A, b, B - b$,答案为 $median(A, b, B - b)$
记录最大答案即可。
::::success[code]
```cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n; // 5e5
vector<int> fri[500005];
int fa[500005];
long long sum[500005];
long long dmin[500005], dmax[500005]; // down max / min
long long umin[500005], umax[500005]; // up 反向的 max和min
int ord[500005];
const long long INF = LLONG_MAX;
const long long _INF = LLONG_MIN;
long long median(long long x, long long y, long long z) {
long long a[3] = {x, y, z};
sort(a, a + 3);
return a[1];
}
int noword;
void dfs(int u) {
ord[++noword] = u;
for (int v : fri[u]) {
if (v == fa[u]) continue;
fa[v] = u;
dfs(v);
}
}
void pre() {
noword = 0;
dfs(1);
int cnt = noword;
for (int i = cnt; i >= 1; --i) {
int now = ord[i];
dmin[now] = INF;
dmax[now] = _INF;
for (int v : fri[now]) {
if (v == fa[now]) continue;
sum[now] += sum[v];
if (sum[v] < dmin[now]) dmin[now] = sum[v];
if (sum[v] > dmax[now]) dmax[now] = sum[v];
if (dmin[v] < dmin[now]) dmin[now] = dmin[v];
if (dmax[v] > dmax[now]) dmax[now] = dmax[v];
}
}
umin[1] = INF;
umax[1] = _INF;
for (int i = 1; i <= cnt; ++i) {
int u = ord[i];
long long min1 = INF, min2 = INF;
long long max1 = _INF, max2 = _INF;
int minid = 0, maxid = 0;
for (int v : fri[u]) {
if (v == fa[u]) continue;
long long mn = sum[v];
if (dmin[v] < mn) mn = dmin[v];
long long mx = sum[v];
if (dmax[v] > mx) mx = dmax[v];
if (mn < min1) {
min2 = min1;
min1 = mn;
minid = v;
} else if (mn < min2) {
min2 = mn;
}
if (mx > max1) {
max2 = max1;
max1 = mx;
maxid = v;
} else if (mx > max2) {
max2 = mx;
}
}
long long pmin = INF, pmax = _INF;
if (u != 1) {
pmin = sum[1] - sum[u];
if (umin[u] < pmin) pmin = umin[u];
pmax = sum[1] - sum[u];
if (umax[u] > pmax) pmax = umax[u];
}
for (int v : fri[u]) {
if (v == fa[u]) continue;
long long amin = (v == minid ? min2 : min1);
long long amax = (v == maxid ? max2 : max1);
umin[v] = pmin < amin ? pmin : amin;
umax[v] = pmax > amax ? pmax : amax;
}
}
}
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; ++i)
scanf("%lld", &sum[i]);
for (int i = 1, u, v; i < n; ++i)
scanf("%lld%lld", &u, &v), fri[u].push_back(v), fri[v].push_back(u);
pre();
long long ans = _INF;
for (int v = 2; v <= n; ++v) {
long long B = sum[1] - sum[v];
if (dmin[v] != INF)
ans = max(ans, median(dmin[v], sum[v] - dmin[v], B));
if (dmax[v] != _INF)
ans = max(ans, median(dmax[v], sum[v] - dmax[v], B));
if (umin[v] != INF)
ans = max(ans, median(umin[v], B - umin[v], sum[v]));
if (umax[v] != _INF)
ans = max(ans, median(umax[v], B - umax[v], sum[v]));
}
printf("%lld\n", ans);
return 0;
}
```
::::
+16
View File
@@ -0,0 +1,16 @@
# P2016 [SEERC 2000] 战略游戏 题解
题意:给你一棵树,要求选中一些点,使得每条边至少一个端点被选中。
令 $dp_{i, 01}$ 表示以 $i$ 为根的子树,是否选中该节点的最少选中节点数。易得:
$$
\begin{aligned}
f_{i, 0} =& \sum f_{u, 1} (u \in child_i) \\
f_{i, 1} =& \sum \min(dp_{u, 0}, dp_{u, 1})
\end{aligned}
$$
树形 dp 即可。
[code](../P2016.cpp)
+11
View File
@@ -0,0 +1,11 @@
# P2607 [ZJOI2008] 骑士 题解
先看[P1453](./P1453.md)
只需要将仇恨关系视作一个连边即可。
本题与[P1453](../P1453.md)的区别是形成的不只是一个树+断边,而是一个森林+断边。
只需要将 [P1453 的并查集](../P1453.cpp#L35-L41)改为[保存 $S, T$ vector](../P2607.cpp#L33-L39),随后对于每个树+断边仿照 P1453 的实现即可
[code](../P2607.cpp)
+16
View File
@@ -0,0 +1,16 @@
# P3205 [HNOI2010] 合唱队 题解
题意插入 $n$ 个数,对于每个数,若大于上一个则放右边,否则放左边。求有多少种插入序列,使得最后排列与给定序列相同。
考虑区间 dp,设 $f_{i, j}$ 表示区间 $[l, r]$ 且最后一次插入从左边的方案数,$g_{i, j}$ 表示区间 $[l, r]$ 且最后一次插入从右边的方案数。则:
$$
\begin{aligned}
f_{i, j} =& f_{l + 1, r} * (a_l < a_{l + 1}) + g_{l + 1, r} * (a_l < a_r) \\
g_{i, j} =& f_{l, r - 1} * (a_l < a_r) + g_{l, r - 1} * (a_{r - 1} < a_r)
\end{aligned}
$$
转移即可。注意 $f_{i, i}, g_{i, i}$ 只有一个能够设为 $1$,否则会重复计算。
[code](../P3205.cpp)
+16
View File
@@ -0,0 +1,16 @@
# P3384 【模板】重链剖分 / 树链剖分 题解
重链剖分模板。习惯用线段树(也可以双 BIT)。
其中:
- [L13-48](../P3384.cpp#L13-L48) 为维护区间和的线段树。
- [L49-59](../P3384.cpp#L49-L59) 为 dfs1,处理 $dep, cnt, fa, son$。
- [L60-71](../P3384.cpp#L60-L71) 为 dfs2,处理 $dfn, top$。
- [L72-80](../P3384.cpp#L72-L80) 为路径加,上跳即可。
- [L81-90](../P3384.cpp#L81-L90) 为路径查询,与修改是类似的。
- [L91-96](../P3384.cpp#L91-L96) 为子树修改与查询,由于子树的 $dfn$ 序列是连续的,直接在线段树上修改即可。
接下来建议用树链剖分解决一些[简单题](https://www.luogu.com.cn/problem/list?type=luogu&page=1&tag=228&orderBy=difficulty&order=asc)。
[code](../P3384.cpp)
+13
View File
@@ -0,0 +1,13 @@
# P3572 [POI 2014] PTA-Little Bird 题解
$n$ 棵有高度的树。有鸟要从第 $1$ 棵树到第 $n$ 棵树。当第 $i$ 只鸟每次最多向前 $k$ 棵树。如果一只鸟飞到一颗高度大于等于当前树的树,那么它的劳累值会增加 $1$,否则不会。最小化劳累值。
朴素做法为设 $f_x$ 为这只鸟飞到 $x$ 的最小劳累值。则:
$$f_x = \min(f_i + (a_x >= a_i)), i \in [x - k, x - 1]$$
但这样是 $O(n^2q)$ 的,无法通过。
注意到 $f_x$ 只会由 $f_i, i \in [x - k, x - 1]$ 转移得到,且不会选择其中值过大的。考虑使用单调队列维护滑动窗口,记录可能产生贡献的值。
[code](../P3572.cpp)
+17
View File
@@ -0,0 +1,17 @@
# P4211 [LNOI2014] LCA 题解
给定一棵树,对于每个给定的 $l, r, z$,求:
$$\sum_{i = l}^r dep[\operatorname{LCA}(i, z)]$$
考虑转化。$dep[\operatorname{LCA}(x, y)]$ 表示从根节点到 $\operatorname{LCA}(x, y)$ 节点经过的节点数,其相当于对于根节点到 $x$ 的路径上每个点都 $+ 1$,求从根节点到 $y$ 的路径上所有节点的权值和。
因此,考虑使用树链剖分,对于每次操作,对 $root \rightarrow i, i \in [l, r]$ 的路径上每个点权值 $+ 1$,并求 $root \rightarrow z$ 的路径上所有节点的权值和。
然后,很自然地,可以分别计算 $l - 1, r$ 时的答案,并维护差分求解。
但是,若是每次操作都清除线段树,显然有点浪费。考虑将询问离线为 $2 \times m$ 次 $[1, l - 1 \text{or } r]$ 时的查询,按照右端点排序求解。
总时间复杂度 $\operatorname O(n \log^2 n)$
[code](../P4211.cpp)
+9
View File
@@ -0,0 +1,9 @@
# P4342 [IOI 1998] Polygon 题解
题意:给定一个环,节点上有权值,边上有运算符 $\times$ 或 $+$,断掉一条边,按一定顺序合并相邻两个点,求最大结果以及能够达到最大结果的第一次断边选择。
显然第一步要破环成链,将序列复制两遍,开一个大小为 $n$ 的滑动窗口来枚举断边。
显然要区间 dp。最初想到用 $f_{l, r}$ 表示 $[l, r]$ 最大值,但是发现负数 $\times$ 负数可能成为新的最大值。因此考虑同时记录 $g_{l, r}$ 表示 $[l, r]$ 的最小值。随后分类讨论转移即可。
[code](../P4342.cpp)
+77
View File
@@ -0,0 +1,77 @@
# P7458 [CERC2018] Clockwork ||ange 题解
## 二进制表示
我们只关心每个畜栏是否非空,因此可以用一个二进制数表示整个畜栏状态。
一次搬迁选择正整数 $K$,在二进制表示中,“右边第 $K$ 个畜栏”正好对应右移 $K$ 位。因此一次操作可以写成 $newa = nowa \mid (nowa \gg K)$。其中:
- $nowa$ 保留原来的非空畜栏。
- $nowa \gg K$ 把兔子移动到右边第 $K$ 个畜栏。
- 按位或表示合并新旧状态。
如果某个畜栏离最后一个畜栏太近,右移后越界,越界部分自然消失;而原畜栏仍然保留,所以正好符合题目“太靠右则全部留在原畜栏”的规则。
## DFS 搜索
设当前状态为 $nowa$,已经进行了 $now$ 次搬迁,上一次选择的 $K$ 为 $pre_k$。
由于每次操作都是“并上右移后的结果”,操作顺序实际上可以交换。因此我们可以限制选择的 $K$ 非降,避免重复枚举同一种操作序列。
其中 $newa > nowa$ 用来剪掉没有产生任何新非空畜栏的操作。因为所有可达状态都包含初始状态 $a$,所以 $newa > nowa$ 等价于“至少新增了一个初始为空的畜栏”。如果一次操作没有新增任何畜栏,它不会帮助达到目标,可以跳过。
如果:
```cpp
now_a == (1ll << n) - 1
```
说明所有 $n$ 个畜栏都非空,更新答案。
## 为什么最多只需要 6 次
如果第一个畜栏有兔子,那么可以依次选择 $K = 1, 2, 4, 8, 16, 32$。
不难发现,只要第一个畜栏有兔子,六次操作一定可以填满。
## 参考代码
::::success[code]
```cpp
#include<bits/stdc++.h>
using namespace std;
long long a;
int minans = INT_MAX;
int n;
void dfs(int now, int pre_k, long long now_a) {
if(now > 6 || now >= minans) {
return;
}
if(now_a == (1ll << n) - 1) {
minans = min(minans, now);
}
for(int k = pre_k; k <= n; ++k) {
long long new_a = now_a | (now_a >> k);
if(new_a > now_a) dfs(now + 1, k, new_a);
}
}
int main() {
char s[45];
scanf("%s", s + 1);
if(s[1] == '0') {
printf("-1\n");
return 0;
}
n = strlen(s + 1);
for(int i = 1; i <= n; ++i) {
a *= 2;
if(s[i] == '1') a += 1;
}
dfs(0, 1, a);
printf("%d\n", minans);
return 0;
}
```
::::
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more