I. 三只骰子
签到。
cpp
void solve()
{
cin >> n >> m;
int a[7] = {0, 1, 0, 0, 4, 0, 0}, b[7] = {0, 0, 2, 3, 0, 5, 6};
rep(1, i, 6)
{
rep(1, j, 6)
{
rep(1, k, 6)
{
if (a[i] + a[j] + a[k] == n && b[i] + b[j] + b[k] == m)
{
yes;
re;
}
}
}
}
no;
}
A. 订单
模拟。
cpp
void solve()
{
cin >> n >> k;
rep(1, i, n)
{
cin >> a[i].first >> a[i].second;
}
sort(a + 1, a + 1 + n);
int now = 0;
rep(1, i, n)
{
auto [x, y] = a[i];
auto [l, r] = a[i - 1];
now += (x - l) * k;
if (now < y)
{
no;
re;
}
now -= y;
}
yes;
}
G. 匹配
经典思路,把 i − j = a i − a j − > a i − i = a j − j i−j=a_i−a_j-> a_i−i=a_j-j i−j=ai−aj−>ai−i=aj−j 。对 a [ i ] − i a[i]-i a[i]−i 处理即可,分组挑选两两之和大于0并累加。
我用了离散,但因为最多只有1e5个数,所以也可以直接开一个map套vector。
cpp
void solve()
{
cin >> n;
int num = 1;
map<int, int> id;
map<int, int> vs;
sum = 0;
rep(1, i, n + 100)
{
e[i].clear();
}
rep(1, i, n)
{
cin >> a[i];
b[i] = a[i] - i;
if (vs[b[i]] == 0)
{
vs[b[i]] = 1;
id[b[i]] = num;
num++;
}
int idx = id[b[i]];
e[idx].emplace_back(a[i]);
}
/* rep(1, i, num)
{
for (auto j : e[i])
{
cout << j << ' ';
}
cout << "\n";
} */
rep(1, i, num)
{
sort(e[i].begin(), e[i].end(), greater<>());
t = e[i].size();
for (int j = 0; j < t / 2 * 2; j += 2)
{
if (e[i][j] + e[i][j + 1] > 0)
{
sum += e[i][j] + e[i][j + 1];
}
}
}
cout << sum;
}
cpp
// 不离散
void solve()
{
map<int, vector<int>> mp;
cin >> n;
sum = 0;
rep(1, i, n)
{
cin >> a[i];
mp[a[i] - i].emplace_back(a[i]);
}
for (auto [x, y] : mp)
{
vector<int> v = y;
sort(v.begin(), v.end(), greater<>());
t = v.size();
for (int j = 0; j < t / 2 * 2; j += 2)
{
if (v[j] + v[j + 1] > 0)
{
sum += v[j] + v[j + 1];
}
else
{
break;
}
}
}
cout << sum;
}
D. 负重越野
答案二分。考虑把速度 > mid的队员的多余速度值加给体重,这样就能最大化他能背的最大重量。
如果每个速度 < mid的队员都能被背上则满足二分条件。
cpp
void solve()
{
cin >> n;
int mx = 0, mi = 1e16;
rep(1, i, n)
{
cin >> a[i].first >> a[i].second;
mx = max(mx, a[i].first);
mi = min(mi, a[i].first);
}
int l = mi, r = mx;
auto ck = [&](int x)
{
int num = 0;
vector<int> v1, v2;
rep(1, i, n)
{
if (a[i].first < x)
{
v1.emplace_back(a[i].second);
}
else
{
b[i] = a[i].second + (a[i].first - x);
v2.emplace_back(b[i]);
}
}
sort(v1.begin(), v1.end(), greater<>());
sort(v2.begin(), v2.end(), greater<>());
/* for (auto i : v1)
{
cout << i << " ";
}
cout << "\n";
for (auto i : v2)
{
cout << i << ' ';
} */
int t1 = v1.size(), t2 = v2.size();
if (t1 > t2)
{
return 0;
}
rep(0, i, t1 - 1)
{
if (v1[i] > v2[i])
{
return 0;
}
}
return 1;
};
while (l <= r)
{
int mid = (l + r) >> 1;
if (ck(mid))
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
/* cout << ck(8); */
cout << ans;
}
L. 谜题:曲尺
构造。必有解,思路很简单,每次用大一圈的正方形去截已知图形,得到的都是L形。
每次需要找出转折点和长度,长度一直递增所以只要找到每次的转折位置就行。
我的思路是按左下,右上,右下,左上的顺序。
cpp
void solve()
{
cin >> n >> x >> y;
cout << "Yes\n";
cout << n - 1 << '\n';
int num = 1;
int a = min(n - x, y - 1);
int b = min(x - 1, n - y);
int c = max(0ll, n + 1 - x - y);
int d = max(0ll, x + y - 1 - n);
int x1 = x, y1 = y;
while (a--) // 左下
{
cout << x + 1 << ' ' << y - 1 << ' ' << -num << ' ' << num << '\n';
num++;
x++, y--;
}
x = x1, y = y1;
while (b--) // 右上
{
cout << x - 1 << ' ' << y + 1 << ' ' << num << ' ' << -num << '\n';
num++;
x--, y++;
}
while (c--) // 右下
{
cout << num + 1 << ' ' << num + 1 << ' ' << -num << ' ' << -num << '\n';
num++;
}
while (d--) // 右下
{
cout << n - num << ' ' << n - num << ' ' << num << ' ' << num << '\n';
num++;
}
}
不过官方题解的思路更妙:记录当前的正方形上下左右四个边界,每次向外扩充。
比如如果左边和下面有空位,转折点就在左下。
cpp
void solve()
{
cin >> n >> x >> y;
cout << "Yes\n";
cout << n - 1 << '\n';
int u = x, d = x, l = y, r = y;
rep(1, i, n - 1)
{
if (u > 1 && l > 1)
{ // 左上
u--, l--;
cout << u << ' ' << l << ' ' << i << ' ' << i << '\n';
}
else if (d < n && l > 1)
{ // 左下
d++, l--;
cout << d << ' ' << l << ' ' << -i << ' ' << i << '\n';
}
else if (u > 1 && r < n)
{ // 右上
u--, r++;
cout << u << ' ' << r << ' ' << i << ' ' << -i << '\n';
}
else
{
d++, r++;
cout << d << ' ' << r << ' ' << -i << ' ' << -i << '\n';
}
}
}
E. 数学问题
先考虑特殊情况,如果n是m的倍数,消耗为0;除此之外,如果k=1,输出-1。
然后我们分析乘法,对n进行操作一包括的范围为 [ n k , n k + ( k − 1 ) ] [nk,nk+(k-1)] [nk,nk+(k−1)] ,再次进行变为 [ n 2 k , n 2 k + k 2 − 1 ] [n^2k,n^2k+k^2-1] [n2k,n2k+k2−1]
推导: [ n k + ( k − 1 ) ] ∗ k + ( k − 1 ) − > n 2 k + k 2 − k + k − 1 − > n 2 k + k 2 − 1 [nk+(k-1)]*k+(k-1)->n^2k+k^2-k+k-1->n^2k+k^2-1 [nk+(k−1)]∗k+(k−1)−>n2k+k2−k+k−1−>n2k+k2−1
显然进行p次,范围长度为 k p k^p kp,如果 k p > = m k^p>=m kp>=m 则必然存在m的倍数,所以乘法次数上界为 l o g k m log_{k}m logkm。
除法上界为 l o g k n log_{k}n logkn,所以复杂度即为 O ( l o g k n × l o g k m ) O(log_{k}n \times log_{k}m) O(logkn×logkm)。
cpp
void solve()
{
cin >> n >> k >> m >> x >> y;
if (n % m == 0)
{
cout << 0;
re;
}
if (k == 1)
{
cout << -1;
re;
}
int num = 0;
ans = 1e18;
while (1)
{
int l = n % m, len = 1;
for (int i = 0;; i++) // 乘的次数
{
int dis = (m - l) % m; // 左端点l与m倍数的距离
if (len > dis)
{
ans = min(ans, num + i * x);
break;
}
l = l * k % m;
len *= k;
}
if (n == 0)
break;
n /= k;
num += y;
}
cout << ans;
}
B. 建筑公司
题目很清晰,一开始有一些员工和一些项目,每个项目要有对应种类和数量的员工才能完成,完成后获得对应种类和数量的员工。这种有前置条件并解锁后续内容,可以联想拓扑排序。
显然每次增加任意种类的员工都可能完成某个项目的某个条件,所以我们用map套一个小顶堆,前者记录员工种类,后者记录项目要求和对应id号,每次达标就判断一次对应项目的任要求是否全部完成。
当某个项目的所有任务都完成,就可以解锁对应的奖励---新员工,然后循环即可。
cpp
void solve()
{
map<int, priority_queue<pii, vector<pii>, greater<pii>>> mp;
// 每种员工对应---需求人数,工程编号
map<int, int> now; // 已拥有的
cin >> n;
rep(1, i, n) //因为初始加入也要进ck函数,所以后面再加
{
cin >> b[i] >> c[i];
}
cin >> m;
rep(1, i, m)
{
cin >> a[i]; // 第i个工程还差多少条件未满足
rep(1, j, a[i])
{ // 需求
cin >> x >> y;
mp[x].emplace(y, i); // 第i种员工,需求人数,工程编号
}
cin >> t;
rep(1, j, t)
{ // 奖励
cin >> x >> y;
e[i].emplace_back(x, y);
}
}
queue<int> q;
rep(1, i, m)
{
if (a[i] == 0)
{
q.push(i);
}
}
ans = 0;
auto ck = [&](int x, int y)
{
now[x] += y;
auto &pq = mp[x]; // 处理所有和所添加员工有关的工程
while (pq.size())
{
auto [num, id] = pq.top();
if (now[x] < num)
{
break;
}
pq.pop();
if (--a[id] == 0) // 如果某个工程的条件全部满足,发放奖励
{
q.push(id);
}
}
};
rep(1, i, n)
{
ck(b[i], c[i]);
}
while (q.size())
{
auto t = q.front();
q.pop();
ans++;
for (auto [x, y] : e[t])
{
ck(x, y);
}
}
cout << ans;
}