A-Bridging the Gap 2
题意
在河岸的一侧有个人,每个人有一个体力值,有一艘船可以将人从一侧载到另一侧,每次航行需要至少个人掌舵,每次掌舵会花费每个掌舵人的一点体力,当体力不足一点时,这个人不能再掌舵,船的容量最大是,提问是否能够将这些人都运送到对岸。
数据范围
-
思路
贪心的运输,从左岸运输的最小次数是,计算每个人最多的来回次数,假如满足,则可以将所有人都运输到对岸。
代码
ll h[maxn], a[maxn];
void solve() {
int n, L, R;cin >> n >> L >> R;
ll sum = 0, k = (n - L - 1) / (R - L);
for (int i = 0;i < n;i++) {
cin >> h[i];
a[i] = (h[i] - 1) / 2;
sum += min(k, a[i]);
}
if (sum >= k * L)cout << "Yes\n";
else cout << "No\n";
}
B-Crash Test
题意
小车有个引擎,第个引擎可以让车前进米。在小车起点正前方距离为处有一反弹墙面,小车如果撞在墙上将会被回弹与当前剩余的前进米数相同的距离,并保持车头朝向墙面。小车可以使用任意引擎任意次,询问距离墙面最近的距离是多少。
数据范围
思路
假设当前选择了某个引擎,并只使用这个引擎,易知,我们可以将距离减小到。
接下来选择另一种引擎,在刚才的基础上,我们可以将距离减小到。
设,,则的变化其实是这样的:
观察结果,可以发现最小的之和所有引擎的有关,计算数组的即可。
代码
void solve() {
ll n, d;cin >> n >> d;
vector<ll>h(n + 1);
for (ll i = 1;i <= n;i++) {
cin >> h[i];
}
sort(h.begin() + 1, h.end());
ll x = h[n];
for (ll i = n;i >= 1;i--) {
x = gcd(x, h[i]);
}
cout << min(d % x, x - d % x) << endl;
}
D-Dominoes!
题意
一张多米诺骨牌的左右两端各有一个数字,给出张这样的多米诺骨牌,是否存在一个可以另所有相邻且不在同一张牌上的两个数字不一样的序列。
数据范围
思路
首先注意到当 某个数字的数量大于等于时,一定没有合法的排列。
而如果符合,一定有合法序列。
如果所有骨牌都满足,则只要满足每次从两侧加入的牌相邻没有相同数字即可,一定有合法序列。
优先考虑排列的骨牌。将的骨牌按照数量从大到小排序,每次选择一张牌,假设这张牌的数字是,之后选择现存数量最多的且符合的一张牌,放在右侧,接下来继续取两侧相同骨牌中数目最多的,若两侧相同骨牌数目不足种,则加入任意两侧不同的牌,注意接触端的条件
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 2e5 + 50;
struct Domi {
ll x, y;
bool operator==(const Domi& d)const {
return tie(x, y) == tie(d.x, d.y);
}
bool operator!=(const Domi& d)const {
return tie(x, y) != tie(d.x, d.y);
}
bool operator<(const Domi& d)const {
return tie(x, y) < tie(d.x, d.y);
}
};
bool compare(const pair<ll, Domi>& a, const pair<ll, Domi>& b) {
if (a.first == b.first)return a.second < b.second;
return a.first > b.first;
}
void solve() {
ll n;cin >> n;
vector<Domi>v, w;
map<ll, ll>cnt;
for (ll i = 1;i <= n;i++) {
ll x, y;cin >> x >> y;
if (x > y) { x = x ^ y, y = x ^ y, x = x ^ y; }
cnt[x]++, cnt[y]++;
if (x == y)
v.push_back({ x,y });
else
w.push_back({ x,y });
}
ll lv = v.size();
ll lw = w.size();
for (auto i : cnt) {
if (i.second >= n + 2) {
cout << "No\n";
return;
}
}
cout << "Yes\n";
if (n == 1) {
struct Domi d;
if (v.empty()) {
d = w.back();
}
else {
d = v.back();
}
cout << d.x << " " << d.y << "\n";
return;
}
sort(v.begin(), v.end());
ll c = 1;
priority_queue<pair<ll, Domi>>pris;
for (ll i = 1;i < lv;i++) {
if (v[i] != v[i - 1]) {
pris.push({ c,v[i - 1] });
c = 1;
}
else {
c++;
}
}
if (lv) {
pris.push({ c,v.back() });
}
vector<Domi>ans;
ll pw = 0;
ll las = -1;
while (!pris.empty() || pw < lw) {
if (pris.empty()) {
struct Domi x = w[pw++];
if (x.x == las) {
x = { x.y,x.x };
}
ans.push_back(x);
las = x.y;
continue;
}
auto [cntx, x] = pris.top();pris.pop();
if (las != x.x) {
cntx--;
if (cntx) {
pris.push({ cntx,x });
}
ans.push_back(x);
las = x.y;
continue;
}
if (pris.empty()) {
pris.push({ cntx,x });
x = w[pw++];
if (x.x == las) {
x = { x.y,x.x };
}
ans.push_back(x);
las = x.y;
continue;
}
auto [cnty, y] = pris.top();pris.pop();
pris.push({ cntx,x });
cnty--;
if (cnty) {
pris.push({ cnty,y });
}
ans.push_back(y);
las = y.y;
}
for (auto i : ans) {
cout << i.x << " " << i.y << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;cin.get();
while (t--)solve();
return 0;
}
E-Malfunctioning Typewriter
题意
有一台打字机,对于每次键入的字符(只有0
或1
),每次有p
的概率正确键入,有句长度为的不会重复的诗句,这些诗句可以以任意顺序组合成一首长度为的诗。询问在打字这首诗的时候,有多大的概率成果打出。
数据范围
思路
将句诗建立字典树,对每个节点统计以该结点为前缀的字符串数目。
考虑到只有01
,每个节点下最多只有0
或1
的子节点。记某个节点为,将节点前缀打对的概率可以预处理(表示打对个,个的概率),打对一整首诗歌是遍历完字典树上的每个字符串次,对于字典树上的某个非叶子节点,在遍历时,经过该结点的数目是它的子树中有多少个叶子结点决定的,假设有是从该节点(表示一个前缀)往后以1
为根节点的子树中的叶子结点数目,是以0
为根节点的子树中叶子结点的数目,那么在决策当前是否正确输出了0/1
的概率是(相当于在当前节点输出正确的个1
和个0
的概率代表这一步打字输出了个合法下行子串的概率),故总概率是。
对的预处理:
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double ld;
const ll maxn = 1010;
int node = 0;
int nxt[maxn * maxn][2];
int sz[maxn * maxn];
void triInsert(string s) {
int n = s.size();
int p = 0;
for (int i = 0;i < n;i++) {
int c = s[i] - '0';
if (!nxt[p][c])nxt[p][c] = ++node;
sz[nxt[p][c]]++; // 以这个点为前缀的字符串数目
p = nxt[p][c];
}
}
ld f[maxn][maxn];
void solve() {
int n, m;ld p;
cin >> n >> m >> p;
vector<string>st(n);
for (int i = 0;i < n;i++) {
string s;cin >> s;
triInsert(s);
}
f[0][0] = 1.0; // 打出0个1,0个0概率为1
for (int x = 0;x <= n;x++) {
// f(x,y):恰好正确打出x个1,y个0
for (int y = 0;y <= n;y++) {
if (!x && !y)continue;
ld w = x ? f[x - 1][y] : 0.0;
ld v = y ? f[x][y - 1] : 0.0;
f[x][y] = max(
p * w + (1 - p) * v,
p * v + (1 - p) * w
);
}
}
ld ans = 1.0;
for (int i = 0;i <= node;i++) {
ans *= f[sz[nxt[i][0]]][sz[nxt[i][1]]];
}
printf("%.12lf", ans);
}
int main() {
int _ = 1;
// cin >> _;cin.get();
while (_--)
solve();
return 0;
}
J-Rigged Games
题意
比赛由大分比和小分比一同决定最终的胜负,小分比Bo2a-1
:先胜出a
场为胜。大分比Bo2b-1
:先胜出b
场为胜。记1次小分比胜负为大分比的一场。
现在有一个长度为的01
字符串,字符串是无限循环重复的,用于记录每一次比赛的胜负,从01
字符串的每个位置开始比赛,询问谁会赢得整个比赛。
数据范围
思路
倍增思想,在01
循环串中,记录每个位置开始后进行Bo2a-1
小局的胜负,以及下一步从哪个位置开始。数组f[i][j]
表示从开始进行轮后到达的位置、对应的、的胜利次数。
在统计Bo2b-1
时,我们要找到第一次0或1胜利b
次以上的位置,将j
倒序处理,是最多获得终局的次数,是最少获得终局的次数。假设,需要至少局,至多局定胜负,注意到,在计数恰好局时,应当只统计先局,然后往后再统计局(这里先1后8也不会影响结果,只要保证不超过9局即可)。在计算Bo2b-1
的结果时,每次统计恰好。
代码
struct status {
int n0, n1;
int overpos;
}f[maxn][20]; // 从位置i开始结束j小局后的状态
void solve() {
ll n, a, b;cin >> n >> a >> b;
string s;cin >> s;
// 更新Bo2a-1的状态
int w0 = 0, w1 = 0; // 统计0/1胜利多少次
int pos = 0;
for (int i = 0;i < n;i++) {
// 位置i开始
while (w0 < a && w1 < a) { // 未出小局结果
if (s[pos] == '1')w1++;
else w0++;
pos = (pos + 1) % n;
}
if (w0 >= a) {
f[i][0] = { 1,0,pos };
}
else {
f[i][0] = { 0,1,pos };
}
if (s[i] == '1')w1--;
else w0--; // 下一次循环会从i+1开始,减去i位置的分
}
// 倍增结果
for (int j = 1;j < 20;j++) {
// 枚举进行了2^j局
for (int i = 0;i < n;i++) {
// 枚举开始位置
// 第i位置开始进行2^j局=第i开始进行2^(j-1)局+第i开始进行2^(j-1)局后的位置pos开始进行2^(j-1)局
int pos = f[i][j - 1].overpos;
f[i][j].overpos = f[pos][j - 1].overpos;
f[i][j].n0 = f[i][j - 1].n0 + f[pos][j - 1].n0;
f[i][j].n1 = f[i][j - 1].n1 + f[pos][j - 1].n1;
}
}
// 进行Bo2b-1的结果统计
for (int i = 0;i < n;i++) {
int w0 = 0, w1 = 0;
int tot = 0;
int pos = i;
for (int j = 0;j < 20;j++) {
// 从pos开始进行2^j次比赛,累计0/1的胜利次数
if ((2 * b - 1) >> j & 1) {
w0 += f[pos][j].n0;
w1 += f[pos][j].n1;
pos = f[pos][j].overpos;
}
}
// cout << w0 << " " << w1 << endl; // 出现第一个胜利b次时正确
if (w0 >= b)cout << '0';
else cout << '1';
}
cout << "\n";
}
L-Sudoku and Minesweeper
题意
给出一个的数独,在这个数独的基础上构造一个扫雷地图,将数独上的一些数字替换为*
,但是不能将所有数字都替换为*
,使得整个扫雷地图合法。
思路
在铺满地雷的地图上保留一些数字,数字8
表明该格周围都是*
,所以选择保留所有的8
,并再次遍历一遍所有的地图(注意边界),删掉不符合要求的8
。
或者另一个想法:中间的格子中中一定有一个数字8
,在去掉除了这个8
以外的所有数字之后,这个8
一定是符合要求的。
代码
char mp[10][10];
void solve() {
for (int i = 1;i <= 9;i++) {
for (int j = 1;j <= 9;j++) {
cin >> mp[i][j];
if (mp[i][j] != '8')mp[i][j] = '*';
}
}
vector<pair<int, int>>dxy = {
{-1,-1},{-1,0},{-1 ,1},
{0,-1},{0 ,1},
{1,-1},{1,0},{1 ,1},
};
for (int i = 1;i <= 9;i++) {
for (int j = 1;j <= 9;j++) {
if (mp[i][j] == '*')continue;
int x = mp[i][j] - '0';
int y = 0;
for (int k = 0;k < 8;k++) {
int ii = i + dxy[k].first, jj = j + dxy[k].second;
if (ii >= 1 && ii <= 9 && jj >= 1 && jj <= 9)
if (mp[ii][jj] == '*') { y++; }
}
if (x != y)mp[i][j] = '*';
}
}
for (int i = 1;i <= 9;i++) {
for (int j = 1;j <= 9;j++) {
cout << mp[i][j];
}
cout << "\n";
}
}