2021CCPC四川省赛题解ABDEFHIJKLM
A. Chuanpai
题意
每张牌上有两个数字 x , y ( 1 ≤ x ≤ y ≤ 6 ) x,y\ \ (1\leq x\leq y\leq 6) x,y (1≤x≤y≤6),两张牌 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)不同当且仅当 x 1 ≠ y 1 x_1\neq y_1 x1=y1或 x 2 ≠ y 2 x_2\neq y_2 x2=y2.
有 t ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t (1≤t≤100)足测试数据.每组测试数据输入一个整数 k ( 1 ≤ k ≤ 100 ) k\ \ (1\leq k\leq 100) k (1≤k≤100),求有多少种不同的牌满足 x + y = k x+y=k x+y=k.
代码I -> 2021CCPC四川省赛题解-A(打表)
map<int, int> ans = { {1,0},{2,1},{3,1},{4,2},{5,2},{6,3},{7,3},{8,3},{9,2},{10,2},{11,1},{12,1},{13,0} };
void solve() {
int k; cin >> k;
cout << ans[min(13, k)] << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
代码II -> 2021CCPC四川省赛题解-A(暴力)
void solve() {
int k; cin >> k;
if (k > 12) {
cout << 0 << endl;
return;
}
int ans = 0;
for (int i = 1; i <= 6; i++) {
for (int j = i; j <= 6; j++) {
if (i + j > k) break; // 优化
ans += i + j == k;
}
}
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
代码III -> 2021CCPC四川省赛题解-A(暴力+优化)
void solve() {
int k; cin >> k;
if (k > 12) {
cout << 0 << endl;
return;
}
int ans = 0;
for (int i = 1; i <= k / 2; i++) ans += k - i <= 6;
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
K. K-skip Permutation
题意
对 1 ∼ n 1\sim n 1∼n的一个排列 P = p 1 p 2 ⋯ p n P=p_1p_2\cdots p_n P=p1p2⋯pn,定义 f ( P , k ) f(P,k) f(P,k)为使得 p i + k = p i + 1 p_i+k=p_{i+1} pi+k=pi+1的 i ∈ [ 1 , n ) i\in [1,n) i∈[1,n)的个数.给定整数 n , k ( 1 ≤ n , k ≤ 1 e 6 ) n,k\ \ (1\leq n,k\leq 1\mathrm{e}6) n,k (1≤n,k≤1e6),构造一个 1 ∼ n 1\sim n 1∼n的排列使得 f ( P , k ) f(P,k) f(P,k)最大.
思路
1 , 1 + k , 1 + 2 k , ⋯ , 2 , 2 + k , 2 + 2 k , ⋯ , 3 , 3 + k , 3 + 2 k , ⋯ 1,1+k,1+2k,\cdots,2,2+k,2+2k,\cdots,3,3+k,3+2k,\cdots 1,1+k,1+2k,⋯,2,2+k,2+2k,⋯,3,3+k,3+2k,⋯.
代码 -> 2021CCPC四川省赛题解-K(思维)
void solve() {
int n, k; cin >> n >> k;
set<int> s;
for (int i = 1; i <= n; i++) s.insert(i);
string ans;
while (s.size()) {
int a = *s.begin(); s.erase(a);
ans += to_string(a) + " ";
while (s.count(a + k)) {
ans += to_string(a + k) + " ";
s.erase(a + k);
a += k;
}
}
cout << ans.substr(0, ans.length() - 1);
}
int main() {
solve();
}
D. Rock Paper Scissors
题意
A和B玩游戏.有三种类型的牌:石头、布、剪刀.初始时每个玩家有 n n n张牌,游戏有 n n n轮,A先打出一张牌,展示给B,然后B再打出一张牌.每一轮的计分方式如下表,打出的牌不能再回收.最后总得分等于每轮得分相加.
B ↓ \downarrow ↓ A → \rightarrow → | 石头 | 布 | 剪刀 |
---|---|---|---|
石头 | 0 0 0 | − 1 -1 −1 | 1 1 1 |
布 | 1 1 1 | 0 0 0 | − 1 -1 −1 |
剪刀 | − 1 -1 −1 | 1 1 1 | 0 0 0 |
A想最小化得分,B想最大化得分,两人都采取最优策略,求最后得分.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据第一行输入三个整数 b r , b p , b s ( 0 ≤ b r , b p , b s ≤ 1 e 9 ) b_r,b_p,b_s\ \ (0\leq b_r,b_p,b_s\leq 1\mathrm{e}9) br,bp,bs (0≤br,bp,bs≤1e9),分别表示A手中的石头、布、剪刀牌数.第二行输入三个整数 d r , d p , d s ( 0 ≤ d r , d p , d s ≤ 1 e 9 ) d_r,d_p,d_s\ \ (0\leq d_r,d_p,d_s\leq 1\mathrm{e}9) dr,dp,ds (0≤dr,dp,ds≤1e9),分别表示B手中的石头、布、剪刀牌数.数据保证 b r + b p + b s = d r + d p + d s b_r+b_p+b_s=d_r+d_p+d_s br+bp+bs=dr+dp+ds.
思路
A出石头,B尽量出布, a n s + + ans++ ans++.若A还有石头,B尽量出石头, a n s ans ans不变.若A还有石头,B只能出剪刀, a n s − − ans-- ans−−.
代码 -> 2021CCPC四川省赛题解-D(贪心+模拟)
void solve() {
int br, bp, bs, dr, dp, ds; cin >> br >> bp >> bs >> dr >> dp >> ds;
ll ans = 0;
int tmp;
tmp = min(br, dp); ans += tmp; br -= tmp, dp -= tmp;
tmp = min(bp, ds); ans += tmp; bp -= tmp, ds -= tmp;
tmp = min(bs, dr); ans += tmp; bs -= tmp, dr -= tmp;
if (br) {
br -= min(br, dr);
ans -= br;
}
if (bp) {
bp -= min(bp, dp);
ans -= bp;
}
if (bs) {
bs -= min(bs, ds);
ans -= bs;
}
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
H. Nihongo wa Muzukashii Desu
题意
给定 t ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t (1≤t≤100)个长度不超过 30 30 30的单词的masu形式,按上述规则将其转化为te形式.数据保证不出现以"imasu"结尾的单词.
思路
易想到用map存下所有的替换规则,但map和unordered_map都可能改变其中元素的顺序,可能出现以"mimasu"结尾的优先被判为"imasu"结尾的情况,故改用vector<pair<string, string>>存,并将"imasu"的替换规则放在最后.
代码 -> 2021CCPC四川省赛题解-H(模拟)
vector<pair<string, string>> mp = {
{"shimasu","shite"},{"chimasu","tte"},{"rimasu","tte"},
{"mimasu","nde"},{"bimasu","nde"},{"nimasu","nde"},
{"kimasu","ite"},{"gimasu","ide"},{"imasu","tte"} }; // 注意imasu放最后
void solve() {
string str; cin >> str;
if (str == "ikimasu") { // 注意特判
cout << "itte" << endl;
return;
}
for (auto s : mp) {
int tmp = str.length() - s.first.length();
if (tmp <= 0) continue;
if (str.substr(tmp) == s.first) {
cout << str.substr(0, str.length() - s.first.length()) + s.second << endl;
break;
}
}
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
B. Hotpot
题意
编号 0 ∼ ( n − 1 ) 0\sim (n-1) 0∼(n−1)的 n n n个人依次围在火锅旁,每个人有一个喜悦值,初始时为 0 0 0.锅中共有 k k k种原料,其中第 i ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i (0≤i≤n−1)个人喜欢原料 a i a_i ai,初始时锅中无原料.从 0 0 0号人开始,每个人轮流做 m m m次操作,轮到第 i i i个人时,若锅中有他喜欢的原料,他会吃一个并增加 1 1 1点喜悦值;否则他会加入一个他喜欢的原料 a i a_i ai.求最终每个人的喜悦值.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据第一行输入三个整数 n , k , m ( 1 ≤ n , k ≤ 1 e 5 , 1 ≤ m ≤ 1 e 9 ) n,k,m\ \ (1\leq n,k\leq 1\mathrm{e}5,1\leq m\leq 1\mathrm{e}9) n,k,m (1≤n,k≤1e5,1≤m≤1e9).第二行输入 n n n个整数 a 0 , ⋯ , a n − 1 ( 1 ≤ a i ≤ k ) a_0,\cdots,a_{n-1}\ \ (1\leq a_i\leq k) a0,⋯,an−1 (1≤ai≤k).数据保证所有测试数据的 n n n之和、 k k k之和都不超过 2 e 5 2\mathrm{e}5 2e5.
思路
显然 n n n次操作是一个周期.
考察人数 n n n的奇偶性对结果的影响,不妨设 n n n个人都喜欢原料 1 1 1.
① n n n为偶数时,不妨设 n = 2 n=2 n=2.第一次操作时锅中无原料 1 1 1,则 0 0 0号人加入一个原料 1 1 1.第二次操作时 1 1 1号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.显然 0 0 0号人每次操作都是加入原料 1 1 1, 1 1 1号人每次操作都是吃一个原料 1 1 1.
② n n n为奇数,不妨设 n = 3 n=3 n=3.第一次操作时锅中无原料 1 1 1,则 0 0 0号人加入一个原料 1 1 1.第二次操作时 1 1 1号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.第三次操作时锅中无原料 1 1 1,则 2 2 2号人加入一个原料 1 1 1.第四次操作时 0 0 0号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.第五次操作时锅中无原料 1 1 1,则 1 1 1号人加入一个原料 1 1 1.第六次操作时 2 2 2号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.
n n n的奇偶性对结果有影响,而 n n n为偶数时每个人最终的喜悦值易求出,不妨取 2 n 2n 2n为周期,统一为 n n n是偶数的情况.
对 m m m次操作,先做 ⌊ m 2 n ⌋ \left\lfloor\dfrac{m}{2n}\right\rfloor ⌊2nm⌋次完整周期,再做 m % ( 2 n ) m\% (2n) m%(2n)次剩下的操作.一个周期和剩下的操作直接暴力模拟即可.
代码 -> 2021CCPC四川省赛题解-B(思维+模拟)
void solve() {
int n, k, m; cin >> n >> k >> m;
vii a(n, { 0,0 }); // first为喜欢吃的食物,second为喜悦值
for (int i = 0; i < n; i++) cin >> a[i].first;
vi food(k + 1); // 当前锅中每种食物的数量
int round = m / (2 * n); // 完整周期数
if (round) {
for (int i = 0; i < 2 * n; i++) {
int j = i % n; // 当前轮到的人
if (food[a[j].first]) food[a[j].first]--, a[j].second++;
else food[a[j].first]++;
}
for (int i = 0; i < n; i++) a[i].second *= round;
}
for (int i = 0; i < m % (2 * n); i++) { // 剩下的操作
int j = i % n; // 当前轮到的人
if (food[a[j].first]) food[a[j].first]--, a[j].second++;
else food[a[j].first]++;
}
for (int i = 0; i < n; i++) cout << a[i].second << " \n"[i == n - 1];
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
L. Spicy Restaurant
题意 ( 2 s 2\ \mathrm{s} 2 s)
有编号 1 ∼ n 1\sim n 1∼n的 n n n个餐厅,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个餐厅的火锅辣度为 w i w_i wi.每个餐厅可视为包含 m m m条边的无向图上的一个节点,每条边的权值都为 1 1 1.现有 q q q个人,每个人有一个能接受的最大辣度.对每个人,求他到一个他能接受的辣度的餐厅的最短距离.
第一行输入三个整数 n , m , q ( 1 ≤ n , m ≤ 1 e 5 , 1 ≤ q ≤ 5 e 5 ) n,m,q\ \ (1\leq n,m\leq 1\mathrm{e}5,1\leq q\leq 5\mathrm{e}5) n,m,q (1≤n,m≤1e5,1≤q≤5e5).第二行输入 n n n个整数 w 1 , ⋯ , w n ( 1 ≤ w i ≤ 100 ) w_1,\cdots,w_n\ \ (1\leq w_i\leq 100) w1,⋯,wn (1≤wi≤100).接下来 m m m行每行输入两个相异的整数 u , v ( 1 ≤ u , v ≤ n ) u,v\ \ (1\leq u,v\leq n) u,v (1≤u,v≤n),表示节点 u u u与 v v v间存在边.接下来 q q q行每行输入两个整数 p , a ( 1 ≤ p ≤ n , 1 ≤ a ≤ n ) p,a\ \ (1\leq p\leq n,1\leq a\leq n) p,a (1≤p≤n,1≤a≤n),分别表示一个人所在的节点编号和他能接受的最大辣度.
对每个人,输出其到他能接受的辣度的餐厅的最短距离,若不存在这样的餐厅,输出 − 1 -1 −1.
思路
d i s [ i ] [ j ] dis[i][j] dis[i][j]表示节点 j j j到辣度不超过 i i i的节点的最短距离.因 i ≤ 100 i\leq 100 i≤100,用 100 100 100次BFS预处理即可.
代码 -> 2021CCPC四川省赛题解-L(思维+BFS)
const int MAXN = 1e5 + 5, MAXM = 105;
int n, m, q; // 节点数、边数、询问数
int w[MAXN];
int dis[MAXM][MAXN]; // dis[i][j]表示节点j到辣度不超过i的节点的最短距离
vi edges[MAXN];
void bfs(int a) { // 辣度a
qi que;
for (int i = 1; i <= n; i++) { // 辣度不超过a的节点入队,并初始化dis[a][]
if (w[i] <= a) {
que.push(i);
dis[a][i] = 0;
}
}
while (que.size()) {
int u = que.front(); que.pop();
for (auto v : edges[u]) {
if (dis[a][v] > dis[a][u] + 1) {
dis[a][v] = dis[a][u] + 1;
que.push(v);
}
}
}
}
void solve() {
memset(dis, INF, so(dis));
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) cin >> w[i];
while (m--) {
int u, v; cin >> u >> v;
edges[u].push_back(v), edges[v].push_back(u);
}
for (int i = 1; i <= 100; i++) bfs(i); // 预处理dis[i][]
while (q--) {
int p, a; cin >> p >> a;
cout << (dis[a][p] == INF ? -1 : dis[a][p]) << endl;
}
}
int main() {
solve();
}
M. True Story
题意
编号 1 ∼ n 1\sim n 1∼n的 n n n个人要赶不上飞机了.在 0 0 0时初,他们距机场 x k m x\ \mathrm{km} x km,登机时间为 p 0 p_0 p0时初.第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个人的速度为 s i k m / h s_i\ \mathrm{km/h} si km/h,他们在不晚于登机时间赶到机场.现登机时间会推迟 k k k次,其中第 i ( 1 ≤ i ≤ k ) i\ \ (1\leq i\leq k) i (1≤i≤k)次推迟会在 t i t_i ti时初发布,并将登机时间推迟到 p i p_i pi时初.每次推迟发布时每个人会立即收到消息,且每个人只会在推迟发布时才能知道消息,不能提前预判.每个人只会在自己能赶上飞机的前提下才会移动,若他发现此时已赶不上飞机,他会停在原地;否则他会从上次停下的地方继续移动.求最后能赶上飞机的人数.
第一行输入四个整数 n , k , x , p 0 ( 1 ≤ n , k ≤ 1 e 5 , 1 ≤ x , p 0 ≤ 1 e 9 ) n,k,x,p_0\ \ (1\leq n,k\leq 1\mathrm{e}5,1\leq x,p_0\leq 1\mathrm{e}9) n,k,x,p0 (1≤n,k≤1e5,1≤x,p0≤1e9).第二行输入 n n n个整数 s 1 , ⋯ , s n ( 1 ≤ s i ≤ 1 e 9 ) s_1,\cdots,s_n\ \ (1\leq s_i\leq 1\mathrm{e}9) s1,⋯,sn (1≤si≤1e9).第三行输入 k k k个整数 t 1 , ⋯ , t k ( 1 ≤ t i ≤ 1 e 9 , t i < t i + 1 , t i < p i − 1 ) t_1,\cdots,t_k\ \ (1\leq t_i\leq 1\mathrm{e}9,t_i<t_{i+1},t_i<p_{i-1}) t1,⋯,tk (1≤ti≤1e9,ti<ti+1,ti<pi−1).第四行输入 k k k个整数 p 1 , ⋯ , p k ( 1 ≤ p i ≤ 1 e 9 , p i < p i + 1 ) p_1,\cdots,p_k\ \ (1\leq p_i\leq 1\mathrm{e}9,p_i<p_{i+1}) p1,⋯,pk (1≤pi≤1e9,pi<pi+1).
思路I
最坏的情况是初始时每个人都赶不上,直至某次推迟后才发现自己能赶上.只需考虑最大间隔 Δ t = max 1 ≤ i ≤ k ( p i − t i ) \displaystyle\Delta t=\max_{1\leq i\leq k}(p_i-t_i) Δt=1≤i≤kmax(pi−ti)的时间内能否赶上即可.
代码I -> 2021CCPC四川省赛题解-M(思维I)
void solve() {
int n, k, x, p0; cin >> n >> k >> x >> p0;
vi s(max(n, k) + 1), t(max(n, k) + 1); // 速度、推迟发布时间
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = 1; i <= k; i++) cin >> t[i];
int maxtime = 0;
for (int i = 1; i <= k; i++) {
int p; cin >> p;
maxtime = max(maxtime, p - t[i]);
}
int ans = 0;
for (int i = 1; i <= n; i++) ans += (ll)s[i] * maxtime >= x;
cout << ans;
}
int main() {
solve();
}
思路II
注意到最终能赶上的人数 = = =原来能赶上的人数 + + +每次延迟后能赶上的人数,且一开始能赶上的人推迟后也能赶上,将所有人按速度降序排列后每次检查速度最大的能能否赶上即可.
代码II -> 2021CCPC四川省赛题解-M(思维II)
void solve() {
int n, k, x, p0; cin >> n >> k >> x >> p0;
vi s(max(n, k) + 1), t(max(n, k) + 1), p(max(n, k) + 1); // 速度、推迟发布时间、登机时间
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = 1; i <= k; i++) cin >> t[i];
for (int i = 1; i <= k; i++) cin >> p[i];
sort(s.begin() + 1, s.end(), greater<int>());
int idx = 1;
while (idx <= n) { // 求原来能赶上的人数
if ((ll)s[idx] * p0 >= x) idx++;
else break;
}
for (int i = 1; i <= k; i++) // 求推迟后能赶上的人数
while (idx <= n && (ll)s[idx] * (p[i] - t[i]) >= x) idx++;
cout << idx - 1; // 注意-1
}
int main() {
solve();
}
E. Don’t Really Like How The Story Ends
题意
给定一张包含 n n n个节点、 m m m条边的无向图,问至少添加多少条边才能使得从节点 1 1 1开始的DFS序为 1 ∼ n 1\sim n 1∼n.
有 t t t组测试数据.每组测试数据第一行输入两个整数 n , m ( 1 ≤ n , m ≤ 1 e 5 ) n,m\ \ (1\leq n,m\leq 1\mathrm{e}5) n,m (1≤n,m≤1e5).接下来 m m m行每行输入两个整数 u , v ( 1 ≤ u , v ≤ n ) u,v\ \ (1\leq u,v\leq n) u,v (1≤u,v≤n),表示节点 u u u与 v v v间存在边.数据保证所有测试数据的 ( n + m ) (n+m) (n+m)之和不超过 1 e 6 1\mathrm{e}6 1e6.
思路
从节点 1 1 1开始搜索,若节点 1 1 1与节点 2 2 2间存在边,则继续搜索节点 2 2 2.
①若节点 2 2 2与节点 3 3 3间存在边,继续搜索节点 3 3 3.
②若节点 2 2 2与节点 3 3 3间不存在边,且节点 2 2 2不与节点 u ( u > 3 ) u\ \ (u>3) u (u>3)相连,则节点 2 2 2已无搜索价值,直接回溯即可.
③若节点 2 2 2与节点 3 3 3间不存在边,但节点 2 2 2与节点 u ( u > 3 ) u\ \ (u>3) u (u>3)相连,则需添加一条边 < 2 , 3 > <2,3> <2,3>.
DFS模拟上述过程即可.
注意连一条节点 1 1 1到节点 ( n + 1 ) (n+1) (n+1)的边作为哨兵,否则若 1 1 1是孤立点则无法往下搜.
代码 -> 2021CCPC四川省赛题解-E(思维+DFS)
const int MAXN = 1e5 + 5;
int n, m;
set<int> edges[MAXN];
int nxt; // 下一个要搜索的节点
int ans;
void dfs(int u) {
if (u == n + 1) return; // 遍历完
for (auto v : edges[u]) {
if (v < nxt) continue; // 遍历过
while (v >= nxt) {
ans += v > nxt;
dfs(nxt++);
}
}
}
void solve() {
cin >> n >> m;
ans = 0, nxt = 2;
for (int i = 1; i <= n; i++) edges[i].clear();
while (m--) {
int u, v; cin >> u >> v;
edges[u].insert(v), edges[v].insert(u);
}
edges[1].insert(n + 1); // 哨兵
dfs(1);
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
J. Ants
题意
编号 1 ∼ n 1\sim n 1∼n的 n n n只蚂蚁在长为 ( 1 e 9 + 1 ) (1\mathrm{e}9+1) (1e9+1)的木棒上,初始时第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)只蚂蚁在距离棒左端 a i a_i ai单位处,有些蚂蚁面朝左,有些蚂蚁面朝右.所有蚂蚁以速度 1 1 1单位 / s /\mathrm{s} /s运动,两蚂蚁相遇时各自立即转向.棒的两端各有一个障碍物,每个障碍物有一个耐久值,左边的耐久值为 A A A,右边的耐久值为 B B B,耐久值降为 0 0 0时障碍物被破坏.一只蚂蚁撞到障碍物时会立即转身,同时障碍物的耐久 − 1 -1 −1.经过被破坏的障碍物的蚂蚁会掉下木棒.求所有蚂蚁掉下木棒所需的时间.
第一行输入三个整数 n , a , b ( 1 ≤ n ≤ 1 e 6 , 1 ≤ a , b ≤ 1 e 9 ) n,a,b\ \ (1\leq n\leq 1\mathrm{e}6,1\leq a,b\leq 1\mathrm{e}9) n,a,b (1≤n≤1e6,1≤a,b≤1e9).第二行输入 n n n格整数 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 9 , a i < a i + 1 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9,a_i<a_{i+1}) a1,⋯,an (1≤ai≤1e9,ai<ai+1).第三行输入 n n n个整数 d 1 , ⋯ , d n ( d i ∈ { 0 , 1 } ) d_1,\cdots,d_n\ \ (d_i\in\{0,1\}) d1,⋯,dn (di∈{0,1}),其中 d i = 0 d_i=0 di=0表示第 i i i只蚂蚁初始时面朝左, d i = 1 d_i=1 di=1表示第 i i i只蚂蚁初始时面朝右.
思路
两蚂蚁相遇时转向视为互相穿过对方.设棒长 l l l,显然每 2 l 1 = 2 l s \dfrac{2l}{1}=2l\ \mathrm{s} 12l=2l s所有蚂蚁回到自己的初始位置,且朝向不变.注意到前 ⌊ min { A , B } n ⌋ \left\lfloor\dfrac{\min\{A,B\}}{n}\right\rfloor ⌊nmin{A,B}⌋个完整的周期没有障碍物被破坏,直接计算障碍物耐久值的减少量即可.
对不完整的周期,用两个小根堆 t l e f t , t r i g h t tleft,tright tleft,tright分别表示面朝左、右的蚂蚁走到对应的端点所需的时间,每次取所需时间最短的蚂蚁进行扩展,转向后插入另一个堆即可.
代码 -> 2021CCPC四川省赛题解-J(思维+模拟) By : 塔子哥来了
const int len = 1e9 + 1; // 棒长
void solve() {
int n, A, B; cin >> n >> A >> B;
vi a(n + 1), d(n + 1); // 初位置、朝向
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> d[i];
int round = min(A, B) / n; // 完整的周期数
ll ans1 = (ll)round * len * 2; // 完整的周期所需时间
int damage = round * n; // 完整的周期对障碍物造成的伤害
A -= damage, B -= damage;
pque<ll, vl, greater<ll>> tleft, tright; // 面朝左、右的蚂蚁走到对应的端点所需的时间
for (int i = 1; i <= n; i++) {
if (d[i]) tright.push(len - a[i]);
else tleft.push(a[i]);
}
ll ans2 = 0; // 不完整的周期所需的时间
while (tleft.size() || tright.size()) {
if (tleft.size()) {
ll t = tleft.top(); tleft.pop();
ans2 = max(ans2, t);
if (A) {
A--;
tright.push(t + len); // 转向
}
}
if (tright.size()) {
ll t = tright.top(); tright.pop();
ans2 = max(ans2, t);
if (B) {
B--;
tleft.push(t + len); // 转向
}
}
}
cout << ans1 + ans2;
}
int main() {
solve();
}
I. Monster Hunter
题意
有编号 1 ∼ m 1\sim m 1∼m的 m m m只怪物,初始时第 i ( 1 ≤ i ≤ m ) i\ \ (1\leq i\leq m) i (1≤i≤m)只怪物的血量为 h i h_i hi.玩家的攻击力是一个长度为 n n n的循环序列 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an.当怪物的血量 ≤ 0 \leq 0 ≤0时被消灭.求消灭所有怪物所需的最少攻击次数.
有 t t t组测试数据.每组测试数据第一行输入一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5).第二行输入 n n n个整数 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 3 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 3) a1,⋯,an (1≤ai≤3).第三行输入一个整数 m ( 1 ≤ m ≤ 1 e 5 ) m\ \ (1\leq m\leq 1\mathrm{e}5) m (1≤m≤1e5).第四行输入 m m m个整数 h 1 , ⋯ , h m ( 1 ≤ h i ≤ 1 e 9 ) h_1,\cdots,h_m\ \ (1\leq h_i\leq 1\mathrm{e}9) h1,⋯,hm (1≤hi≤1e9).数据保证所有测试数据的 n n n之和、 m m m之和都不超过 1 e 5 1\mathrm{e}5 1e5.
思路 By : 墨染空
显然有解,且可二分出最小攻击次数.对每个攻击次数,可求出各种攻击力分别能用多少次.
显然为使得攻击次数最小,应优先用攻击力大的攻击,且尽量先打死血量少的怪物.故将 h [ ] h[] h[]升序排列.
对攻击力为 3 3 3和 2 2 2的攻击,按怪物血量模 3 3 3和 2 2 2的余数分类.
(1)①先对每个怪物在不打死的前提下尽量用攻击力为 3 3 3的攻击.注意若最大攻击次数 ⌊ h [ i ] 3 ⌋ \left\lfloor\dfrac{h[i]}{3}\right\rfloor ⌊3h[i]⌋后怪物剩下的血量为奇数,如原血量为 7 7 7,攻击一次后变为 1 1 1的情况,此时先用一次攻击力为 3 3 3的攻击,再用两次攻击力为 2 2 2的攻击更优.
②若还有多余的攻击力为 3 3 3的攻击,对每个怪物在不打死的前提下尽量用.
③若还有多余的攻击力为 3 3 3的攻击,先优先打死血量为 2 2 2的怪物,剩余的攻击再打死血量为 1 1 1的怪物.
(2)①先对血量为偶数的怪物用攻击力为 2 2 2的攻击,再对血量为奇数的怪物用攻击力为 2 2 2的攻击.
②若还有多余的攻击力为 2 2 2的攻击,打死血量为 1 1 1的怪物.
(3)剩余的怪物用攻击力为 1 1 1的攻击打.
代码 -> 2021CCPC四川省赛题解-I(贪心+二分)
const int MAXN = 1e5 + 5;
int n, m; // 攻击力数、怪物数
int a[MAXN][3]; // 攻击力出现的次数
int h[MAXN], tmph[MAXN]; // 怪物血量及其备份
ll cnt[4]; // 每种攻击力能用的次数
bool check(ll x) {
for (int i = 1; i <= m; i++) tmph[i] = h[i]; // 备份
for (int i = 1; i <= 3; i++) cnt[i] = (ll)(x / n) * a[n][i] + a[x % n][i];
for (int i = 1; i <= m; i++) { // 全部用攻击力3
int t = min((ll)tmph[i] / 3, cnt[3]); // 攻击次数
if (!t) continue;
if ((tmph[i] - 3 * t) & 1) t--; // 打完剩下的血量为奇数,不如用若干次攻击力为2更优
tmph[i] -= 3 * t, cnt[3] -= t;
}
for (int i = 1; i <= m; i++) { // 全部用攻击力3
if (!cnt[3]) break; // 攻击力为3用完了
int t = min((ll)tmph[i] / 3, cnt[3]); // 攻击次数
tmph[i] -= 3 * t, cnt[3] -= t;
}
for (int i = 1; i <= m; i++) { // 用攻击力3打血量为2的怪物
if (!cnt[3]) break; // 攻击力为3用完了
if (tmph[i] == 2) tmph[i] = 0, cnt[3]--;
}
for (int i = 1; i <= m; i++) { // 用攻击力3打血量为1的怪物
if (!cnt[3]) break; // 攻击力为3用完了
if (tmph[i] == 1) tmph[i] = 0, cnt[3]--;
}
for (int i = 1; i <= m; i++) { // 用攻击力2打血量为偶数的怪物
if (!cnt[2]) break; // 攻击力为2用完了
if (tmph[i] % 2 == 0) {
int t = min((ll)tmph[i] / 2, cnt[2]); // 攻击次数
tmph[i] -= 2 * t, cnt[2] -= t;
}
}
for (int i = 1; i <= m; i++) { // 用攻击力2打血量为奇数的怪物
if (!cnt[2]) break; // 攻击力为2用完了
if (tmph[i] % 2 == 1) {
int t = min((ll)tmph[i] / 2, cnt[2]); // 攻击次数
tmph[i] -= 2 * t, cnt[2] -= t;
}
}
for (int i = 1; i <= m; i++) { // 用攻击力2打血量为1的怪物
if (!cnt[2]) break; // 攻击力为2用完了
if (tmph[i] == 1) tmph[i] = 0, cnt[2]--;
}
ll rest = 0;
for (int i = 1; i <= m; i++) rest += tmph[i];
return cnt[1] >= rest;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
int x; cin >> x;
for (int j = 1; j <= 3; j++) a[i][j] = a[i - 1][j]; // 继承上一个状态
a[i][x]++;
}
cin >> m;
ll l = m, r = 0;
for (int i = 1; i <= m; i++) {
cin >> h[i];
r += h[i];
}
sort(h + 1, h + m + 1);
while (l < r) {
ll mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
F. Direction Setting
题意
给定一张包含编号 1 ∼ n 1\sim n 1∼n的 n n n个节点和编号 1 ∼ m 1\sim m 1∼m的 m m m条边的无向图,其中节点 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)有一个限制 a i a_i ai.为每条边规定一个方向,使得该图变为有向图,同时 D = ∑ i = 1 n max { 0 , d i − a i } \displaystyle D=\sum_{i=1}^n \max\{0,d_i-a_i\} D=i=1∑nmax{0,di−ai}最小,其中 d i d_i di表示节点 i i i的入度.
有 t t t组测试数据.每组测试数据第一行输入两个整数 n , m ( 2 ≤ n ≤ 300 , 1 ≤ m ≤ 300 ) n,m\ \ (2\leq n\leq 300,1\leq m\leq 300) n,m (2≤n≤300,1≤m≤300).第二行输入 n n n个整数 a 1 , ⋯ , a n ( 0 ≤ a i ≤ 1 e 4 ) a_1,\cdots,a_n\ \ (0\leq a_i\leq 1\mathrm{e}4) a1,⋯,an (0≤ai≤1e4).接下来 m m m行每行输入两个整数 u i , v i ( 1 ≤ u i , v i ≤ n ) u_i,v_i\ \ (1\leq u_i,v_i\leq n) ui,vi (1≤ui,vi≤n),表示第 i i i条边连接节点 u u u与 v v v.注意可能存在重边和自环.数据保证所有测试数据的 n n n之和、 m m m之和不超过 3000 3000 3000.
对每组测试数据,第一行输出一个整数,表示 D D D的最小值.第二行输出一个长度为 m m m的 0 − 1 0-1 0−1串 s 1 ⋯ , s m s_1\cdots,s_m s1⋯,sm,表示边 i ( 1 ≤ i ≤ m ) i\ \ (1\leq i\leq m) i (1≤i≤m)的方向,其中 s i = 0 s_i=0 si=0表示边 i i i从节点 u i u_i ui指向 v i v_i vi, s i = 1 s_i=1 si=1表示边 i i i从节点 v i v_i vi指向 u i u_i ui.若有多组解,输出任一组.
思路 By : kaka0010
由节点的限制和数据范围易想到网络流,考虑如何建图.因限制在节点上而不在边上,而连接节点 u u u与 v v v的边 < u , v > <u,v> <u,v>可选择为节点 u u u贡献 1 1 1点入度还是为节点 v v v贡献 1 1 1点入度,考虑拆边,即将边 < u , v > <u,v> <u,v>变成一个虚节点 P P P,分别向节点 u u u和 v v v连边,容量为 1 1 1、费用为 0 0 0.
对每个节点 i i i,显然可向汇点连一条容量为 a [ i ] a[i] a[i]、费用为 0 0 0的边,它表示 a [ i ] a[i] a[i]是节点 i i i的免费流量.实际流量可超过 a [ i ] a[i] a[i],此时从节点 i i i向汇点连一条容量为 I N F INF INF、费用为 1 1 1的边,表示超出 a [ i ] a[i] a[i]的流量需 1 1 1点花费,最终答案即达到最大流的最小花费.
对输出方案,只需考虑残量网络中容量减为 0 0 0的边的方向与输入数据中边的方向是否相同即可.
代码 -> 2021CCPC四川省赛题解-F(拆边+费用流)
namespace SPFA_Cost_Flow {
static const int MAXN = 3005, MAXM = 3e5 + 10; // 边开两倍
int n, m, s, t; // 点数、边数、源点、汇点
int head[MAXN], edge[MAXM], capa[MAXM], cost[MAXM], nxt[MAXM], idx; // capa[i]表示边i的容量,cost[i]表示边i的费用
int min_capa[MAXN]; // min_capa[i]表示到节点i的所有边的容量的最小值
int dis[MAXN]; // dis[i]表示源点到节点i的最短路
int pre[MAXN]; // pre[i]表示节点i的前驱边的编号
bool state[MAXN]; // SPFA中记录每个节点是否在队列中
void add(int a, int b, int c, int d) { // 建边a->b,容量为c,费用为d
edge[idx] = b, capa[idx] = c, cost[idx] = d, nxt[idx] = head[a], head[a] = idx++; // 正向边
edge[idx] = a, capa[idx] = 0, cost[idx] = -d, nxt[idx] = head[b], head[b] = idx++; // 反向边,流量初始为0,费用为正向边的相反数
}
bool spfa() { // 返回是否找到增广路
memset(dis, INF, so(dis));
memset(min_capa, 0, so(min_capa));
qi que;
que.push(s);
dis[s] = 0, min_capa[s] = INF; // 源点处的流量无限制
while (que.size()) {
int u = que.front(); que.pop();
state[u] = false;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = edge[i];
if (capa[i] && dis[v] > dis[u] + cost[i]) { // 边还有容量
dis[v] = dis[u] + cost[i];
pre[v] = i; // 记录前驱边
min_capa[v] = min(min_capa[u], capa[i]);
if (!state[v]) {
que.push(v);
state[v] = true;
}
}
}
}
return min_capa[t]; // 汇点的流量非零即可以到达汇点,亦即存在增广路
}
pll EK() { // first为最大流、second为最小费用
pll res(0, 0);
while (spfa()) { // 当前还有增广路
int tmp = min_capa[t];
res.first += tmp, res.second += (ll)tmp * dis[t];
for (int i = t; i != s; i = edge[pre[i] ^ 1])
capa[pre[i]] -= tmp, capa[pre[i] ^ 1] += tmp; // 正向边减,反向边加
}
return res;
}
}
using namespace SPFA_Cost_Flow;
pii edges[MAXM];
void solve() {
memset(head, -1, so(head));
cin >> n >> m;
s = n + m + 1, t = n + m + 2; // 源点、汇点
for (int i = 1; i <= n; i++) {
int a; cin >> a;
add(i, t, a, 0), add(i, t, INF, 1); // 免费流量、额外流量
}
for (int i = 1; i <= m; i++) {
cin >> edges[i].first >> edges[i].second;
add(s, n + i, 1, 0); // 拆边的虚节点
add(n + i, edges[i].first, 1, 0), add(n + i, edges[i].second, 1, 0); // 虚点向u,v连边
}
cout << EK().second << endl;
for (int u = n + 1; u <= n + m; u++) { // 枚举拆边的虚节点
for (int i = head[u]; ~i; i = nxt[i]) {
int v = edge[i];
if (v > n + m) continue; // 不是原图中的节点
if (!capa[i]) {
if (v == edges[u - n].second) cout << 0;
else cout << 1;
break;
}
}
}
cout << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}