2021CCPC新疆省赛题解BDEFGHIJK
K. chino with c language
题意
m e m c p y ( ) memcpy() memcpy()不会检查源地址范围与目标地址范围是否重叠,它只从左往右复制每个字节; m e m m o v e ( ) memmove() memmove()会检查源地址范围与目标地址范围是否重叠,若发生重叠,它会通过特殊处理使得目的地址填充上与源地址相同的数据.设两函数调用都需要三个参数 p 1 , p 2 , l p_1,p_2,l p1,p2,l,分别表示源地址、目的地址、要复制的字节数是.如对下标从 1 1 1开始的字符串"abcdefg"调用 m e m c p y ( 1 , 3 , 5 ) memcpy(1,3,5) memcpy(1,3,5)将得到"ababab",而调用 m e m m o v e ( 1 , 3 , 5 ) memmove(1,3,5) memmove(1,3,5)将得到"ababcde".
给定长度为 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5)的、只包含小写英文字母的字符串 s s s,给定参数 p 1 , p 2 , l ( 1 ≤ p 1 , p 2 , l ≤ n , 1 ≤ p 1 + l − 1 , p 2 + l − 1 ≤ n ) p_1,p_2,l\ \ (1\leq p_1,p_2,l\leq n,1\leq p_1+l-1,p_2+l-1\leq n) p1,p2,l (1≤p1,p2,l≤n,1≤p1+l−1,p2+l−1≤n),分别输出调用 m e m c p y ( p 1 , p 2 , l ) memcpy(p_1,p_2,l) memcpy(p1,p2,l)和 m e m m o v e ( p 1 , p 2 , l ) memmove(p_1,p_2,l) memmove(p1,p2,l)的结果.
代码 -> 2021CCPC新疆省赛-K(模拟)
const int MAXN = 1e5 + 5;
int n;
char s[MAXN], t[MAXN];
void solve() {
cin >> n >> s + 1;
strcpy(t + 1, s + 1);
int p1, p2, l; cin >> p1 >> p2 >> l;
for (int i = 0; i < l; i++) t[p2 + i] = s[p1 + i];
for (int i = 0; i < l; i++) s[p2 + i] = s[p1 + i];
cout << s + 1 << endl << t + 1;
}
int main() {
solve();
}
J. do NOT a=2b
题意
称一个序列是坏的,如果其中存在两个元素 a , b s . t . a = 2 b a,b\ s.t.\ a=2b a,b s.t. a=2b.给定一个长度为 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq1\mathrm{e}5) n (1≤n≤1e5)的序列 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 6 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}6) a1,⋯,an (1≤ai≤1e6),删除尽量少的元素使得它不是坏的,输出最少要删除的元素的个数.
思路
d p [ a ] [ 0 ] dp[a][0] dp[a][0]表示不删除值为 a a a的元素, d p [ a ] [ 1 ] dp[a][1] dp[a][1]表示删除所有值为 a a a的元素.设值为 a a a的元素个数为 c n t [ a ] cnt[a] cnt[a].
注意到 a a a只能是偶数,状态转移方程: { a 为奇数 { d p [ a ] [ 0 ] = 0 , 因为 a 2 不是一个整数 d p [ a ] [ 1 ] = c n t [ a ] , 即要删除所有值为 a 的元素 a 为偶数 { d p [ a ] [ 0 ] = d p [ b ] [ 1 ] , 即不删 a 就要删除所有值为 b = a 2 的元素 d p [ a ] [ 1 ] = min { d p [ b ] [ 0 ] , d p [ b ] [ 1 ] } + c n t [ a ] , 删除 a 后 b 可删可不删 \begin{cases}a为奇数\begin{cases}dp[a][0]=0,因为\dfrac{a}{2}不是一个整数 \\ dp[a][1]=cnt[a],即要删除所有值为a的元素\end{cases} \\ a为偶数\begin{cases}dp[a][0]=dp[b][1],即不删a就要删除所有值为b=\dfrac{a}{2}的元素 \\ dp[a][1]=\min\{dp[b][0],dp[b][1]\}+cnt[a],删除a后b可删可不删\end{cases}\end{cases} ⎩ ⎨ ⎧a为奇数{dp[a][0]=0,因为2a不是一个整数dp[a][1]=cnt[a],即要删除所有值为a的元素a为偶数{dp[a][0]=dp[b][1],即不删a就要删除所有值为b=2a的元素dp[a][1]=min{dp[b][0],dp[b][1]}+cnt[a],删除a后b可删可不删.
注意到每个 d p [ a ] dp[a] dp[a]都包含了 d p [ b ] dp[b] dp[b]的最优解,故只需对所有 2 a > 1 e 6 2a>1\mathrm{e}6 2a>1e6的 a a a统计答案即可.
代码 -> 2021CCPC新疆省赛-J(线性DP)
const int MAXN = 1e6 + 5;
int n;
map<int, int> cnt; // cnt[b]表示值为b的元素个数
int dp[MAXN][2]; // dp[a][0]表示不删除值为b的元素,dp[a][1]表示删除所有值为b的元素
void solve() {
cin >> n;
while (n--) {
int a; cin >> a;
cnt[a]++;
}
for (int a = 1; a <= 1e6; a++) {
if (a & 1) {
dp[a][0] = 0; // b/2不是整数
dp[a][1] = cnt[a]; // 删除所有值为b的元素
}
else {
dp[a][0] = dp[a / 2][1]; // 不删a就要删除所有值为b=a/2的元素
dp[a][1] = min(dp[a / 2][0], dp[a / 2][1]) + cnt[a]; // 删除a后b可删可不删
}
}
int ans = 0;
for (int a = 5e5 + 1; a <= 1e6; a++) // 对所有2a>1e6的a统计答案
ans += min(dp[a][0], dp[a][1]);
cout << ans;
}
int main() {
solve();
}
E. is the order a rabbit ??
题意
兔子交易市场有规则:①市场只在上午和下午开放,每天的上午、下午的兔子交易价格可能不同;②每天至多只能买一只兔子,每只兔子只能买一次;③每天只能卖一只兔子.某人到该交易市场经营 n n n天,设他有足够的钱可用于购买兔子,第一天前和最后一天后他都没有兔子,问他最多能赚多少钱.
第一行输入一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5).接下来 n n n行每行输入两个整数 a , b ( 1 ≤ a , b ≤ 1 e 9 ) a,b\ \ (1\leq a,b\leq 1\mathrm{e}9) a,b (1≤a,b≤1e9),分别表示该天上午和下午兔子的价格.
思路
从最后一天往前推,维护当前的最高价格 m a x p r i c e maxprice maxprice.
设某天上午和下午的价格分别是 u , v u,v u,v.
(1)若 m a x p r i c e ≥ max { u , v } maxprice\geq \max\{u,v\} maxprice≥max{u,v},则以当天的最低价格 min { u , v } \min\{u,v\} min{u,v}买入,以 m a x p r i c e maxprice maxprice卖出,利润 m a x p r i c e − min { u , v } maxprice-\min\{u,v\} maxprice−min{u,v}.
(2)若 m a x p r i c e < max { u , v } maxprice<\max\{u,v\} maxprice<max{u,v}:
①若 u < v u<v u<v,则当天上午买入,下午卖出,利润 v − u v-u v−u.
②若 u ≥ v u\geq v u≥v且 m a x p r i c e > v maxprice>v maxprice>v,则以 m a x p r i c e maxprice maxprice买入,当天下午卖出,利润 m a x p r i c e − v maxprice-v maxprice−v.
代码 -> 2021CCPC新疆省赛-E(思维)
const int MAXN = 1e5 + 5;
int n;
pii prices[MAXN];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> prices[i].first >> prices[i].second;
int maxprice = 0; // 当前的最高价格
ll ans = 0;
for (int i = n; i >= 1; i--) {
auto [u, v] = prices[i];
if (maxprice >= max(u, v)) ans += maxprice - min(u, v);
else {
if (u < v) ans += v - u;
else if (maxprice > v) ans += maxprice - v;
maxprice = max(u, v); // 更新最高价格
}
}
cout << ans;
}
int main() {
solve();
}
G. cocktail with snake
题意
某游戏界面可视为一个 n × m n\times m n×m的网格.初始时玩家在点 ( 1 , 1 ) (1,1) (1,1)处,先向右走到 ( n , 1 ) (n,1) (n,1),再向上走到 ( n , 2 ) (n,2) (n,2),再向左走到 ( 1 , 2 ) (1,2) (1,2),再向上走到 ( 1 , 3 ) , ⋯ (1,3),\cdots (1,3),⋯,重复该过程直至走到终点.每一步玩家可按路线移动 1 1 1个单位长度,求移动 k k k步后到达的点与起点的Manhattan距离.
有 t ( 1 ≤ t ≤ 1 e 4 ) t\ \ (1\leq t\leq 1\mathrm{e}4) t (1≤t≤1e4)组测试数据.每组测试数据输入三个整数 n , m , k ( 1 ≤ n , m ≤ 1 e 18 , 0 ≤ k ≤ min { n m − 1 , 1 e 18 } ) n,m,k\ \ (1\leq n,m\leq 1\mathrm{e}18,0\leq k\leq \min\{nm-1,1\mathrm{e}18\}) n,m,k (1≤n,m≤1e18,0≤k≤min{nm−1,1e18}).
思路
如上图,将 ( 1 , 1 ) → ( 3 , 1 ) → ( 3 , 2 ) (1,1)\rightarrow(3,1)\rightarrow(3,2) (1,1)→(3,1)→(3,2)视为一段,将 ( 3 , 2 ) → ( 1 , 2 ) → ( 1 , 3 ) (3,2)\rightarrow (1,2)\rightarrow(1,3) (3,2)→(1,2)→(1,3)视为一段.显然移动 k k k步后在第 k ′ = ⌊ k n ⌋ k'=\left\lfloor\dfrac{k}{n}\right\rfloor k′=⌊nk⌋段,且 k ′ k' k′为奇数时向右走,为偶数时向左走.显然向上走的距离 Δ y = k ′ \Delta y=k' Δy=k′, Δ x \Delta x Δx只需考察 k % n k\% n k%n和行进方向即可.
代码 -> 2021CCPC新疆省赛-G(思维)
void solve() {
ll n, m, k; cin >> n >> m >> k;
ll r = k % n;
k/=n;
ll ans = k; // Δy
if (k & 1) ans += (n - r - 1);
else ans += r;
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
I. chino with mates
题意
给定一个长度为 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5)的序列 a 1 , ⋯ , a n ( − 1 e 9 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (-1\mathrm{e}9\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (−1e9≤ai≤1e9)和一个长度为 m ( 1 ≤ m ≤ 1 e 5 ) m\ \ (1\leq m\leq 1\mathrm{e}5) m (1≤m≤1e5)的序列 b 1 , ⋯ , b n ( − 1 e 9 ≤ b i ≤ 1 e 9 ) b_1,\cdots,b_n\ \ (-1\mathrm{e}9\leq b_i\leq 1\mathrm{e}9) b1,⋯,bn (−1e9≤bi≤1e9).称数对 ( a i , b j ) (a_i,b_j) (ai,bj)是好的,如果 a i b j ∈ [ l , r ] ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , − 1 e 9 ≤ l ≤ r ≤ 1 e 9 ) a_ib_j\in[l,r]\ \ (1\leq i\leq n,1\leq j\leq m,-1\mathrm{e}9\leq l\leq r\leq 1\mathrm{e}9) aibj∈[l,r] (1≤i≤n,1≤j≤m,−1e9≤l≤r≤1e9).问有几个好的数对 ( a , b ) (a,b) (a,b).
思路
先将 b [ ] b[] b[]升序排列.对每个 a i a_i ai,若 a i b j ∈ [ l , r ] a_ib_j\in[l,r] aibj∈[l,r],则 b j b_j bj在 l a i \dfrac{l}{a_i} ail和 r a i \dfrac{r}{a_i} air之间.
令 l e f t = min { l a i , r a i } , r i g h t = max { l a i , r a i } left=\min\left\{\dfrac{l}{a_i},\dfrac{r}{a_i}\right\},right=\max\left\{\dfrac{l}{a_i},\dfrac{r}{a_i}\right\} left=min{ail,air},right=max{ail,air},在 b [ ] b[] b[]上二分出第一个 ≥ l e f t \geq left ≥left的位置 p o s 1 pos_1 pos1和第一个 > r i g h t >right >right的位置 p o s 2 pos_2 pos2,
则 a i a_i ai对答案的贡献为 p o s 2 − p o s 1 pos_2-pos_1 pos2−pos1.
代码 -> 2021CCPC新疆省赛-I(思维)
void solve() {
int n, m; cin >> n >> m;
vi a(n), b(m);
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < m; i++) cin >> b[i];
int l, r; cin >> l >> r;
sort(all(b));
ll ans = 0.;
for (int i = 0; i < n; i++) {
double left = (double)l / a[i], right = (double)r / a[i];
if (cmp(left, right) > 0) swap(left, right);
int pos1 = lower_bound(all(b), left) - b.begin();
int pos2 = upper_bound(all(b), right) - b.begin();
ans += pos2 - pos1;
}
cout << ans;
}
int main() {
solve();
}
F. chino with ball
题意
x x x轴上有编号 1 ∼ n 1\sim n 1∼n的 n n n个球,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个球的初位置坐标为 p i p_i pi,初速度为 v i v_i vi(以向右为 x x x轴正方向).球的体积忽略不计,且两球碰撞时会交换速度.求 k k k秒后每个球的位置.
第一行输入两个整数 n , k ( 1 ≤ n ≤ 1 e 5 , 0 ≤ k ≤ 1 e 9 ) n,k\ \ (1\leq n\leq 1\mathrm{e}5,0\leq k\leq 1\mathrm{e}9) n,k (1≤n≤1e5,0≤k≤1e9).接下来 n n n行每行输入两个整数 p i , v i ( − 1 e 9 ≤ p i ≤ 1 e 9 , v i ∈ { − 1 , 0 , 1 } ) p_i,v_i\ \ (-1\mathrm{e}9\leq p_i\leq 1\mathrm{e}9,v_i\in\{-1,0,1\}) pi,vi (−1e9≤pi≤1e9,vi∈{−1,0,1}).数据保证所有球的初位置不同.
输出 n n n个整数,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个数表示编号为 i i i的球 k k k秒后的位置.
思路
注意到两球交换速度可视为互相穿过了对方,故球 k k k秒后的位置即 p i + k v i p_i+kv_i pi+kvi.但题目要求按原编号输出球 k k k秒后的位置,互相穿过后继续向前的球未必是原来的球,可将所有初位置和末位置都升序排列,这时顺序一一对应.
代码 -> 2021CCPC新疆省赛-F(思维)
void solve() {
int n, k; cin >> n >> k;
vii start(n + 1); // 初位置
vi end(n + 1); // 末位置
for (int i = 1; i <= n; i++) {
int p, v; cin >> p >> v;
start[i] = { p,i };
end[i] = p + k * v;
}
sort(start.begin() + 1, start.end());
sort(end.begin() + 1, end.end());
vi ans(n + 1);
for (int i = 1; i <= n; i++) ans[start[i].second] = end[i];
for (int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
}
int main() {
solve();
}
B. cocktail with hearthstone
题意
某游戏的规则:①每个玩家的战绩用数对 ( a , b ) (a,b) (a,b)表示,其中 a a a表示胜利场数, b b b表示失败场数.初始时战绩为 ( 0 , 0 ) (0,0) (0,0);②每轮胜利的玩家 a + + a++ a++,失败的玩家 b + + b++ b++,没有平局;③每轮对战的两个人的战绩相同,设为 ( a , b ) (a,b) (a,b),则该轮结束后会产生一个 ( a + 1 , b ) (a+1,b) (a+1,b)和一个 ( a , b + 1 ) (a,b+1) (a,b+1);④若有玩家胜利 n n n次或失败 m m m次,他将自动退出游戏.
现安排了 2 n + m 2^{n+m} 2n+m个玩家,保证所有玩家在退出游戏前都能找到对手.现有 q q q个询问,每个询问给定 a , b a,b a,b,设战绩 ( a , b ) (a,b) (a,b)符合退出游戏的要求,问有多少个人以战绩 ( a , b ) (a,b) (a,b)退出游戏,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
第一行输入三个整数 n , m , q ( 1 ≤ m < n ≤ 2 e 5 , 1 ≤ q ≤ 2 e 5 ) n,m,q\ (1\leq m<n\leq 2\mathrm{e}5,1\leq q\leq 2\mathrm{e}5) n,m,q (1≤m<n≤2e5,1≤q≤2e5).接下来 q q q行每行输入两个整数 a , b ( 0 ≤ a ≤ n , 0 ≤ b ≤ m ) a,b\ \ (0\leq a\leq n,0\leq b\leq m) a,b (0≤a≤n,0≤b≤m),数据保证 a = n a=n a=n或 b = m b=m b=m.
对每个询问,输出以战绩 ( a , b ) (a,b) (a,b)退出游戏的人数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
思路
游戏过程: ( 0 , 0 ) { ( 1 , 0 ) { ( 2 , 0 ) { ( 3 , 0 ) ( 2 , 1 ) ( 1 , 1 ) { ( 2 , 1 ) ( 1 , 2 ) ( 0 , 1 ) { ( 1 , 1 ) { ( 2 , 1 ) ( 1 , 2 ) ( 0 , 2 ) { ( 1 , 2 ) ( 0 , 3 ) (0,0)\begin{cases}(1,0)\begin{cases}(2,0)\begin{cases}(3,0) \\ (2, 1)\end{cases} \\ (1,1)\begin{cases}(2,1) \\ (1,2)\end{cases}\end{cases} \\ (0,1)\begin{cases}(1,1)\begin{cases}(2,1) \\ (1,2)\end{cases} \\ (0,2)\begin{cases}(1,2) \\ (0,3)\end{cases}\end{cases}\end{cases} (0,0)⎩ ⎨ ⎧(1,0)⎩ ⎨ ⎧(2,0){(3,0)(2,1)(1,1){(2,1)(1,2)(0,1)⎩ ⎨ ⎧(1,1){(2,1)(1,2)(0,2){(1,2)(0,3).
第 0 0 0层: ( 0 , 0 ) (0,0) (0,0)有 1 1 1个.
第 1 1 1层: ( 1 , 0 ) (1,0) (1,0)有 1 1 1个、 ( 0 , 1 ) (0,1) (0,1)有 1 1 1个.
第 2 2 2层: ( 2 , 0 ) (2,0) (2,0)有 1 1 1个、 ( 1 , 1 ) (1,1) (1,1)有 2 2 2个、 ( 0 , 2 ) (0,2) (0,2)有 1 1 1个.
第 3 3 3层: ( 3 , 0 ) (3,0) (3,0)有 1 1 1个、 ( 2 , 1 ) (2,1) (2,1)有 3 3 3个、 ( 1 , 2 ) (1,2) (1,2)有 3 3 3个、 ( 0 , 3 ) (0,3) (0,3)有 1 1 1个.
显然个数的规律为杨辉三角.
考察 a = n a=n a=n的情况.显然 ( a , b ) (a,b) (a,b)在第 ( a + b ) (a+b) (a+b)层,且在该层数量占比 C a + b a 2 a + b \dfrac{C_{a+b}^a}{2^{a+b}} 2a+bCa+ba.但注意到 a = n a=n a=n时,到达 ( a , b ) (a,b) (a,b)前 ( a , b − 1 ) , ( a , b − 2 ) , ⋯ (a,b-1),(a,b-2),\cdots (a,b−1),(a,b−2),⋯早已退出游戏,故能转移到 ( a , b ) (a,b) (a,b)的合法状态只有 ( a − 1 , b ) (a-1,b) (a−1,b),故占比应取 C a + b − 1 a − 1 2 a + b \dfrac{C_{a+b-1}^{a-1}}{2^{a+b}} 2a+bCa+b−1a−1.总人数 2 n + m 2^{n+m} 2n+m,故 a n s = 2 n + m − a − b ⋅ C a + b − 1 a − 1 ans=2^{n+m-a-b}\cdot C_{a+b-1}^{a-1} ans=2n+m−a−b⋅Ca+b−1a−1.同理 b = m b=m b=m时, a n s = 2 n + m − a − b ⋅ C a + b − 1 b − 1 ans=2^{n+m-a-b}\cdot C_{a+b-1}^{b-1} ans=2n+m−a−b⋅Ca+b−1b−1.
注意特判 ( a , b ) = ( n , m ) (a,b)=(n,m) (a,b)=(n,m)的情况,因无任何合法状态能转移到 ( n , m ) (n,m) (n,m),故 a n s = 0 ans=0 ans=0.
代码 -> 2021CCPC新疆省赛-B(组合计数)
const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;
int fac[MAXN], ifac[MAXN];
void init() { // 预处理阶乘及其逆元
fac[0] = 1;
for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;
ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}
int C(int n, int m) { // 组合数C(n,m)
return (ll)fac[n] * ifac[m] % MOD* ifac[n - m] % MOD;
}
void solve() {
init();
int n, m; cin >> n >> m;
CaseT{
int a,b; cin >> a >> b;
if (a == n && b == m) {
cout << 0 << endl;
continue;
}
int ans = 0;
if (a == n) ans = qpow(2, n + m - a - b, MOD) * C(a + b - 1, a - 1) % MOD;
else ans = qpow(2, n + m - a - b, MOD) * C(a + b - 1, b - 1) % MOD;
cout << ans << endl;
}
}
int main() {
solve();
}
H. cocktail with pony
题意
x x x轴的线段 [ 1 , n ] [1,n] [1,n]上A在追B,其中A初位置为 x 1 x_1 x1,每次走 v 1 v_1 v1步;B初位置为 x 2 x_2 x2,每次走 v 2 v_2 v2步.B与A交替移动,B先移动,即B在奇数轮移动,A在偶数轮移动,且两人的移动都不能超出线段边界.若有某一瞬间A和B在同一位置,则A抓住B.B想尽量晚被抓住,A想尽快抓住B,两人都采取最优策略,问几轮后A能抓住B.
有 t ( 1 ≤ t ≤ 1 e 4 ) t\ \ (1\leq t\leq 1\mathrm{e}4) t (1≤t≤1e4)组测试数据.每组测试数据输入五个整数 n , v 1 , v 2 , x 1 , x 2 ( 2 ≤ n ≤ 1000 , 1 ≤ v 1 , v 2 ≤ 1 e 9 , 1 ≤ x 1 , x 2 ≤ n , x 1 ≠ x 2 ) n,v_1,v_2,x1,x_2\ \ (2\leq n\leq 1000,1\leq v_1,v_2\leq 1\mathrm{e}9,1\leq x_1,x_2\leq n,x_1\neq x_2) n,v1,v2,x1,x2 (2≤n≤1000,1≤v1,v2≤1e9,1≤x1,x2≤n,x1=x2).
思路
若初始时 x 1 < x 2 x_1<x_2 x1<x2,令 x 1 = n − x 1 + 1 , x 2 = n − x 2 + 1 x_1=n-x_1+1,x_2=n-x_2+1 x1=n−x1+1,x2=n−x2+1,统一为两人都往左走.
模拟.注意为保证B尽量晚被抓住,其位置应尽量靠左,则B移动到边界 x = 1 x=1 x=1处时若还有多余的步数会左右横跳,最终停在 x = 1 x=1 x=1或 x = 2 x=2 x=2处.
代码 -> 2021CCPC新疆省赛-H(思维+模拟)
void solve() {
int n, v1, v2, x1, x2; cin >> n >> v1 >> v2 >> x1 >> x2;
if (x1 < x2) x1 = n - x1 + 1, x2 = n - x2 + 1; // 保证两人都往左走
int ans = 1;
while (true) {
if (ans & 1) { // B走
if (x2 == 1 && x1 == 2) break; // B的路被堵死
if (x2 - v2 >= 1) x2 -= v2; // B完整地向左走
else if (v2 - x2 & 1) x2 = 1; // B在边界附近左右横跳,最终停在x=1
else x2 = 2; // B在边界附近左右横跳,最终停在x=2
}
else { // A走
if (x1 - x2 <= v1) break; // 下一步抓到
x1 -= v1; // A完整地向左走
}
ans++;
}
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
D. cocktail with swap
题意
给定一个长度为 n n n的字符串 s s s,下标从 1 1 1开始.现有操作:选择下标 i , j ∈ [ 1 , n ] s . t . l ≤ ∣ i − j ∣ ≤ r i,j\in[1,n]\ s.t.\ l\leq |i-j|\leq r i,j∈[1,n] s.t. l≤∣i−j∣≤r,交换 s i s_i si和 s j s_j sj.求若干次(可能为零次)操作后能得到的字典序最小的字符串.
第一行输入三个整数 n , l , r ( 2 ≤ n ≤ 1 e 5 , 1 ≤ l ≤ r < n ) n,l,r\ \ (2\leq n\leq 1\mathrm{e}5,1\leq l\leq r<n) n,l,r (2≤n≤1e5,1≤l≤r<n).
思路I
① l = r l=r l=r时,只能跳着排序.
② l ≤ ⌊ n 2 ⌋ l\leq\left\lfloor\dfrac{n}{2}\right\rfloor l≤⌊2n⌋时,显然能做到交换相邻元素,则能得到的字典序最小的字符串即原串升序排列的结果.
③ l > ⌊ n 2 ⌋ l>\left\lfloor\dfrac{n}{2}\right\rfloor l>⌊2n⌋时,分为两段 [ 1 , n − l ] , [ l + 1 , n ] [1,n-l],[l+1,n] [1,n−l],[l+1,n].
对情况①和③,预处理出所有能动的字符,对它们排序后放回原串即可.
代码I -> 2021CCPC新疆省赛-D(思维+模拟)
const int MAXN = 1e5 + 5;
char s[MAXN], tmp[MAXN]; // 原串、临时串
void solve() {
int n, l, r; cin >> n >> l >> r >> s;
if (l == r) {
for (int i = 0; i < l; i++) {
// 预处理出能动的字符
int m = 0, j = i;
while (j < n) tmp[m++] = s[j], j += l;
sort(tmp, tmp + m);
// 结果放回原串
m = 0, j = i;
while (j < n) s[j] = tmp[m++], j += l;
}
}
else if (l <= n / 2) sort(s, s + n);
else {
// 预处理出能动的字符
int m = 0;
for (int i = 0; i < n - l; i++) tmp[m++] = s[i];
for (int i = l; i < n; i++) tmp[m++] = s[i];
sort(tmp, tmp + m);
// 结果放回原串
m = 0;
for (int i = 0; i < n - l; i++) s[i] = tmp[m++];
for (int i = l; i < n; i++) s[i] = tmp[m++];
}
cout << s;
}
int main() {
solve();
}
思路II
用一个并查集 d s u 2 dsu2 dsu2维护每个起点开始的区间中能动的下标,枚举每个起点 i i i,将以其开始的区间中能动的下标 j j j对应的集合合并.朴素做法时间复杂度 O ( n 2 ) O(n^2) O(n2),会TLE.
考虑优化,注意到可用另一个并查集 d s u 1 dsu1 dsu1维护指针 j j j下一步跳到的位置,保证每次 j j j跳到下一段的开头,时间复杂度 O ( n ) O(n) O(n).
代码II -> 2021CCPC新疆省赛-D(思维+并查集)
const int MAXN = 1e5 + 5;
int n, l, r;
string s;
struct DSU {
int n; // 元素个数
int fa[MAXN]; // fa[]数组
int siz[MAXN]; // 集合大小
void init() { // 初始化fa[]、siz[]
for (int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;
}
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
bool merge(int x, int y) { // 返回合并是否成功,即初始时是否不在同一集合中
x = find(x), y = find(y);
if (x != y) {
if (siz[x] > siz[y]) swap(x, y); // 保证x所在的集合小
fa[x] = y;
siz[y] += siz[x];
return true;
}
else return false;
}
}dsu1, dsu2;
void solve() {
cin >> n >> l >> r >> s;
dsu1.n = n; dsu1.init(); // 维护维护j跳的位置
dsu2.n = n; dsu2.init(); // 维护每个起点开始的区间中能动的下标
vb vis(n);
for (int i = 0; i < n; i++) { // 枚举起点
for (int j = i + l; j <= i + r && j < n; j = dsu1.find(j) + 1) { // j跳到下一段的开头
dsu2.fa[dsu2.find(j)] = dsu2.fa[dsu2.find(i)];
vis[j] = true;
if (vis[j - 1]) dsu1.fa[dsu1.find(j - 1)] = dsu1.fa[dsu1.find(j)];
}
}
// 预处理每个起点开始的区间中出能动的字符的下标
vector<vi> son(n);
for (int i = 0; i < n; i++) son[dsu2.find(i)].push_back(i);
for (int i = 0; i < n; i++) {
if (son[i].size()) {
// 预处理出能动的字符
string t;
for (auto j : son[i]) t.push_back(s[j]);
sort(all(t));
// 结果放回原串
int idx = 0;
for (auto j : son[i]) s[j] = t[idx++];
}
}
cout << s;
}
int main() {
solve();
}