文章目录
A. Bridging the Gap 2
Problem
现在有 n n n 个人需要通过一艘船过河,给定每个人的体力为 h i h_i hi,以及该艘船最多能够承载 R R R 个人,至少需要 L L L 个人操作。
同时每乘坐一次船后体力变为 h i − 1 h_i-1 hi−1,当体力变为 0 0 0 后就不能再乘坐船过河了。
问这 n n n 个人能否全部到达河对岸。
数据范围: 1 ≤ L < R ≤ n ≤ 5 × 1 0 5 , 1 ≤ h i ≤ 5 × 1 0 5 1\le L<R\le n\le 5\times10^5,1\le h_i\le 5\times 10^5 1≤L<R≤n≤5×105,1≤hi≤5×105
Solution
最优的策略显然是每次选当前体力值最大的 R R R 个人过河,再选择河对面体力值最大的 L L L 个人将船运回来。
假设前 i i i 趟需要的需要的体力值为 x x x,而所有人目前能够提供的体力值为 y y y,那么判断前 i i i 趟能否进行的充要条件其实就是 x ≤ y x\le y x≤y。
必要性是显然的,因为如果连这个条件都不满足那么前 i i i 趟是不可能进行的;而充分性采用最优的策略后可以通过数学归纳法证明。
那么只需要判断最后一趟是否满足条件即可。
记 t t t 表示总共的趟数,那么 x = ( t − 1 ) ( R + L ) + n − ( t − 1 ) ( R − L ) x=(t-1)(R+L)+n-(t-1)(R-L) x=(t−1)(R+L)+n−(t−1)(R−L),要注意最后过河的人数可能不足 R R R 个。
当 h i h_i hi 为奇数时,能提供 m i n ( 2 ( t − 1 ) + 1 , h i ) min(2(t-1)+1,h_i) min(2(t−1)+1,hi)
当 h i h_i hi 为偶数时,能提供 m i n ( 2 ( t − 1 ) + 1 , h i − 1 ) min(2(t-1)+1,h_i-1) min(2(t−1)+1,hi−1)
时间复杂度: O ( n ) O(n) O(n)
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 5e5;
int n, l, r;
int h[N + 5];
int main()
{
cin >> n >> l >> r;
for (int i = 1; i <= n; i++)
{
cin >> h[i];
}
int t = (n - r) / (r - l) + 1;
if ((n - r) % (r - l) != 0)
{
t++;
}
int sum = (t - 1) * (r + l) + (n - (t - 1) * (r - l));
//printf("%d %d\n", t, sum);
int cur = 0;
for (int i = 1; i <= n; i++)
{
if(h[i]%2==0)
{
h[i]--;
}
cur += min(h[i], 2 * (t - 1) + 1);
}
//printf("\n");
if (cur < sum)
{
printf("No\n");
}
else
{
printf("Yes\n");
}
return 0;
}
B. Crash Test
Problem
初始时汽车离墙的距离为 D D D,同时给定 n n n 种前进距离 h i h_i hi。如果当前离墙的距离 r < h i r<h_i r<hi,那么汽车碰到墙后会反弹并继续移动 h i − r h_i-r hi−r 的距离,当 r ≥ h i r\ge h_i r≥hi 时, r = r − h i r=r-h_i r=r−hi。
问离墙的最近距离是多少?
数据范围: 1 ≤ n ≤ 100 , 1 ≤ D , h i ≤ 1 0 18 1\le n\le 100,1\le D,h_i\le 10^{18} 1≤n≤100,1≤D,hi≤1018
Solution
当 n = 1 n=1 n=1 时,答案显然为 m i n ( D m o d h 1 , h 1 − D m o d h 1 ) min(D\ mod\ h_1,h1-D\ mod\ h_1) min(D mod h1,h1−D mod h1)。
当 n = 2 n=2 n=2 时,由裴蜀定理可知存在整数 x , y x,y x,y,使得 x h 1 + y h 2 = g c d ( h 1 , h 2 ) xh_1+yh_2=gcd(h_1,h_2) xh1+yh2=gcd(h1,h2),假设汽车碰到墙后能继续前进,那么之后在墙另一边执行的操作就可以看作是负数。记 d = g c d ( h 1 , h 2 ) d=gcd(h_1,h_2) d=gcd(h1,h2),那么此时答案即为 m i n ( D m o d d , d − D m o d d ) min(D\ mod\ d,d-D\ mod\ d) min(D mod d,d−D mod d)。
当 n > 2 n>2 n>2 时,记 d = g c d ( h 1 , h 2 , … ) d=gcd(h_1,h_2,\dots) d=gcd(h1,h2,…),由数学归纳法可知,此时最小的距离为 m i n ( D m o d d , d − D m o d d ) min(D\ mod\ d,d-D\ mod\ d) min(D mod d,d−D mod d)。
时间复杂度: O ( n l o g ( d ) ) O(n\ log(d)) O(n log(d)),其中 l o g ( d ) log(d) log(d) 为求 g c d gcd gcd 的复杂度。
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 5e5;
int n;
ll D,h[N+5];
int main()
{
cin>>n>>D;
for(int i=1;i<=n;i++)
{
cin>>h[i];
}
ll d=0;
for(int i=1;i<=n;i++)
{
d=__gcd(d,h[i]);
}
printf("%lld\n",min(D%d,d-D%d));
return 0;
}
D. Dominoes!
Problem
给定 n n n 块多米诺骨牌,每个多米诺骨牌有两个数字 x i x_i xi 和 y i y_i yi,问怎么将这 n n n 块多米诺骨牌拼接成一条直线,使得相邻两块多米诺骨牌之间的数字均不同。
数据范围: 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ x i , y i ≤ 1 0 9 1\le n\le 2\times10^5,1\le x_i,y_i\le 10^9 1≤n≤2×105,1≤xi,yi≤109
Solution
首先想到可以将多米诺骨牌分成两部分,其中一部分 x i x_i xi 和 y i y_i yi 相等,而另一部分 x i x_i xi 和 y i y_i yi 不相等。对于不相等的部分显然可以拼接在一起,而相等的部分则要另外考虑。
对于 x i x_i xi 和 y i y_i yi 相等的多米诺骨牌,每次挑选出现次数最多的两种不同的骨牌,将其拼接在一起,反复执行这一操作直到没有多余的骨牌或者只剩下一种骨牌。
接下去将剩余的相等的骨牌和不相等的骨牌轮流拼接在一起即可。如果当前可以拼接相等的骨牌就拼在一起,如果不可以就将不相等的骨牌拼接起来,直到不相等的骨牌被全部拼接完毕。
如果此时还剩下相等的骨牌,那么就说明不能将所有的骨牌拼接起来。
时间复杂度: O ( n l o g ( n ) ) O(n\ log(n)) O(n log(n))
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5;
int n;
vector<pair<int, int>> ans, p;
map<int, int> mp;
priority_queue<pair<int, int>> q;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int x, y;
cin >> x >> y;
if (x == y)
{
mp[x]++;
}
else
{
p.push_back({x, y});
}
}
for (auto it : mp)
{
q.push({it.second, it.first});
}
while (q.size() >= 2)
{
pair<int, int> p1, p2;
p1 = q.top(), q.pop();
p2 = q.top(), q.pop();
ans.push_back({p1.second, p1.second});
ans.push_back({p2.second, p2.second});
p1.first--;
p2.first--;
if (p1.first)
{
q.push(p1);
}
if (p2.first)
{
q.push(p2);
}
}
int num = 0, cnt = 0;
if (q.size())
{
num = q.top().second;
cnt = q.top().first-1;
ans.push_back({num,num});
}
//printf("%d %d\n", num, cnt);
for (int i = 0; i < p.size(); i++)
{
if (!ans.empty() && ans.back().second != num && cnt)
{
ans.push_back({num, num});
cnt--;
}
if (!ans.empty() && ans.back().second == p[i].first)
{
swap(p[i].first, p[i].second);
}
ans.push_back(p[i]);
}
if (ans.size() != n)
{
printf("No\n");
return 0;
}
printf("Yes\n");
for (int i = 0; i < n; i++)
{
printf("%d %d\n", ans[i].first, ans[i].second);
}
return 0;
}
E. Malfunctioning Typewriter
Problem
给定 n n n 个长度为 m m m 的01串,这 n n n 个01串互不相同,定义好诗是将这 n n n 个01串以任何顺序拼接在一起形成的字符串。
现在又一台打字机,当你决定打字符 x x x 时,有 p p p 的概率可以正确打出,同时有 1 − p 1-p 1−p 的概率打出相反的字符 1 − x 1-x 1−x。
问打出好诗的最大概率是多少。
数据范围: 1 ≤ n , m ≤ 1000 , 0.5 ≤ p < 1 1\le n,m\le1000, 0.5\le p<1 1≤n,m≤1000,0.5≤p<1
Solution
先对这 n n n 个01串建立字典树,这样题目就相当于要求在这颗字典树上走恰好 n n n,同时每次都不能走出字典树的最大概率。
对于字典树的某个节点 i i i,记 f i , 0 / 1 f_{i,0/1} fi,0/1 分别表示左右两边字符串出现的次数。要想使得概率最大,相当于在这个节点向左恰好 f i , 0 f_{i,0} fi,0 次以及向右恰好 f i , 1 f_{i,1} fi,1 次的概率要最大。同时个节点间是相互独立的,那么答案就是每个节点最大概率的乘积。
每个节点的概率可以预处理出来,记 g i , j g_{i,j} gi,j 表示向左有 i i i 个,向右有 j j j 个字符串的最大概率,那么
g i , j = m a x ( p × g i − 1 , j + ( 1 − p ) × g i , j − 1 , p × g i , j − 1 + ( 1 − p ) × g i − 1 , j ) g_{i,j}=max(p\times g_{i-1,j}+(1-p)\times g_{i,j-1},p\times g_{i,j-1}+(1-p)\times g_{i-1,j}) gi,j=max(p×gi−1,j+(1−p)×gi,j−1,p×gi,j−1+(1−p)×gi−1,j)
解释:对于当前的字符可以选择打字符0,此时有 p p p 的概率打印成功,状态变成 g i − 1 , j g_{i-1,j} gi−1,j,有 1 − p 1-p 1−p 的概率打印失败,打出的字符变成1,此时状态变成 g i , j − 1 g_{i,j-1} gi,j−1,选择打字符1同理,然后两种情况取 m a x max max 即可。
最后的答案为: Π g f i , 0 , f i , 1 \Pi g_{f_{i,0},f_{i,1}} Πgfi,0,fi,1
时间复杂度: O ( n 2 ) O(n^2) O(n2)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
int n, m, cnt;
int nxt[N + 5][2], f[N + 5][2];
double g[1005][1005], p;
void insert(string s)
{
int p = 0;
for (int i = 0; i < (int)s.size(); i++)
{
int c = s[i] - '0';
if (!nxt[p][c])
{
nxt[p][c] = ++cnt;
}
f[p][c]++;
p = nxt[p][c];
}
}
int main()
{
cin >> n >> m >> p;
for (int i = 1; i <= n; i++)
{
string s;
cin >> s;
insert(s);
}
g[0][0] = 1;
for (int i = 1; i <= n; i++)
{
g[i][0] = g[i - 1][0] * p;
g[0][i] = g[0][i - 1] * p;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
g[i][j] = max(p * g[i - 1][j] + (1 - p) * g[i][j - 1], p * g[i][j - 1] + (1 - p) * g[i - 1][j]);
// printf("%lf ", g[i][j]);
}
// printf("\n");
}
double ans = 1;
for (int i = 0; i <= cnt; i++)
{
//printf("%d %d\n",f[i][0],f[i][1]);
ans = ans * g[f[i][0]][f[i][1]];
}
printf("%.15lf\n", ans);
return 0;
}
J. Rigged Games
Problem
两支队伍打比赛,其中大分为 2 b − 1 2b-1 2b−1,小分为 2 a − 1 2a-1 2a−1。同时给定长度为 n n n 的01串,比赛的结果将由这个字符串无限重复得到。
问从第 i ( 1 ≤ i ≤ n ) i\ (1\le i\le n) i (1≤i≤n) 个字符开始,获胜的队伍是哪支?
数据范围: 1 ≤ n , a , b ≤ 1 0 5 1\le n,a,b\le10^5 1≤n,a,b≤105
Solution
对于每个 i i i 来说,要找到第一次出现某支队伍大分为 b b b 的位置。可以发现队伍的得分是递增的,因此我们可以通过倍增的方式快速的找到这一位置。
记 f i , j f_{i,j} fi,j 表示从 i i i 开始经过 2 j 2^j 2j 次大分后的比赛情况,包括两支队伍的得分 x x x 和 y y y,以及比赛结束的位置 p o s pos pos,可以用结构体将这些信息放在一起便于处理。
初始值
f
i
,
0
f_{i,0}
fi,0 可以通过双指针的方式在一次遍历中得到,其他位置通过下面的公式得到:
f
i
,
j
=
f
f
i
,
j
−
1
,
j
−
1
f_{i,j}=f_{f_{i,j-1},j-1}
fi,j=ffi,j−1,j−1
最后倍增找到大比分恰好为 b b b 的位置即可。
时间复杂度: O ( n l o g ( n ) ) O(n\ log(n)) O(n log(n))
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5;
int n, a, b;
string t;
struct node
{
int x, y;
int pos;
} f[N + 5][20];
int main()
{
cin >> n >> a >> b;
cin >> t;
int cnt0 = (t[0] == '0'), cnt1 = (t[0] == '1');
for (int i = 0, j = 0; i < n;)
{
if (cnt0 == a || cnt1 == a)
{
f[i][0].x = (cnt0 == a);
f[i][0].y = (cnt1 == a);
f[i][0].pos = j;
// printf("%d %d %d\n", f[i][0].x, f[i][0].y, f[i][0].pos);
if (t[i] == '0')
{
cnt0--;
}
else
{
cnt1--;
}
i++;
}
else
{
j = (j + 1) % n;
if (t[j] == '0')
{
cnt0++;
}
else
{
cnt1++;
}
}
}
for (int j = 1; j < 20; j++)
{
// printf("j=%d\n", j);
for (int i = 0; i < n; i++)
{
f[i][j].x = f[i][j - 1].x + f[(f[i][j - 1].pos + 1) % n][j - 1].x;
f[i][j].y = f[i][j - 1].y + f[(f[i][j - 1].pos + 1) % n][j - 1].y;
f[i][j].pos = f[(f[i][j - 1].pos + 1) % n][j - 1].pos;
// printf("%d %d %d\n", f[i][j].x, f[i][j].y, f[i][j].pos);
}
}
for (int i = 0; i < n; i++)
{
int cur = i;
int cnt0 = 0, cnt1 = 0;
for (int j = 19; j >= 0; j--)
{
if (f[cur][j].x + cnt0 >= b || f[cur][j].y + cnt1 >= b)
{
continue;
}
cnt0 += f[cur][j].x;
cnt1 += f[cur][j].y;
cur = (f[cur][j].pos + 1) % n;
// printf("%d %d\n", cur, j);
}
cnt0 += f[cur][0].x;
cnt1 += f[cur][0].y;
// printf("%d %d \n", cnt0, cnt1);
if (cnt0 == b)
{
printf("0");
}
else
{
printf("1");
}
// printf("\n");
}
printf("\n");
return 0;
}
L. Sudoku and Minesweeper
Problem
给定一个大小 9 × 9 9\times 9 9×9 的数独,问能否将其中某些数字替换成炸弹,使得数字表示的是周围炸弹的数量。同时要求不能将所有的数字替换成炸弹。
Solution
因为给的是数独的形式,所以必定存在一个数字 8 8 8 在中间,那么只要将除这个位置外的所有数字全部替换成炸弹即可。
Code
#include<bits/stdc++.h>
using namespace std;
string s[9];
int ans[9][9];
void solve()
{
for(int i=1;i<8;i++)
{
for(int j=1;j<8;j++)
{
if(s[i][j]=='8')
{
ans[i][j]=1;
return;
}
}
}
}
int main()
{
for(int i=0;i<9;i++)
{
cin>>s[i];
}
solve();
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(ans[i][j])
{
printf("8");
}
else{
printf("*");
}
}
printf("\n");
}
return 0;
}