牛客练习赛86题解
A - 取因数
- 偶数先手必胜,奇数后手必胜
- 奇数取完之后一定会变成偶数,偶数的话拿掉
就会变成奇数
- 这样回来还是偶数,总有一次会拿到
,然后就游戏结束了
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
if (n & 1) puts("Bob");
else puts("Alice");
return 0;
} B - A + B
因为K只取「0」,「1」,「2」,N最大为「100」,所以完全可以先本地跑出来,然后交表。
找到abcd型数字构造k=1型:abcd0abcd,如123401234,1230123等
找到"(int)(aaa)"+"(int)(aa+a)" 构造k=2型:如11112,11111122,1111111122等
找到"a","aa","aaa",构造k=0型:如1,11,111,1111,2,22,22222等
#include <bits/stdc++.h>
#define int long long
using namespace std;
vector<int> v[10];
vector<string> Ans[10];
signed main() {
for (int j = 1; j < 10; j++) {
int sum = 0;
for (int i = 0; i < 16; i++) {
sum = sum * 10 + j;
Ans[0].push_back(to_string(sum));
v[j].push_back(sum);
}
}
for (int i = 12345; i <= 12468; i++)
Ans[1].push_back(to_string(i) + "0" + to_string(i));
for (int i = 0; i < 10; i++) {
for (int j = 0; j + 1 < v[i].size(); j++) {
for (int k = j + 1; k < v[i].size(); k++) {
if (to_string(v[i][j]).length() + to_string(v[i][k]).length() + to_string(v[i][k] + v[i][j]).length() < 30)
Ans[2].push_back(to_string(v[i][j]) + to_string(v[i][k]) + to_string(v[i][k] + v[i][j]));
}
}
}
int m, n;
cin >> m >> n;
for (int i = 0; i < n; i++)
cout << Ans[m][i] << endl;
return 0;
} C - 取钱
- 先处理出不超过当前钞票面额的最多钞票数
- 对于每个输入,二分查找,然后加上差值这部分就好了
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define LASSERT(X) assert(X)
#else
#define LASSERT(X) {}
#endif
template <typename T>
T input() {
T res;
cin >> res;
LASSERT(cin);
return res;
}
template <typename IT>
void input_seq(IT b, IT e) {
std::generate(b, e, input<typename std::remove_reference<decltype(*b)>::type>);
}
#define ALL(data) data.begin(),data.end()
#define TYPEMAX(type) std::numeric_limits<type>::max()
int main() {
std::iostream::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
const int64_t inf = TYPEMAX(int64_t) / 3;
int n = input<int>();
vector<int64_t> a(n);
input_seq(ALL(a));
a.push_back(inf);
vector<int64_t> b(n + 1), btake(n + 1);
b[0] = 0;
btake[0] = 0;
for (int i = 1; i <= n; ++i) {
int64_t res = b[i - 1];
int64_t can = (a[i] - 1 - res) / a[i - 1];
b[i] = res + can * a[i - 1];
btake[i] = btake[i - 1] + can;
}
for (int m = input<int>(); m != 0; --m) {
int64_t s = input<int64_t>();
int i = std::upper_bound(ALL(b), s) - b.begin() - 1;
assert(0 <= i and i < n);
int64_t res = b[i];
int64_t take = btake[i];
int64_t can = (a[i + 1] - 1 - res) / a[i];
can = (s - res) / a[i];
res += can * a[i];
take += can;
cout << res << " " << take << "\n";
}
return 0;
} D - 翻转数列
- 如果我们每次反转都多制造出两组要求的数对,效率就是最高的,当两组两组翻到无法再多制造出两组后,就不断多翻出一组直到不能再多为止
- 假设
是出现最多的数字出现的次数,若
,则最多可以有
组,否则最多可以有
组
- 问题回到如何在一次反转多制造出两组?
- 找到类似这样的区间
,反转中间就可以得到
- 因此我们可以对一开始的序列统计每种数字相邻且相同的数对个数
- 之后问题就转化为了:给定一正整数序列,你每次可以挑两个大于零的数字同时减一,问最多可以进行几次操作?
- 每次找最大的数字和随意的数字同时减一,就可以得到答案
- 做完以上繁琐的操作之后,我们可以知道:
- 原本有
组相邻且不同的数对,两组两组增加最多可以执行
次,最终最多可以有
组相邻且不同的数对
- 最后,做
次反转最多可以有几组数对就能通过简单的判断得到了
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<int> v(n);
for (int i = 0; i < n; ++i) cin >> v[i];
for (int i = 0; i < n; ++i) --v[i];
vector<int> cnt(n, 0);
int ans = 0;
for (int i = 1; i < n; ++i) if (v[i - 1] == v[i]) ++cnt[v[i]]; else ++ans;
multiset<int> st(cnt.begin(), cnt.end());
st.erase(0);
while (st.size() > 1u && k > 0) {
int x = *st.begin();
st.erase(st.begin());
int y = *prev(st.end());
st.erase(prev(st.end()));
--x, --y, --k, ans += 2;
if (x) st.insert(x);
if (y) st.insert(y);
}
if (st.size() && k) {
int z = min(*st.begin(), k);
ans += z;
k -= z;
}
cnt = vector<int>(n, 0);
for (int i = 0; i < n; ++i) ++cnt[v[i]];
sort(cnt.rbegin(), cnt.rend());
ans = min(min(ans, n - 1), (n - cnt[0]) * 2);
cout << ans << endl;
} E - 排列
- 排序
个东西需要
次比较,但是数列的比较需要
吗?
- 显然我们无法存下所有的排列,就像字符串比较,我们可以通过
得到
的数比较
- 但是
也需要
的空间,并没有解决另外一个问题
- 注意到相邻的两个数列只差一丢丢
- 可持久化
,交换两个数字相当于两个单点修改,
查询是前缀查询
- 可以用可持久化线段树/BIT,
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
#define IOS ios_base::sync_with_stdio(0); cin.tie(0)
#define ALL(a) a.begin(),a.end()
#define SZ(a) ((int)a.size())
#define F first
#define S second
#define REP(i, n) for(int i=0;i<((int)n);i++)
#define pb push_back
#define MP(a, b) make_pair(a,b)
template<typename T1, typename T2>
ostream &operator<<(ostream &out, pair<T1, T2> P) {
out << '(' << P.F << ',' << P.S << ')';
return out;
}
template<typename T>
ostream &operator<<(ostream &out, vector<T> V) {
REP(i, SZ(V)) out << V[i] << ((i != SZ(V) - 1) ? " " : "");
return out;
}
const ll maxn = 100005;
const ll MOD = ll(1e9 + 7);
const ll pp = 880301;
const ll P = 31;
ll mypow(ll a, ll b) {
ll res = 1LL;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
pll operator+(pll A, pll B) {
return MP((A.F + B.F >= MOD) ? (A.F + B.F - MOD) : (A.F + B.F),
(A.S + B.S >= MOD) ? (A.S + B.S - MOD) : (A.S + B.S));
}
pll operator-(pll A, pll B) {
return MP((A.F >= B.F) ? (A.F - B.F) : (A.F - B.F + MOD), (A.S >= B.S) ? (A.S - B.S) : (A.S - B.S + MOD));
}
pll operator*(pll A, pll B) {
return MP(A.F * B.F % MOD, A.S * B.S % MOD);
}
pll operator*(ll A, pll B) {
return MP(A * B.F % MOD, A * B.S % MOD);
}
pll operator*(pll A, ll B) {
return B * A;
}
pll ex[maxn];
pll invex[maxn];
struct node {
pll val;
node *lc, *rc;
node(pll _val, node *_lc, node *_rc) : val(_val), lc(_lc), rc(_rc) {}
node(pll _val) : val(_val), lc(0), rc(0) {}
};
int n, m;
ll a[maxn];
int x[maxn], y[maxn];
node *build(int l, int r) {
if (r - l == 1) {
return new node(ex[l] * a[l]);
}
int mid = (l + r) / 2;
node *ret = new node(MP(0LL, 0LL), build(l, mid), build(mid, r));
ret->val = ret->lc->val + ret->rc->val;
return ret;
}
node *mdf(node *root, int l, int r, int p, pll val) {
if (p < l or r <= p) return root;
if (r - l == 1) {
return new node(root->val + val);
}
int mid = (l + r) / 2;
node *ret = new node(MP(0LL, 0LL), mdf(root->lc, l, mid, p, val), mdf(root->rc, mid, r, p, val));
ret->val = ret->lc->val + ret->rc->val;
return ret;
}
node *root[maxn * 2];
bool cmp(int s, int t) {
if (root[s + s]->val == root[t + t]->val) {
return s < t;
}
int l = 0, r = n;
node *ss = root[s + s], *tt = root[t + t];
while (l < r - 1) {
int mid = (l + r) / 2;
if (ss->lc->val == tt->lc->val) {
ss = ss->rc;
tt = tt->rc;
l = mid;
} else {
ss = ss->lc;
tt = tt->lc;
r = mid;
}
}
return ss->val * invex[l] < tt->val * invex[l];
}
int main() {
IOS;
cin >> n >> m;
ex[0] = MP(1, 1);
for (int i = 1; i < maxn; i++) ex[i] = ex[i - 1] * MP(pp, P);
invex[0] = MP(1, 1);
invex[1] = MP(mypow(pp, MOD - 2), mypow(P, MOD - 2));
for (int i = 2; i < maxn; i++) invex[i] = invex[i - 1] * invex[1];
for (int i = 0; i < n; i++) cin >> a[i];
REP(i, m - 1) {
cin >> x[i] >> y[i];
x[i]--;
y[i]--;
}
root[0] = build(0, n);
for (int i = 1; i < m; i++) {
root[i * 2 - 1] = mdf(root[i * 2 - 2], 0, n, x[i - 1], (a[y[i - 1]] - a[x[i - 1]] + MOD) % MOD * ex[x[i - 1]]);
root[i * 2] = mdf(root[i * 2 - 1], 0, n, y[i - 1], (a[x[i - 1]] - a[y[i - 1]] + MOD) % MOD * ex[y[i - 1]]);
swap(a[x[i - 1]], a[y[i - 1]]);
}
vector<int> ans;
REP(i, m) ans.pb(i);
sort(ALL(ans), cmp);
REP(i, m) cout << ans[i] << " \n"[i == m - 1];
return 0;
} F - 字符删除
- 考虑字符
,若
被保留在最后的答案中,代表
之后的字符是用
删除的
- 如果一个段
可以用
删除,那么
也可以用
删除
- 所以,对于每个位置
,就必须找到使
可以删除的最大长度
- 这个计算长度部分可以dp也可以用栈,我用的栈
- 然后问题就转变为将字符串
转化为
段,每段都可以用
删除
- 我们不在
存答案,存
,就是答案的第二个字符形成的索引
- 那么
的链接形成一棵悬浮的树,每个
都是由顶点
到根的路径
- 在这棵树上,可以计算二元提升,并为每个二元提升计算hash并做字典序比较,找到它们的最大匹配前缀,然后比较下一个字符
#include <cstdio>
#include <string>
#include <vector>
using namespace std;
const int L = 20;
const int N = (int) 1e6 + 10;
const int M = 26;
const int H = 2;
const int MOD = (int) 1e9 + 7;
const int P[] = {17239, 23917};
int n, m, best[N], go[L][N], ppow[H][N];
bool can[M][M];
char ch, s[N];
struct data {
int len, h[H];
data() {}
data(char c): len(1) {
for (int ih = 0; ih < H; ++ih) {
h[ih] = c;
}
}
} str[L][N];
data operator+(const data& a, const data& b) {
data c;
c.len = a.len + b.len;
for (int ih = 0; ih < H; ++ih) {
c.h[ih] = (a.h[ih] * 1LL * ppow[ih][b.len] + b.h[ih]) % MOD;
}
return c;
}
bool operator==(const data& a, const data& b) {
if (a.len != b.len) return false;
for (int ih = 0; ih < H; ++ih) {
if (a.h[ih] != b.h[ih]) return false;
}
return true;
}
int compare_lex(int si, int sj) {
int i = si, j = sj;
for (int k = L - 1; k >= 0; --k) {
if (str[k][i] == str[k][j]) {
i = go[k][i];
j = go[k][j];
}
}
if ((i == n) || (j == n)) return (i == n) ? si : sj;
return (s[i] < s[j]) ? si : sj;
}
int main() {
scanf("%d%d", &m, &n);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
scanf(" %c", &ch), can[i][j] = (ch == '1');
}
}
scanf("%s", s);
for (int ih = 0; ih < H; ++ih) {
ppow[ih][0] = 1;
for (int i = 0; i < n; ++i) {
ppow[ih][i + 1] = (ppow[ih][i] * 1LL * P[ih]) % MOD;
}
}
vector<int> cur;
go[0][n] = n;
for (int i = n - 1; i >= 0; --i) {
best[i] = i + 1;
while (!cur.empty() && can[s[i] - 'a'][s[cur.back()] - 'a']) {
best[i] = compare_lex(best[cur.back()], best[i]);
cur.pop_back();
}
cur.push_back(i);
go[0][i] = best[i];
str[0][i] = data(s[i]);
for (int j = 1; j < L; ++j) {
go[j][i] = go[j - 1][go[j - 1][i]];
str[j][i] = str[j - 1][i] + str[j - 1][go[j - 1][i]];
}
}
for (int i = 0; i < n; i = best[i]) printf("%c", s[i]);
return 0;
}
查看2道真题和解析