2018冬令营模拟测试赛(八)
[Problem A]Rpg
试题描述
这个RPG采用回合战斗,怪物只有一种,但是个数无限多,小X初始攻击力为 \(1\),防御力为 \(0\).
小X的生命也是足够多。每消灭一次怪物,小X可以得到一个金币,这个金币可以增加 \(1\) 攻击或 \(1\) 防御,
回合规则如下:小X攻击一次怪物,然后怪物攻击小X,伤害为对方的攻击减去己方的防御,如果这个值小于零,则不造成伤害。当怪物生命为 \(0\) 时,战斗结束。
你很容易理解,小X总有一个时候会变得无敌,但是小X想知道在变无敌之前至少承受多少伤害。
输入
一行两个整数,表示怪物的攻击 \(n\) 和生命 \(k\),怪物的防御为零。
输出
一个整数,表示小X最低承受伤害。答案可能会超过 long long,但一定在 \(50\) 位数之内
输入示例1
16 21
输出示例1
1032
输入示例2
35 66
输出示例2
9275
数据规模及约定
\(100\%\) \(n,k \le 10^{13}\)
题解
可以证明一定是先升攻再升防。因为升攻的话每杀死一个怪需要受到的伤害呈双曲线,而升防则呈直线;前者下降速度先快后满,后者下降速度保持不变;所以一定存在一个分界点使得前者下降速度恰好从比后者快变成比后者慢。
那么现在考虑枚举这个转折点,然后计算答案。
将攻击力升级到 \(t\),后面专心升级防御需要消耗的血量是(令 \(att\) 为怪兽的攻击力,\(blood\) 为怪兽血量):\(\sum_{i=1}^{t-1} { att \cdot \lceil \frac{blood - i}{i} \rceil } + \frac{(att+1)att}{2} \cdot \lfloor \frac{blood - 1}{t} \rfloor\),注意是小X先攻击,所以是 \(\lceil \frac{blood - i}{i} \rceil\),然后发现这个玩意等于 \(\lfloor \frac{blood - i + i -1}{i} \rfloor = \lfloor \frac{blood - 1}{i} \rfloor\)。
于是整个式子变成了我们熟悉的样子:
\[ \sum_{i=1}^{t-1} { att \cdot \lfloor \frac{blood - 1}{i} \rfloor } + \frac{(att+1)att}{2} \cdot \lfloor \frac{blood - 1}{t} \rfloor \]
左边所有的除法下取整的取值只有 \(O(\sqrt{blood})\) 种,并且可以发现如果增加攻击力无法导致被攻击次数减少,我们就没必要增加攻击力。所以我们只用枚举这 \(O(\sqrt{blood})\) 种转折点,暴力算答案就好啦。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
#define LL __int128
LL read() {
LL x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
int num[55], cntn;
void puti128(LL x) {
cntn = 0;
while(x > 0) num[++cntn] = x % 10, x /= 10;
dwn(i, cntn, 1) putchar(num[i] + '0'); putchar('\n');
return ;
}
int main() {
LL k = read(), n = read(); // n means blood, k means attack
LL att = 0, def = 0, ans = -1;
for(LL t = 1; ;) {
def = (n - 1) / t * ((k + 1) * k >> 1);
if(ans < 0) ans = att + def;
else ans = min(ans, att + def);
if(t == n) break;
LL lst = (n - 1) / ((n - 1) / t);
att += (n - 1) / t * k * (lst - t + 1);
t = min(lst + 1, n);
}
puti128(ans);
return 0;
}
[Problem B]字母树
试题描述
有一棵树,一共 \(n\) 个点,\(n-1\) 条边,每一条边上都有一个权值,这些权值都是小写字母。这样每一条路都可以产生一个系母序列。一共有 \(q\) 次询问,每一次询问为 \(a\ b\),表示从 \(a\) 出发的所有路径中,有几条路产生的字母序列的字典序比 \(a\) 到 \(b\) 的字典序小。
输入
第一行两个正整数 \(n\),\(q\) 表示树的大小和询问的次数。
接下来 \(n-1\) 行,每行两个正整数 \(x\ y\) 和一个字母 \(v\),表示每一条边。
接下来 \(q\) 行,每行两个正整数 \(x\ y\) 表示询问的结果。
输出
\(q\) 行,表示每一次询问的答案。
输入示例1
4 3
4 1 t
3 2 p
1 2 s
3 2
1 3
2 1
输出示例1
0
1
1
输入示例2
8 4
4 6 p
3 7 o
7 8 p
4 5 d
1 3 o
4 3 p
3 2 e
8 6
3 7
8 1
4 3
输出示例2
6
1
3
1
数据规模及约定
\(n \le 4000, q \le 500000\)
题解
一看 \(n\) 这么小,那就枚举所有的根,预处理出所有的答案嘛。以某个节点为根,边递归边建 trie 树(为的是让同一个节点连出去的字母相同的边合并),并在 trie 树上每个节点开一个 vector 记录它对应原树的哪些节点。然后我们遍历一遍这棵 trie 树,再往下递归之前把这个节点 vector 的 size 加上,然后就能搞出所有的答案了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 4010
#define maxc 26
#define pci pair <char, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
char str[5];
int n;
vector <pci> to[maxn];
int ToT, rt, ch[maxn][maxc];
vector <int> tag[maxn];
void build(int now, int u, int fa) {
rep(i, 0, (int)to[u].size() - 1) if(to[u][i].y != fa) {
int x = to[u][i].x - 'a';
if(!ch[now][x]) ch[now][x] = ++ToT;
tag[ch[now][x]].push_back(to[u][i].y);
build(ch[now][x], to[u][i].y, u);
}
return ;
}
int cnt, tot[maxn][maxn];
void getans(int st, int u) {
rep(i, 0, (int)tag[u].size() - 1) tot[st][tag[u][i]] = cnt;
if(u != rt) cnt += tag[u].size();
rep(i, 0, maxc - 1) if(ch[u][i]) getans(st, ch[u][i]);
return ;
}
int main() {
n = read(); int q = read();
rep(i, 1, n - 1) {
int a = read(), b = read(); scanf("%s", str);
to[a].push_back(mp(str[0], b)); to[b].push_back(mp(str[0], a));
}
rep(i, 1, n) sort(to[i].begin(), to[i].end());
rep(i, 1, n) {
rep(j, 1, ToT) tag[j].clear();
ToT = 0; memset(ch, 0, sizeof(ch));
tag[rt = ++ToT].push_back(i);
build(rt, i, 0);
cnt = 0; getans(i, rt);
}
while(q--) {
int x = read(), y = read();
printf("%d\n", tot[x][y]);
}
return 0;
}
[Problem C]数字操作
试题描述
变量 \(a\) 的初始数字为零,你需要进行一系列操作,使变量 \(a\) 的值变为目标值
这些操作仅涉及到增加或减少,但是每一次增加或减少的值只能使用提供的值
输入
第一行一个数 \(n\) 表示目标值
第二行一个正整数 \(m\),表示提供的值的个数
接下来 \(m\) 行,每行一个正整数,表示提供的值 \(b[x]\)
输出
第一行一个正整数 \(n\),表示操作的个数
接下来 \(n\) 行,用 \(x\) 或者 \(-x\) 来表示
其中 \(x\) 表示提供值的位置
如果为 \(x\),表示将 \(a\) 加了 \(b[x]\),如果为 \(-x\),表示将 \(a\) 减少 \(b[x]\)
输入示例
9
3
1
21
37
输出示例
7
2
2
-3
2
2
-3
-1
数据规模及约定
给定score0.log
~score9.log
表示评分参数。
每一个评分参数为十行,对应十个变量,表示你操作次数小于等于这些数时可以得到相应分数
满足第一个数得 \(1\) 分,满足第二个得 \(2\) 分,以此类推如果多个满足,取最大值。
题解
这题后面几个点实在丧,搞不出来了 QAQ。
其中有三四个点可以直接 bfs 出来,有些需要加一些技巧如将所有数字两两相减然后再 bfs。
对于一个给出来的操作都是 \(2\) 的整数次幂的点可以数位 dp 一下记录当前位与进位。
这样大概前 \(7(0 \sim 6)\) 个点就搞定了吧……
后面太丧病了,随机化、贪心乱搞?
// 略(QwQ)