比赛链接
官方题解
Problem A. Cow and Haybales
按照题意模拟即可。
单组数据时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, d, a[MAXN];
int main() {
int T; read(T);
while (T--) {
read(n), read(d);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 2; i <= n; i++) {
while (a[i] && d >= i - 1) {
d -= i - 1;
a[i]--, a[1]++;
}
}
cout << a[1] << endl;
}
return 0;
}
Problem B. Cow and Friend
考虑跳跃总距离 应满足的条件。
若跳跃次数为
,则应有
,否则,应有
。
因此,判断答案是否为
,否则取最大的跳跃距离计算答案即可。
单组数据时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int main() {
int T; read(T);
while (T--) {
int n, m; read(n), read(m);
int Max = 0, ans = INT_MAX;
for (int i = 1; i <= n; i++) {
int x; read(x);
chkmax(Max, x);
if (x == m) ans = 1;
}
chkmin(ans, max(2, (m - 1) / Max + 1));
cout << ans << endl;
}
return 0;
}
Problem C. Cow and Message
显然,满足题目中要求的子序列由其长度和前两个字符的位置唯一确定。
因此,任意出现次数为
的字符串
的长度不小于
的前缀
的出现次数一定不少于
。
由此,我们可以只考虑长度为
的字符串。
则用前缀和简单统计每个字符串分别的出现次数即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXN];
int pre[MAXN][26]; ll cnt[26][26];
int main() {
scanf("\n%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
memcpy(pre[i], pre[i - 1], sizeof(pre[i - 1]));
for (int j = 0; j <= 25; j++)
cnt[j][s[i] - 'a'] += pre[i][j];
pre[i][s[i] - 'a']++;
}
ll ans = 0;
for (int i = 0; i <= 25; i++)
chkmax(ans, 0ll + pre[n][i]);
for (int i = 0; i <= 25; i++)
for (int j = 0; j <= 25; j++)
chkmax(ans, cnt[i][j]);
cout << ans << endl;
return 0;
}
Problem D. Cow and Fields
首先用 BFS 求出各个点到 号点的最短路 和到 号点的最短路 。
考虑连接关键点
后最短路的变化,显然,若不经过边
最短路不变,否则,最短路应为
注意到若
,连接
不会使最短路发生变化,我们可以将上式稍作修改:
我们需要找到最大化这个式子的 ,并将得到的值与原有最短路取最小值。则可以将所有点按照 排序,从大到小枚举 ,并维护 的次大值用于更新答案。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int f[MAXN], g[MAXN];
int n, m, k, key[MAXN];
vector <int> a[MAXN];
void bfs(int pos, int *dist) {
static int q[MAXN];
for (int i = 1; i <= n; i++)
dist[i] = -1;
dist[pos] = 0;
int l = 0, r = 0; q[0] = pos;
while (l <= r) {
int tmp = q[l++];
for (auto x : a[tmp])
if (dist[x] == -1) {
dist[x] = dist[tmp] + 1;
q[++r] = x;
}
}
}
int main() {
read(n), read(m), read(k);
for (int i = 1; i <= k; i++)
read(key[i]);
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
bfs(1, f);
bfs(n, g);
sort(key + 1, key + k + 1, [&] (int x, int y) {return f[x] > f[y]; });
int Max = 0, Nax = 0, ans = 0;
for (int i = 1; i <= k; i++) {
int pos = key[i];
if (g[pos] > Max) {
Nax = Max;
Max = g[pos];
} else chkmax(Nax, g[pos]);
if (i != 1) chkmax(ans, f[pos] + Nax + 1);
}
chkmin(ans, f[n]);
cout << ans << endl;
return 0;
}
Problem E. Cow and Treats
每个元素有三个属性,颜色
,从左到右行走的距离
,从右到左行走的距离
。
我们希望找到大小之和最大的两个集合
,满足同一集合中的元素颜色两两不同,且
则首先枚举 ,那么,元素 可以被归入 中需要满足 ,可以被归入 中需要满足 。并且由于我们希望最大化选出的元素个数,问题对于每一种颜色的独立的,简单计算即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, s[MAXN]; pair <int, int> ans;
vector <pair <int, int>> a[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void update(pair <int, int> &x, pair <int, int> y) {
if (y.first > x.first) x = y;
else if (y.first == x.first) update(x.second, y.second);
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
read(s[i]);
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
pair <int, int> res = make_pair(-1, -1);
for (int j = 1, k = y; j <= n; j++)
if (s[j] == x && --k == 0) {
res.first = j;
break;
}
for (int j = n, k = y; j >= 1; j--)
if (s[j] == x && --k == 0) {
res.second = j;
break;
}
if (res.first != -1) a[x].push_back(res);
}
pair <int, int> ans = make_pair(0, 1);
for (int i = 1; i <= n; i++)
if (a[i].size()) {
ans.first++;
ans.second = 1ll * ans.second * a[i].size() % P;
}
for (int i = 1; i <= n; i++)
for (auto x : a[i]) {
int pos = x.first, cnt = 0;
pair <int, int> cur = make_pair(1, 1);
for (auto y : a[i])
if (y.first != pos && y.second > pos) cnt++;
if (cnt != 0) {
cur.first++;
cur.second = cnt;
}
for (int j = 1; j <= n; j++) {
if (i == j) continue;
int l = 0, r = 0, m = 0;
for (auto y : a[j])
if (y.first < pos && y.second > pos) m++;
else if (y.first < pos) l++;
else if (y.second > pos) r++;
int ways = 0;
ways += l * r;
ways += l * m;
ways += r * m;
ways += m * (m - 1);
if (ways) {
cur.first += 2;
cur.second = 1ll * cur.second * ways % P;
} else if (l + r + m) {
cur.first += 1;
cur.second = 1ll * cur.second * (l + r + 2 * m) % P;
}
}
update(ans, cur);
}
cout << ans.first << ' ' << ans.second << endl;
return 0;
}
Problem F. Cow and Vacation
首先考虑一个 的解法。
我们称还能行走的步数为当前的活力
。
求出到每个点
最近的补给站的距离
,经过点
时,若
,则可以将活力补足至
。由此,我们从起点出发,走向终点,尝试在途经的每一个节点补充活力,可以得到一个
的算法。
考虑如何优化,我们首先特判掉 可以不经过补给站到达 的情况。
对于询问
,考虑求出上述解法中,
到
的路径上到达的任意一个补给站
,
到
的路径上到达的任意一个补给站
。若求得了这两个补给站,则询问
将等价于询问
。
由上面的算法的正确性,不难证明这两个询问的等价性。
考虑走出
步后,到达了点
,我们在何种情况下会考虑去补给站。则应为
注意到该区间若有意义,应当有
。
因此若
,应有
,否则,即
,应有
。
注意到有
因此,若存在可以到达的补给站,一定存在走出恰好 步后可以到达的补给站。
因此,将所有边倍长,使得 成为偶数,然后对于询问 ,令 分别靠近 步,再走到最近的补给站 即可。
补给站之间的连通性可以简单通过 BFS 求得。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 5;
const int MAXLOG = 20;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int depth[MAXN], father[MAXN][MAXLOG];
vector <int> a[MAXN];
void work(int pos, int fa) {
depth[pos] = depth[fa] + 1;
father[pos][0] = fa;
for (int i = 1; i < MAXLOG; i++)
father[pos][i] = father[father[pos][i - 1]][i - 1];
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i] != fa) work(a[pos][i], pos);
}
int climb(int x, int y) {
for (int i = MAXLOG - 1; i >= 0; i--)
if (y & (1 << i)) x = father[x][i];
return x;
}
int lca(int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[x][i]] >= depth[y]) x = father[x][i];
if (x == y) return x;
for (int i = MAXLOG - 1; i >= 0; i--)
if (father[x][i] != father[y][i]) {
x = father[x][i];
y = father[y][i];
}
return father[x][0];
}
int n, m, l, r, k, q[MAXN], f[MAXN], dist[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
int main() {
read(n), read(k), read(r), l = 1;
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(n + i);
a[n + i].push_back(x);
a[y].push_back(n + i);
a[n + i].push_back(y);
}
n += n - 1;
work(1, 0);
for (int i = 1; i <= n; i++)
f[i] = i;
memset(dist, -1, sizeof(dist));
for (int i = 1; i <= r; i++)
read(q[i]), dist[q[i]] = 0;
while (l <= r) {
int pos = q[l++];
if (dist[pos] == k) continue;
for (auto x : a[pos]) {
if (dist[x] == -1) {
dist[x] = dist[pos] + 1;
q[++r] = x;
}
f[find(x)] = find(pos);
}
}
read(m);
while (m--) {
int x, y; read(x), read(y);
int z = lca(x, y);
if (depth[x] + depth[y] - 2 * depth[z] <= 2 * k) puts("YES");
else {
if (depth[x] - depth[z] >= k) x = climb(x, k);
else x = climb(y, depth[x] + depth[y] - 2 * depth[z] - k);
if (depth[y] - depth[z] >= k) y = climb(y, k);
else y = climb(x, depth[x] + depth[y] - 2 * depth[z] - k);
if (find(x) == find(y)) puts("YES");
else puts("NO");
}
}
return 0;
}
Problem G. Cow and Exercise
这是一道先有解法,后有题面的题。
考虑最小费用流的标准型线性规划:
令点集为
,边集为
,源点为
,汇点为
,流量为
。
令边
的流量为
,费用为
,容量限制为
。
最小化
满足约束
考虑其对偶线性规划,考虑为流量平衡限制设置变量
,为容量限制设置变量
,则有:
最大化
满足约束
不难发现,若将
全部设置为
,得到的线性规划与题面所要求的内容一致。
其中
即为
到
的最短路,
即为修改边权的代价总和。
因此,在给定的图上求出所有流量
对应的最小费用流
,也即是
的最大值。
从而有限制 ,取所有限制中最紧的作为答案即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
namespace MincostFlow {
const int MAXP = 55;
const int MAXQ = 2e5 + 5;
const int INF = 2e9;
struct edge {int dest, flow, pos, cost; };
vector <edge> a[MAXP];
int n, m, s, t, tot, flow, cost;
int dist[MAXP], path[MAXP], home[MAXP];
void FlowPath() {
int p = t, ans = INF;
while (p != s) {
ans = min(ans, a[path[p]][home[p]].flow);
p = path[p];
}
flow += ans;
cost += ans * dist[t];
p = t;
while (p != s) {
a[path[p]][home[p]].flow -= ans;
a[p][a[path[p]][home[p]].pos].flow += ans;
p = path[p];
}
}
bool spfa() {
static int q[MAXQ];
static bool inq[MAXP];
static int l = 0, r = 0;
for (int i = 0; i <= r; i++)
dist[q[i]] = INF;
q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost < dist[a[tmp][i].dest]) {
dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
path[a[tmp][i].dest] = tmp;
home[a[tmp][i].dest] = i;
if (!inq[a[tmp][i].dest]) {
q[++r] = a[tmp][i].dest;
inq[q[r]] = true;
}
}
l++, inq[tmp] = false;
}
return dist[t] != INF;
}
void addedge(int x, int y, int z, int c) {
a[x].push_back((edge){y, z, a[y].size(), c});
a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
}
void work(int n, int *res) {
for (int i = 1; i <= n; i++)
dist[i] = INF;
while (spfa()) {
FlowPath();
res[flow] = cost;
}
}
}
int res[MAXN];
int main() {
using namespace MincostFlow;
read(n), read(m), s = 1, t = n;
for (int i = 1; i <= m; i++) {
int x, y, z; read(x), read(y), read(z);
addedge(x, y, 1, z);
}
work(n, res);
int q; read(q);
while (q--) {
int c; read(c); double ans = 1e99;
for (int i = 1; i <= flow; i++)
chkmin(ans, 1.0 * (c + res[i]) / i);
printf("%.10lf\n", ans);
}
return 0;
}