一些废话
不喜欢这次的牛客比赛,打得太抽象了,除了题目带给我的感觉很不顺手以外,打的人很少也没什么意思了,不,主要还是自己太🍬了,怎么连一个二维前缀和都想不到呢,妈的,直接卡死在第四题,同时,我感觉这次没什么很有意思的题,真的很想念以前的牛客,倒也没有很久以前,也就前几个月,多有意思啊 (´;д;`)
A 运动会
题意
给出一段由 ‘_‘, ‘|’, ‘=’ 构成的字符串,分别为跑道,支柱,杆,由两个支柱,中间有若干个非零的杆构成一个栏架,问,一共有多少个合法的栏架
思路
,每次遇到支柱就往后面找杆,若有连续的杆,紧接着的又是支柱,则为一个合法的栏架
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = ' ' + s;
int ans = 0;
for (int i = 1, j = 1; i <= n; ++i) {
if (s[i] != '|')
continue;
j = max(i, j);
while (j < n && s[j + 1] == '=') {
++j;
}
if (j < n && s[j] == '=' && s[j + 1] == '|') {
++ans;
}
i = j;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
B 数方格
题意
给出 n 行 m 列的网格和一个数 x,每个网格都有其非负权值,请你选择一行和一列,总共 n + m – 1 个网格,这些网格的权值异或和为 x,你可以最多修改其中一个网格的权值
思路
没啥好说的,直接选第一行第一列,求出除开 一行一列其余的异或和为 ,设 , 最后要使得 , 根据异或运算的可逆性,,然后修改 即可
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
int n, m, x, ans =0 ;
cin >> n >> m;
vector<vector<int>> g(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> g[i][j];
}
}
cin >> x;
for (int i = 2; i <= n; ++i) {
ans ^= g[i][1];
}
for (int j = 2; j <= m; ++j) {
ans ^= g[1][j];
}
ans ^= x;
g[1][1] = ans;
cout << "YES" << endl;
cout << 1 << " " << 1 << " " << ans << endl;
cout << 1 << " " << 1 << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
C 列竖式
题意
给出两个小数,求出这两个数的相加的进位次数,保证数的长度小于等于 10
思路
直接用高精度相加的做法,每次记录一下进位的数字为多少,在下一次加上即可,对了,记得统一位数
这可能就是我不大喜欢这次比赛的原因吧,这种题出的目的是为了啥,有模版直接抄就行,我宁愿你出点陷阱题,把我们当成傻子耍也行啊,我现在都记得 有道题说什么对 66579 取模,但其实答案根本达不了这么大,纯纯误导
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
string s, t;
cin >> s >> t;
int sa = 0, sb = 0, ta = 0, tb = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] == '.') {
break;
}
++sa;
}
for (int i = s.size() - 1; i >= 0; --i) {
if (s[i] == '.')
{
break;
}
++sb;
}
for (int i = 0; i < s.size(); ++i)
{
if (t[i] == '.')
{
break;
}
++ta;
}
for (int i = s.size() - 1; i >= 0; --i)
{
if (t[i] == '.')
{
break;
}
++tb;
}
for (int i = 1; i <= 10 - sa; ++i) {
s = '0' + s;
}
for (int i = 1; i <= 10 - sb; ++i) {
s.push_back('0');
}
for (int i = 1; i <= 10 - ta; ++i)
{
t = '0' + t;
}
for (int i = 1; i <= 10 - tb; ++i)
{
t.push_back('0');
}
// cout << s << endl;
// cout << t << endl;
int m = s.size(), pre =0 , ans = 0;
for (int i = m - 1; i >= 0; --i) {
if (s[i] == '.')
continue;
int a = s[i] - '0';
int b = t[i] - '0';
int c = a + b + pre;
if (c >= 10) {
++ans;
}
pre = c / 10;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
D 走迷宫
题意
给出一个 n 行 m 列的网格,起点 S,终点 E,‘.’ 代表可通过的方格,即路,’#’ 代表不可通过的方格,即墙,这道题相对于常规的 BFS 的不同之处在于他还设置了个 a 行 b 列的矩形,起点在此矩形的左上角,每次移动,此矩形要进行相同方向和距离的移动,墙不可在此矩形以内,若最后能使得终点 E 在此矩形的左上角,则输出 Yes,否则输出 No
思路
对于常规的 BFS ,由于每个点可能都要走一遍,从最坏的来看,时间复杂度已经达到 ,而这个每次都要判断一行或者一列中是否有墙,时间复杂度就达到了 ,一般上限是 8.3e ,这个肯定是过不了的,我当时就卡在这儿,怎么就没想到 呢?直接 判断,然后时间复杂度又回到了 ,包过的
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PII pair<int, int>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};
void solve() {
int n, m, a, b;
cin >> n >> m >> a >> b;
vector<vector<int>> s(n + 1, vector<int>(m + 1, 0));
int sx, sy, tx, ty;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
char ch;
cin >> ch;
if (ch == 'S') {
sx = i;
sy = j;
}
if (ch == 'E') {
tx = i;
ty = j;
}
if (ch == '#') {
s[i][j] = 1;
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
s[i][j] += s[i - 1][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
s[i][j] += s[i][j - 1];
}
}
auto area = [&](int x1, int y1, int x2, int y2)
{
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] > 0;
};
auto in = [&](int x, int y)
{
return x >= 1 && x <= n && y >= 1 && y <= m;
};
vector<vector<int>> vis(n+ 1,vector<int>(m+ 1, 0));
queue<PII> q;
q.push({sx, sy});
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
if (x == tx && y == ty) {
cout << "Yes" << endl;
return;
}
if (vis[x][y]) continue;
vis[x][y] = 1;
for (int i = 0; i < 4; ++i)
{
int fx1 = x + dx[i];
int fy1 = y + dy[i];
int fx2 = fx1 + a - 1;
int fy2 = fy1 + b - 1;
if (!in(fx1, fy1) || !in(fx2, fy2)) continue;
if (area(fx1, fy1, fx2, fy2)) continue;
q.push({fx1, fy1});
}
}
cout << "No" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
E 跷跷板
题意
有 个同学在玩跷跷板,木板左端点为 ,右端点为 ,第 个同学在 ,体重为 ,要想让跷跷板平衡,需满足以下公式:( 为整数)
每次需减少一个同学,让剩余同学仍能让跷跷板平衡,问有多少种方案数
思路
我是傻逼,我当时以为那个求和符号会管到后面所有,也就是 这一部分,右边同理
OK,正确做法就是公式化简
令 ,得
所以,只要该式子能被整除,则说明存在可行的
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
LL n, l, W;
cin >> n >> l >> W;
vector<LL> x(n+ 1, 0), w(n + 1, 0);
__int128 S = 0, T = 0;
for (LL i = 1; i <= n; ++i) {
cin >> x[i] >> w[i];
S += w[i];
T += x[i] * w[i];
}
LL ans = 0;
for (LL i = 1; i <= n; ++i) {
__int128 s = S - w[i];
__int128 t = T - x[i] * w[i];
__int128 up = W * l + 2 * t;
__int128 down = (s + W) * 2;
if (up % down == 0) {
++ans;
}
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
F 解方程
题意
给出一个由 个数构成的数组 ,给出一个整数 ,你需要从数组 中选取 个数,求得他们的最大公约数,问,在保证最大公约数尽可能大的情况下有多少种选择方案
思路
我当时第一反应是用二分,先枚举最大公约数是多少,然后看有多少个数满足这个最大公约数的倍数,然后,得满足的个数为 ,从 m 中选取 个,求 即可。在这 个中随便选取 个都是一种方案,不可能会使最大公约数更大或者更小。假设会更大,那么最大公约数就不可能是该数,一定会更大;假设会更小,这些数一定都是最大公约数的倍数,不可能会更小
但是,用二分的话,他的总体是可以二分的,但是并不是说大的可以,比他小的就都可以。因为这是倍数,你不知道哪个约数会出现在答案中,他不是像一个一个加,一个一个减的那种
但是,我们可以利用这种思路,预处理出每个数的约数,时间复杂度为 ,记录约数出现次数,之后有 次询问,预处理出每个 对应的答案,从大到小枚举约数,看第一次约数出现次数大于等于 的约数, 用差分去处理,如果当前出现的次数为 ,则之后出现的 才会更新,因为后面的约数更小,若出现次数也更小,断然不会取他出现的次数,只有后面更小的约数,但出现的次数更大才会被更新, 记录上一次最大次数,然后更新 这段为 。若出现次数为 ,答案即为 ,预处理时间复杂度为 ,整体时间复杂度为
代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long LL;
const LL MAX = 2e5 + 100;
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1, 0), cnt(MAX, 0);
auto pre = [&](int x)
{
for (int i = 1; i * i <= x; ++i)
{
if (x % i == 0)
{
++cnt[i];
if (x / i != i)
{
++cnt[x / i];
}
}
}
};
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
pre(a[i]);
}
int mx = 0;
vector<int> d(MAX + 2, 0);
for (int i = MAX; i >= 1; --i)
{
if (cnt[i] > mx)
{
d[mx + 1] += cnt[i];
d[cnt[i] + 1] -= cnt[i];
}
mx = max(mx, cnt[i]);
}
vector<int> ans(MAX + 1, 0);
for (int i = 1; i <= MAX; ++i)
{
ans[i] = ans[i - 1] + d[i];
}
int q;
cin >> q;
while (q--)
{
int k;
cin >> k;
cout << comb.C(ans[k], k) << endl;
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
优化代码
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;
const int MAXA = 200000;
int freq[MAXA + 1];
int cnt[MAXA + 1];
int best[200005];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
freq[x]++;
}
// cnt[d] = 能被 d 整除的数的个数
for (int d = 1; d <= MAXA; d++) {
for (int j = d; j <= MAXA; j += d)
cnt[d] += freq[j];
}
int ptr = 0;
for (int d = MAXA; d >= 1; d--) {
while (ptr < cnt[d]) {
++ptr;
best[ptr] = d;
}
}
int q;
cin >> q;
while (q--) {
int k;
cin >> k;
cout << comb.C(cnt[best[k]], k).val() << '\n';
}
return 0;
}组合数+取模类
using i64 = long long;
template <class T>
constexpr T power(T a, i64 b)
{
T res = 1;
for (; b; b >>= 1, a *= a)
{
if (b & 1)
{
res *= a;
}
}
return res;
}
template <int P>
struct MInt
{
int x;
constexpr MInt() : x(0) {}
constexpr MInt(i64 v)
{
x = v % P;
if (x < 0)
{
x += P;
}
}
constexpr int val() const
{
return x;
}
constexpr MInt inv() const
{
return power(*this, P - 2);
}
constexpr MInt &operator+=(const MInt &rhs)
{
x += rhs.x;
if (x >= P)
{
x -= P;
}
return *this;
}
constexpr MInt &operator-=(const MInt &rhs)
{
x -= rhs.x;
if (x < 0)
{
x += P;
}
return *this;
}
constexpr MInt &operator*=(const MInt &rhs)
{
x = 1LL * x * rhs.x % P;
return *this;
}
constexpr MInt &operator/=(const MInt &rhs)
{
return *this *= rhs.inv();
}
friend constexpr MInt operator+(MInt a, const MInt &b)
{
return a += b;
}
friend constexpr MInt operator-(MInt a, const MInt &b)
{
return a -= b;
}
friend constexpr MInt operator*(MInt a, const MInt &b)
{
return a *= b;
}
friend constexpr MInt operator/(MInt a, const MInt &b)
{
return a /= b;
}
friend ostream &operator<<(ostream &os, const MInt &a)
{
return os << a.x;
}
friend istream &operator>>(istream &is, MInt &a)
{
i64 v;
is >> v;
a = MInt(v);
return is;
}
};
using Z = MInt<1000000007>;
struct Comb
{
int n;
vector<Z> fac_;
vector<Z> ifac_;
vector<Z> inv_;
Comb() : n(0), fac_(1, 1), ifac_(1, 1), inv_(1, 0) {}
void init(int m)
{
if (m <= n)
{
return;
}
fac_.resize(m + 1);
ifac_.resize(m + 1);
inv_.resize(m + 1);
for (int i = n + 1; i <= m; i++)
{
fac_[i] = fac_[i - 1] * i;
}
ifac_[m] = fac_[m].inv();
for (int i = m; i > n; i--)
{
ifac_[i - 1] = ifac_[i] * i;
inv_[i] = ifac_[i] * fac_[i - 1];
}
n = m;
}
Z fac(int m)
{
if (m > n)
{
init(2 * m);
}
return fac_[m];
}
Z ifac(int m)
{
if (m > n)
{
init(2 * m);
}
return ifac_[m];
}
Z inv(int m)
{
if (m > n)
{
init(2 * m);
}
return inv_[m];
}
Z C(int n, int m)
{
if (m < 0 || m > n)
{
return 0;
}
return fac(n) * ifac(m) * ifac(n - m);
}
Z P(int n, int m)
{
if (m < 0 || m > n)
{
return 0;
}
return fac(n) * ifac(n - m);
}
};
Comb comb;