目录
题目 | 难度 |
---|---|
A. scx 的散文诗句 | *1200 |
B. 喵喵喵 | *2000 |
C. 猫猫大队 | *800 |
D. 无限的韵律源点 | *1700 |
E. 樱花下落的速度 | *2000 |
F. 奕!悟! | *2200 |
G. 怯战蜥蜴 | *1200 |
H. To the Moon, Finding Paradise | *1600 |
I. fumo 星 | *1500 |
J. 上春山 | *1400 |
K. BiuBiuBiu | *2400 |
L. 南湖少年团 | *800 |
M. 上帝造题的八分钟 | *1800 |
A. scx 的散文诗句
预估难度:*1200
贪心。很显然,最优的策略一定是尽可能让正数和正数分为一组,负数和负数分为一组。如果正数的数量和负数的数量均为奇数,需要最小化分组后剩下的正数和负数的绝对值。
一种想法是将正数和负数分别放进一个 vector 中并分别排序,然后分类讨论进行处理。也可以在 为奇数时往序列中加一个 (因为让最后剩下的一个数和 相乘与扔掉这个数是等价的),排序后每连续的两个数分组求和即可。
参考代码:
#include <bits/stdc++.h>
using i64 = long long;
void solve()
{
int n;
std::cin >> n;
std::vector<i64> arr;
for (int i = 1; i <= n; i++)
{
i64 x;
std::cin >> x;
arr.push_back(x);
}
if (n % 2)
arr.push_back(0);
std::sort(arr.begin(), arr.end());
i64 ans = 0;
for (int i = 0; i + 1 < arr.size(); i += 2)
ans += arr[i] * arr[i + 1];
std::cout << ans << '\n';
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0);
int _;
std::cin >> _;
while (_--)
solve();
return 0;
}
B. 喵喵喵
预估难度:*2000
莫比乌斯反演。记 出现的次数为 ,所求式即为 。
我们可以通过预处理前缀和快速计算
设 为数组 中最大的数,线性筛求出莫比乌斯函数时间复杂度为 ,预处理前缀和为 ,总体时间复杂度为 。
参考代码:
#include <bits/stdc++.h>
#define MAXN 100007
#define MAXM 5007
#define MOD 1000000007
typedef long long ll;
using namespace std;
ll X, Y, Z;
ll c[MAXN], a[MAXN], maxn;
ll mu[MAXN], sum_mu[MAXN], sum[MAXN];
bool isnp[MAXN];
vector<ll> prime;
void init_mu(ll limit)
{
mu[1] = 1;
for (ll i = 2; i <= limit; i++)
{
if (!isnp[i])
prime.push_back(i), mu[i] = (-1LL + MOD) % MOD;
for (ll p : prime)
{
if (p * i > limit)
break;
isnp[p * i] = 1;
if (i % p == 0)
{
mu[i * p] = 0;
break;
}
else
mu[i * p] = (mu[i] * mu[p] % MOD + MOD) % MOD;
}
}
for (ll p = 1; p <= limit; p++)
for (ll T = p; T <= limit; T += p)
sum_mu[T] = ((sum_mu[T] + mu[T / p] * (((X * p + Y) / Z) % MOD) % MOD) + MOD) % MOD;
for (ll T = 1; T <= limit; T++)
for (ll i = 1; i * T <= limit; i++)
sum[T] = (sum[T] + i * c[T * i] % MOD + MOD) % MOD;
for (ll i = 1; i <= limit; i++)
sum[i] = (sum[i] * sum[i] % MOD + MOD) % MOD;
}
ll solve()
{
ll ans = 0;
for (ll i = 1; i <= maxn; i++)
ans = (ans + i * i % MOD * sum[i] % MOD * sum_mu[i] % MOD + MOD) % MOD;
return (ans % MOD + MOD) % MOD;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
ll n;
cin >> n;
cin >> X >> Y >> Z;
for (ll i = 1; i <= n; i++)
{
cin >> a[i];
c[a[i]]++;
maxn = max(maxn, a[i]);
}
init_mu(maxn);
cout << solve() << '\n';
return 0;
}
C. 猫猫大队
预估难度:*800
签到题。按题意模拟即可。
参考代码:
#include <bits/stdc++.h>
void solve()
{
int n;
std::cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++)
{
int x;
std::cin >> x;
ans ^= x;
}
std::cout << (ans ? "miao" : "zzy") << '\n';
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0);
solve();
return 0;
}
D. 无限的韵律源点
预估难度:*1700
本题的重点在于 recent best部分,即指定下标和指定长度的区间 k 最大值,这实际上是区间 k 最值的***版。因此可以选择主席树,划分树等数据结构直接维护。由于本题固定长度的限制,实际上可以借助对顶堆,通过滑动窗口的思想直接维护。我们只需要维护两个堆,一个用来保存当前下标的 个最大值,另一个用来保存 到 大值即可。
也可以直接用set替代堆,实现两个部分的维护。
参考代码 :
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
struct nodeA
{
int val;
int pos;
nodeA(int x = 0, int y = 0) : val(x), pos(y){};
bool operator<(const nodeA &x) const
{
return val > x.val;
}
};
struct nodeB
{
int val;
int pos;
nodeB(int x = 0, int y = 0) : val(x), pos(y){};
bool operator<(const nodeB &x) const
{
return val < x.val;
}
};
int a[N];
template <typename node>
struct rheap
{
priority_queue<node> q;
int sz;
bool in[N];
int sum;
rheap()
{
memset(in, 0, sizeof in);
}
void push(int val, int pos)
{
if (!in[pos])
{
q.push(node(val, pos));
in[pos] = 1;
sz++;
sum += val;
}
}
node top()
{
node res;
while (in[(res = q.top()).pos] == 0)
{
q.pop();
}
return res;
}
int get_sum()
{
return sum;
}
node pop()
{
node res;
while (in[(res = q.top()).pos] == 0)
q.pop();
q.pop();
sz--;
in[res.pos] = 0;
sum -= res.val;
return res;
}
void del(int x)
{
if (in[x])
{
sz--, in[x] = 0;
sum -= a[x];
}
}
bool exist(int pos)
{
return in[pos];
}
bool empty()
{
return !sz;
}
};
rheap<nodeA> Rah;
rheap<nodeB> Rbh;
rheap<nodeA> Bch;
signed main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
int n, b, ra, rb;
cin >> n >> b >> ra >> rb;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
int ans = 0;
for (int i = 1; i <= n; ++i)
{
Bch.push(a[i], i);
if (Bch.sz > b)
{
Bch.pop();
}
if (i <= ra)
{
Rah.push(a[i], i);
if (Rah.sz > rb)
{
nodeA tmp = Rah.pop();
Rbh.push(tmp.val, tmp.pos);
}
}
else
{
int del_pos = i - ra;
if (Rah.exist(del_pos))
{
Rah.del(del_pos);
if (Rbh.empty() || a[i] > Rbh.top().val)
{
Rah.push(a[i], i);
}
else
{
Rbh.push(a[i], i);
nodeB tmp = Rbh.pop();
Rah.push(tmp.val, tmp.pos);
}
}
else
{
Rbh.del(del_pos);
if (a[i] < Rah.top().val)
{
Rbh.push(a[i], i);
}
else
{
Rah.push(a[i], i);
nodeA tmp = Rah.pop();
Rbh.push(tmp.val, tmp.pos);
}
}
}
int res = Rah.sum + Bch.sum;
ans = max(res, ans);
}
cout << ans << '\n';
return 0;
}
参考代码 :
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
int n, b, ra, rb;
int a[N];
signed main()
{
cin >> n >> b >> ra >> rb;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
using PDI = pair<int, int>;
multiset<PDI> sb, sra, srb;
int ans = 0, suma = 0, sumb = 0;
for (int i = 1; i <= n; ++i)
{
sb.insert({a[i], i});
suma += a[i];
if (sb.size() > b)
{
suma -= (*sb.begin()).first;
sb.erase(sb.begin());
}
if (i > ra)
{
PDI del = {a[i - ra], i - ra};
if (sra.count(del))
{
sumb -= a[i - ra];
sra.erase(del);
if (!srb.empty())
{
PDI add = *srb.rbegin();
srb.erase(add);
sra.insert(add);
sumb += add.first;
}
}
else if (srb.count(del))
{
srb.erase(del);
}
}
PDI now = {a[i], i};
sra.insert(now);
sumb += a[i];
if (sra.size() > rb)
{
PDI del = *sra.begin();
sumb -= del.first;
sra.erase(del);
srb.insert(del);
}
ans = max(ans, suma + sumb);
}
cout << ans << '\n';
return 0;
}
E. 樱花下落的速度
预估难度:*2000
一个小 trick 题。首先总结一个排列p对应的前缀 MEX 序列和后缀 MEX 的性质:前缀 MEX 序列 单调不减,且当且仅当遇到 时 ,后缀 MEX 序列单调不增,变化同理。因而我们可以通过前后缀 MEX 序列得到原排列部分固定位置的数。对于剩下不确定位置的数,它在原排列中的出现位置也是有限的:它必须出现在小于等于 的位置( 前面),也必须出现在小于等于 的位置( 后面),最终固定在一个区间内。可以发现,对于未知位置的数,越大的数对应的可放置区间越大。因而我们只需要从小到大放数字即可。公式为:
代表的是放置的第i个数的可放区间大小。接下来我们只需要求出每个未知位置数的 即可。可以直接在原顺序下查找每个未知位置的数右侧前缀 MEX 和左侧后缀 MEX(复杂度 ),也可以将所有原排列按照出现位置排序,根据区间的放缩得知每个数字的可放区间大小(复杂度 )。
参考代码 :
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
void solve_n()
{
int n;
cin >> n;
vector<int> o(n + 1, 0);
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
o[x + 1] = i;
}
int lef = n + 1, rig = 0;
int result = 1;
for (int i = 1; i <= n; ++i)
{
lef = min(lef, o[i]), rig = max(rig, o[i]);
if (o[i] != lef && o[i] != rig)
{
(result *= (rig - lef + 1) - (i - 1)) %= mod;
}
}
cout << result << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve_n();
return 0;
}
参考代码 :
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
void solve_nlogn()
{
int n, cnt = 0, vis = 0;
cin >> n;
map<int, int> mp, lef, rig;
set<int> cmp, need;
vector<int> fa(n + 1, 0), fb(n + 1, 0), a(n + 1, 0);
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
for (int i = 1; i <= n; ++i)
{
mp[a[i]] = 1;
while (mp.count(cnt))
++cnt;
fa[i] = cnt;
}
mp.clear(), cnt = 0;
for (int i = n; i; --i)
{
mp[a[i]] = 1;
while (mp.count(cnt))
++cnt;
fb[i] = cnt;
}
a = vector<int>(n + 1, -1);
fa.push_back(0);
fb.push_back(0);
mp.clear();
for (int i = 1; i <= n; ++i)
{
need.insert(i - 1);
if (fa[i] != fa[i - 1])
{
a[i] = fa[i - 1];
++vis;
need.erase(a[i]);
}
}
for (int i = n; i; --i)
{
if (fb[i] != fb[i + 1] && a[i] == -1)
{
a[i] = fb[i + 1];
++vis;
need.erase(a[i]);
}
}
cnt = 0;
for (int i = 1; i <= n; ++i)
{
if (a[i] == -1)
cnt++;
else
mp[fa[i]] = cnt;
}
for (int i = n; i; --i)
{
cmp.insert(fa[i]);
}
for (auto v : need)
{
auto p = lower_bound(cmp.begin(), cmp.end(), v);
rig[v] = mp[*p];
}
cnt = 0, mp.clear(), cmp.clear();
for (int i = n; i; --i)
{
if (a[i] == -1)
{
cnt++;
}
else
mp[fb[i]] = cnt;
}
for (int i = 1; i <= n; ++i)
{
cmp.insert(fb[i]);
}
for (auto v : need)
{
auto p = lower_bound(cmp.begin(), cmp.end(), v);
lef[v] = mp[*p];
}
cnt = 0;
int res = 1;
for (auto v : need)
{
res *= (rig[v] + lef[v] + vis - n - (cnt++));
res %= mod;
}
cout << res << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve_nlogn();
return 0;
}
F. 奕!悟!
预估难度:*2200
模拟题。
每次询问需要判断芙卡洛斯是否能在“黑——白——黑——白”的 步内判断芙卡洛斯是否能够取胜,而第 步为天理的落子阶段,对芙卡洛斯的获胜不产生影响。实际上我们只需要判断"黑——白——黑"的 步。为了叙述整洁,以下将上述 步分别称为第 步,第 步,第 步。
很显然暴力枚举每一步可能的位置会超时(因为棋盘上最多有 个落子的位置)。本题没有特意卡搜索剪枝的做法(因为验题的时候没人过这道题,导致没有搜索剪枝的样本),但是本题实际上可以在 的时间复杂度内通过(常数比较大)。
我们将连续的 个落子点,其中 个为黑子, 个没有任何棋子的情况称作黑子的即将获胜情况,这个没有任何棋子的落子点称为黑子的获胜点。类似地定义白子的即将获胜情况和白子的获胜点。
首先分析如果芙卡洛斯获胜,需要达成什么条件。最简单地,如果在第 步之前,当前局面已经存在获胜点,那么芙卡洛斯可以在第 步取胜。而如果不存在这样的局面,则需要考虑是否存在白子的获胜点,如果白子的获胜点的数量大于等于 ,芙卡洛斯就不可能获胜;如果白子的获胜点的数量等于 ,第 步落下的黑子必须落在白子的获胜点上;如果白子的获胜点的数量等于 ,第 步落下的黑子可以落在任何位置。在黑子落下第一步之后,考察当前局面上黑子的获胜点数量:如果黑子的获胜点的数量小于等于 ,那么芙卡洛斯无法获胜;否则芙卡洛斯可以获胜。
根据题意,首先需要判断当前局面天理是否已经达成获胜条件。如果没有,则继续判断芙卡洛斯是否已经达成获胜条件。如果没有,则计算黑子的获胜点:如果黑子的获胜点大于等于 ,那么芙卡洛斯可以获胜;否则判断白子的获胜点的数量,根据白子的获胜点的数量的不同分类讨论。
参考代码:
#include <bits/stdc++.h>
constexpr int N = 105 + 10;
int n, q;
char map[N][N];
int iskey[N][N]; // 黑棋可以获胜的点,下下去就五子连珠
struct opr
{
int x, y;
char ch, org;
};
inline bool validator(int x, int y)
{
return 1 <= x && x <= n && 1 <= y && y <= n;
}
bool check_iskey(int x, int y, char target)
{
if (map[x][y] == 'O')
return 0;
int cnt;
cnt = 0;
for (int d = -1; d >= -4; d--)
{
if (validator(x + d, y) == 0 || map[x + d][y] != target)
break;
cnt++;
}
for (int d = 1; d <= 4; d++)
{
if (validator(x + d, y) == 0 || map[x + d][y] != target)
break;
cnt++;
}
if (cnt >= 4)
{
return 1;
}
cnt = 0;
for (int d = -1; d >= -4; d--)
{
if (validator(x, y + d) == 0 || map[x][y + d] != target)
break;
cnt++;
}
for (int d = 1; d <= 4; d++)
{
if (validator(x, y + d) == 0 || map[x][y + d] != target)
break;
cnt++;
}
if (cnt >= 4)
{
return 1;
}
cnt = 0;
for (int d = -1; d >= -4; d--)
{
if (validator(x + d, y + d) == 0 || map[x + d][y + d] != target)
break;
cnt++;
}
for (int d = 1; d <= 4; d++)
{
if (validator(x + d, y + d) == 0 || map[x + d][y + d] != target)
break;
cnt++;
}
if (cnt >= 4)
{
return 1;
}
cnt = 0;
for (int d = -1; d >= -4; d--)
{
if (validator(x - d, y + d) == 0 || map[x - d][y + d] != target)
break;
cnt++;
}
for (int d = 1; d <= 4; d++)
{
if (validator(x - d, y + d) == 0 || map[x - d][y + d] != target)
break;
cnt++;
}
if (cnt >= 4)
{
return 1;
}
return 0;
}
bool check(int len, char target)
{ // 检查是否存在连续的len个target
for (int x = 1; x <= n; x++)
{
for (int y = 1; y <= n; y++)
{
bool isok = 1;
for (int i = len - 1; i >= 0; i--)
{
if (validator(x + i, y + i) == 0)
{
isok = 0;
break;
}
if (map[x + i][y + i] != target)
{
isok = 0;
break;
}
}
if (isok)
{
return 1;
}
isok = 1;
for (int i = len - 1; i >= 0; i--)
{
if (validator(x + i, y - i) == 0)
{
isok = 0;
break;
}
if (map[x + i][y - i] != target)
{
isok = 0;
break;
}
}
if (isok)
{
return 1;
}
isok = 1;
for (int i = len - 1; i >= 0; i--)
{
if (validator(x + i, y) == 0)
{
isok = 0;
break;
}
if (map[x + i][y] != target)
{
isok = 0;
break;
}
}
if (isok)
{
return 1;
}
isok = 1;
for (int i = len - 1; i >= 0; i--)
{
if (validator(x, y + i) == 0)
{
isok = 0;
break;
}
if (map[x][y + i] != target)
{
isok = 0;
break;
}
}
if (isok)
{
return 1;
}
}
}
return 0;
}
void query()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
iskey[i][j] = 0;
}
}
if (check(5, 'O'))
{
std::cout << "Pity" << '\n';
return;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (map[i][j] == 'X' && check_iskey(i, j, 'X'))
{
std::cout << "Focalors" << '\n';
return;
}
if (map[i][j] == '*' && check_iskey(i, j, 'X'))
{
iskey[i][j] = 1;
std::cout << "Focalors" << '\n';
return;
}
}
}
int cnta = 0, cntb = 0;
int keyx = 0, keyy = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (map[i][j] == '*' && check_iskey(i, j, 'O'))
{
cnta++;
keyx = i, keyy = j; // 必须堵白棋的点
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (map[i][j] == '*' && check_iskey(i, j, 'X'))
{
cntb++;
}
}
}
if (cnta >= 2)
{
std::cout << "Pity" << '\n';
return;
}
if (cnta == 1)
{
map[keyx][keyy] = 'X';
iskey[keyx][keyy] = 1;
for (int i = keyx - 4; i <= keyx + 4; i++)
{
for (int j = keyy - 4; j <= keyy + 4; j++)
{
if (validator(i, j) && iskey[i][j] == 0 && check_iskey(i, j, 'X'))
cntb++;
}
}
map[keyx][keyy] = '*';
iskey[keyx][keyy] = 0;
if (cntb >= 2)
{
std::cout << "Focalors" << '\n';
return;
}
std::cout << "Pity" << '\n';
return;
}
else
{
for (int chx = 1; chx <= n; chx++)
{
for (int chy = 1; chy <= n; chy++)
{
if (map[chx][chy] != '*')
continue;
int add = 0;
int keyx = chx, keyy = chy;
map[keyx][keyy] = 'X';
iskey[keyx][keyy] = 1;
for (int i = keyx - 4; i <= keyx + 4; i++)
{
for (int j = keyy - 4; j <= keyy + 4; j++)
{
if (validator(i, j) && iskey[i][j] == 0 && check_iskey(i, j, 'X'))
{
add++;
}
}
}
map[keyx][keyy] = '*';
iskey[keyx][keyy] = 0;
if (cntb + add >= 2)
{
std::cout << "Focalors" << '\n';
return;
}
}
}
}
std::cout << "Pity" << '\n';
return;
}
int opr_vec_x[5005], opr_vec_y[5005];
char opr_vec_ch[5005], opr_vec_org[5005];
int tot = 0;
void solve()
{
std::cin >> n >> q;
// n = 15;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
std::cin >> map[i][j];
}
}
std::vector<opr> opr_vec;
for (int i = 1; i <= q; i++)
{
int op;
std::cin >> op;
if (op == 1)
{
int x, y;
char ch;
std::cin >> x >> y >> ch;
opr_vec.push_back({x, y, ch, map[x][y]});
map[x][y] = ch;
}
else if (op == 2)
{
opr lstopr = opr_vec.back();
opr_vec.pop_back();
map[lstopr.x][lstopr.y] = lstopr.org;
}
else
{
query();
}
}
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0);
solve();
return 0;
}
G. 怯战蜥蜴
预估难度:*1200
二分+前缀和。实际上是对于每一次询问,需要给出最大的下标 ,使得 。比较自然地可以想到先用前缀和预处理,对于每一次询问二分查找合法答案即
参考代码:
#include <bits/stdc++.h>
constexpr int N = 2e5 + 10;
int a[N], pre[N];
void solve()
{
int n, q;
std::cin >> n >> q;
for (int i = 1; i <= n; i++)
{
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
for (int i = 1; i <= q; i++)
{
int x;
std::cin >> x;
int l = 0, r = n, ans = 0;
while (l <= r)
{
int mid = (l + r) / 2;
if (pre[mid] > x)
r = mid - 1;
else
l = mid + 1, ans = mid;
}
std::cout << ans << '\n';
}
}
signed main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0);
solve();
return 0;
}
H. To the Moon, Finding Paradise
预估难度:*1600
题意:给你一个有向无环图,请你寻找出从起点 到终点 的一条路径,在保证路径上点权之和小于等于 的前提下,使得路径中最大的边权最小。
当看到最大边权最小这句话时,大概就知道:本题主要考察了二分。
一个显然的结论:当我们规定的路径中的最大边权越大,能够加入此路径的边就越多,能够经过的点就越多,就越有可能得到一个点权和更小的路径。由于题目保证了一定有解,因此当最大边权的上限足够大时,一定能够找到一个合法的路径。
由此可以得出该问题的单调性:最大边权的上限越大,路径的点权和越小,直到达到最小的点权和。
因此我们可以二分路径中最大边权的上限 ,然后以 为起点进行 ,遇到边权大于 的边直接忽略,最后判断一下 到 是否可达以及路径的最小点权和是否小于等于 即可。
正解的时间复杂度是 。由于校内 OJ 较为抽象的评测速度以及出题人不会如何在 DAG 上优雅地卡掉 SPFA ,本题的数据范围由 缩小至 ,放过了 Dijkstra 和 SPFA 做法。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int n, m, cnt, s, t, c, ans;
int head[N], in[N], dis[N], a[N], tmp[N];
struct Edge
{
int nxt, to, w;
} edge[N << 3];
void add(int from, int to, int w)
{
edge[++cnt].nxt = head[from];
edge[cnt].to = to;
edge[cnt].w = w;
head[from] = cnt;
}
bool check(int qwq)
{
queue<int> q, q1;
for (int i = 1; i <= n; ++i)
in[i] = tmp[i];
for (int i = 1; i <= n; ++i)
dis[i] = 1e12;
for (int i = 1; i <= n; ++i)
if (!in[i] && i != s)
q1.push(i);
while (!q1.empty())
{
int cur = q1.front();
q1.pop();
for (int i = head[cur]; i; i = edge[i].nxt)
{
int nxt = edge[i].to;
--in[nxt];
if (!in[nxt])
q1.push(nxt);
}
}
dis[s] = 0;
q.push(s);
while (!q.empty())
{
int cur = q.front();
q.pop();
for (int i = head[cur]; i; i = edge[i].nxt)
{
int nxt = edge[i].to;
--in[nxt];
if (edge[i].w <= qwq)
dis[nxt] = min(dis[nxt], dis[cur] + a[cur]);
if (!in[nxt])
q.push(nxt);
}
}
return dis[t] + a[t] <= c;
}
void solve()
{
cnt = 0;
cin >> n >> m >> s >> t >> c;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= m; ++i)
{
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
++tmp[y];
}
ans = 1e9;
int l = 0, r = 1e9;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
{
l = mid + 1;
}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
}
I. fumo星
预估难度:*1500
一个不像计算几何的计算几何。根据题意,有以下几种特殊情况:
-
时答案为 。
-
当存在两颗 fumo 星坐标相同时,答案为
inf
(因为题目中要求经过序号不同的 fumo 星的直线数量)。 -
当存在多点共线时需要去重(因为题目中要求不同的直线的数量)
因为数据范围比较大,以及题目中没有给出 ,所以在计算直线的时候不能直接使用点斜式,而是应该将直线化为一般式放入 set 进行比对。
参考代码:
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 1e3 + 10;
i64 gcd(i64 a, i64 b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
struct NODE
{
i64 x, y;
} node[N];
struct LINE
{
i64 A, B, C;
bool operator<(LINE l) const { return A == l.A ? (B == l.B ? C < l.C : B < l.B) : A < l.A; }
};
void solve()
{
int n;
std::cin >> n;
for (int i = 1; i <= n; i++)
{
std::cin >> node[i].x >> node[i].y;
}
if (n == 1)
{
std::cout << 0 << '\n';
return;
}
std::set<LINE> s;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
if (node[i].x == node[j].x && node[i].y == node[j].y)
{
std::cout << "inf" << '\n';
return;
}
i64 a = node[i].y - node[j].y;
i64 b = node[j].x - node[i].x;
i64 c = node[i].x * node[j].y - node[j].x * node[i].y;
i64 g = gcd(gcd(a, b), c);
a /= g, b /= g, c /= g;
if (c < 0)
{
a = -a, b = -b, c = -c;
}
s.insert({a, b, c});
}
}
std::cout << s.size() << '\n';
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0);
solve();
return 0;
}
J. 上春山
预估难度:*1400
题意:给你两个长度为 的序列 和 ,求:
由于每个位置对最终答案的贡献是相互独立的,因此 的相对位置是不重要的,所以可以将 由小到大排好序后再处理。
除此之外,一个很显然的贪心: 只有取得与某个 相差为 时,才有可能取得最优解。
这个贪心策略的证明如下:
假设当 时取得最优解 ,我们用 表示数组 中第一个大于 的位置。当我们继续增大 且保证 ,此时我们获得的价值 会增大,与假设不符。
由此,我们只需要按照 对每个春山进行排序,对 和 分别维护一个前缀和 与 ,接下来枚举 ,然后更新 即可。
参考代码:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
int n;
i64 ans, preh[N], prea[N], h[N], a[N];
struct node
{
i64 first, second;
node(int x, int y)
{
first = x;
second = y;
}
friend bool operator<(node x, node y) { return x.first < y.first; }
};
vector<node> p;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> h[i];
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
p.push_back(node(h[i], a[i]));
}
sort(p.begin(), p.end());
for (int i = 1; i <= n; ++i)
{
preh[i] = preh[i - 1] + p[i - 1].first;
prea[i] = prea[i - 1] + p[i - 1].second;
}
for (int i = 1; i <= n; ++i)
{
i64 cur = prea[n] - prea[i - 1] - (preh[n] - preh[i - 1] - (n - i + 1) * (p[i - 1].first - 1));
ans = max(ans, cur);
}
cout << ans << '\n';
return 0;
}
K. BiuBiuBiu
预估难度:*2400
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAXN 1000107
#define MAXM 400107
#define MOD 1000000007
const ll inf = 1e9;
pair<ll, ll> t1, t2, t;
ll n, m, x, y, p, q, base;
ll qpow(ll a, ll n, ll mod)
{
ll ans = 1;
a = a % mod;
while (n)
{
if (n & 1)
ans = (ans * a) % mod;
n >>= 1;
a = (a * a) % mod;
}
return ans;
}
ll gcd(ll a, ll b)
{
if (b == 0)
return a;
else
return gcd(b, a % b);
}
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (!b)
{
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
pair<ll, ll> sol(ll a, ll b, ll c)
{
pair<ll, ll> ans;
ll x, y;
ll d = exgcd(a, b, x, y);
if (c % d)
ans.first = ans.second = -1;
else
{
ll dx = b / d;
x = x * (c / d);
x = (x % dx + dx) % dx - dx;
ans.first = dx;
ans.second = x;
}
return ans;
}
pair<ll, ll> hb(pair<ll, ll> t1, pair<ll, ll> t2)
{
pair<ll, ll> ans;
if (t1.first == -1 || t2.first == -1)
ans.first = ans.second = -1;
else
{
if (t1.second > t2.second)
swap(t1, t2);
ans = sol(t1.first, t2.first, t2.second - t1.second);
if (ans.first == -1 && ans.second == -1)
ans.first = ans.second = -1;
else
{
ans.first = ans.first * t1.first;
ans.second = ans.second * t1.first + t1.second;
ans.second = (ans.second % ans.first + ans.first) % ans.first - ans.first;
}
}
return ans;
}
ll check_x(ll lenx)
{
ll sum1 = (lenx + x) / n;
ll sum2 = (lenx * q + y * p) / (m * p);
ll time = lenx / p;
ll add = (time - t.second) / t.first;
return sum1 + sum2 - add;
}
ll check_y(ll leny)
{
ll sum1 = (leny * p + x * q) / (n * q);
ll sum2 = (leny + y) / m;
ll time = leny / q;
ll add = (time - t.second) / t.first;
return sum1 + sum2 - add;
}
void solve()
{
cin >> n >> m >> x >> y >> p >> q >> base;
ll gcd_qp = gcd(p, q);
p /= gcd_qp, q /= gcd_qp;
t1 = sol(p, n, n - x);
t2 = sol(q, m, m - y);
t = hb(t1, t2);
if (t.first == -1)
t.second = 0, t.first = 9e18;
ll ans1 = inf, ans2 = inf;
ll l = 0, r = inf;
while (l < r)
{
ll mid = (l + r) >> 1;
if (check_x(mid * n - x) >= base + 1)
r = mid;
else
l = mid + 1;
}
if (check_x(l * n - x) == base + 1)
ans1 = l;
l = 0, r = inf;
while (l < r)
{
ll mid = (l + r) >> 1;
if (check_y(mid * m - y) >= base + 1)
r = mid;
else
l = mid + 1;
}
if (check_y(l * m - y) == base + 1)
ans2 = l;
if ((ans1 * n - x) * q <= (ans2 * m - y) * p)
printf("%lld %lld\n", (ans1 * n - x) % MOD, (ans1 * n - x) % MOD * q % MOD * qpow(p, MOD - 2, MOD) % MOD);
else
printf("%lld %lld\n", (ans2 * m - y) % MOD * p % MOD * qpow(q, MOD - 2, MOD) % MOD, (ans2 * m - y) % MOD);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
L. 南湖少年团
预估难度:*800
签到题。按题意模拟即可。
参考代码:
#include <bits/stdc++.h>
char s1[105], s2[105];
void solve()
{
int n, m;
std::cin >> n >> m;
std::cin >> s1 >> s2;
int cnt = 0;
for (int i = 0; i + n - 1 < m; i++)
{
bool isok = 1;
for (int j = 0; j < n; j++)
{
if (s2[i + j] != s1[j])
{
isok = 0;
break;
}
}
cnt += isok;
}
std::cout << cnt << std::endl;
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0);
solve();
}
M. 上帝造题的八分钟
预估难度:*1800
数位 dp。由于读入的数字超过了 long long
的范围,所以考虑使用字符串读入。dp[pos][last2][last1][c1][dx]
表示已经搜到了第 位(从高位到低位), 位数字为 , 位数字为 , 表示之前的数字是否满足数位中有至少 个相邻的数字 的性质, 表示数字 的个数与数字 的差值。接下来注意转移细节即可。
参考代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
#define MAX_LEN 100
#define MOD 1000000007
ll A[MAX_LEN + 2], dp[MAX_LEN + 2][11][11][2][((MAX_LEN + 2) << 1) + 2];
ll cnt, digit;
ll l, r;
ll dfs(ll pos, ll last, ll llast, bool c1, ll dx, bool limit, bool zero)
{
if (!pos)
return (c1 && (abs(dx - MAX_LEN - 2) >= 1));
if (!limit && !zero && dp[pos][llast][last][c1][dx] != -1)
return (dp[pos][llast][last][c1][dx] % MOD + MOD) % MOD;
ll ans = 0;
ll res = limit ? A[pos] : 9;
for (ll i = 0; i <= res; i++)
{
if (zero && !i)
ans = (ans + dfs(pos - 1, 10, 10, 0, dx, limit && (i == res), 1)) % MOD;
else if (zero)
ans = (ans + dfs(pos - 1, i, 10, c1, dx + (i == 0) - (i == 1), limit && (i == res), 0)) % MOD;
else
ans = (ans + dfs(pos - 1, i, last, c1 || (i == 3 && i == last && last == llast), dx + (i == 0) - (i == 1), limit && (i == res), 0)) % MOD;
}
ans = (ans % MOD + MOD) % MOD;
if (!limit && !zero)
dp[pos][llast][last][c1][dx] = ans;
return ans;
}
ll f(string s)
{
cnt = s.size();
for (ll i = 1; i <= cnt; i++)
A[i] = (s[cnt - i] - '0');
return (dfs(cnt, 10, 10, 0, (MAX_LEN + 2), 1, 1) % MOD + MOD) % MOD;
}
ll check(string s)
{
bool c1 = 0;
for (ll i = 2; i < s.size(); i++)
if (s[i] == '3' && s[i - 1] == '3' && s[i - 2] == '3')
c1 = 1;
if (!c1)
return 0;
ll cnt1 = 0, cnt2 = 0;
for (ll i = 0; i < s.size(); i++)
if (s[i] == '0')
cnt1++;
else if (s[i] == '1')
cnt2++;
if (cnt1 == cnt2)
return 0;
return 1;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
memset(dp, -1, sizeof(dp));
string l, r;
cin >> l >> r;
printf("%lld ", ((f(r) - f(l) + check(l)) % MOD + MOD) % MOD);
}