题目链接
A. “Aaawww…” or “Aaayyy!!!”
题意
模拟滚榜的流程
题解
注意每次过完一道题都应该更新榜单
代码
struct team{ int id; string problems;};
void idol_produce(int testCase) { /*Code Here*/ int n, m, p; cin >> n >> m >> p; vector<team> teams(n + 1); int sum = 0; for(int i = 1;i <= n; i++){ cin >> teams[i].problems; teams[i].id = i; sum += count(teams[i].problems.begin(), teams[i].problems.end(), 'P'); } int id = n; for(int i = 1;i <= sum; i++){ string s; cin >> s >> s; while(teams[id].problems.find('P') == string::npos) id--; if (s == "Aaawww..."){ for(auto &c : teams[id].problems){ if(c == 'P'){ c = 'R'; break; } } } else{ s.pop_back(); s.pop_back(); s.pop_back(); s = s.substr(6); int up = s.size(); for (auto &c : teams[id].problems) { if (c == 'P') { c = 'A'; break; } } for(int i = id - 1; i >= id - up; i --){ swap(teams[i], teams[i + 1]); } } } for(int i = 1;i <= n; i++){ if(teams[i].id == p){ cout << i << '\n'; return; } }}
J. Jumbled Scoreboards
题意
判断给定的对是否形成不降序
题解
按题意模拟即可
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; vector<pair<int, int> > a(n + 1); for (int i = 1; i <= n; i++) { cin >> a[i].first >> a[i].second; } for (int i = 2; i <= n; i++) { if (a[i].second < a[i - 1].second || a[i].first < a[i - 1].first) { cout << "no\n"; return; } } cout << "yes\n";}
G. Grocery Greed
题意
给定 个价格的物品,可以选择现金或刷卡支付,若选择现金支付价格将四舍五入最接近的 ,你可以将多个物品分为一组进行支付,使总价格最小
题解
将所有价格单位转化成美分,并对 美分取模,这样就得到模数为 美分的各个物品的价格
- 对于所有的 和 美分的价格直接向下取整
- 对于每一对 和 美分的价格分为一组得到 美分后向下取整
- 若剩余 ,则将每一对 分为一组得到 后向下取整
- 若剩余 ,则将每三个 分为一组得到 后向下取整
- 剩余的价格直接加入答案
为什么要先把 和 组成一对,再处理剩余的?
令 表示将一些价格分为一组可以省下来的美分
显然对于省下同样的美分,将 和 组合起来一定比单独分组更优
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; vector<int> a(5); int ans = 0; for (int i = 1; i <= n; i++) { double x; cin >> x; // use round() not (int)() int p = round(x * 100); a[p % 5]++; ans += p; } ans -= a[1]; ans -= a[2] * 2; ans -= min(a[3], a[4]) * 2; int tmp = min(a[3], a[4]); a[3] -= tmp; a[4] -= tmp; ans -= a[3] / 2; ans -= a[4] / 3 * 2; cout << fixed << setprecision(2) << ans / 100.00 << '\n';}
E. Extraterrestrial Exploration
题意
给定一个长度为 的不递减的未知数列 ,每一次询问 可以得到 的值,在 次询问内,得到一个三元组 使得 最大
题解
设 ,显然 必然最优,即:
我们只需要确定 的值即可,该函数求导后为一个凸函数,在 最大,二分找到原数列最接近的值即可
int ask(int x) { cout << "? " << x << endl; int res; cin >> res; return res;}
void answer(int x, int y, int z) { cout << "! " << x << " " << y << " " << z << endl; return;}void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; int target = ask(1) + ask(n); int ans = -1; int res = INF; int l = 2, r = n - 1; while(l <= r){ int mid = (l + r) >> 1; int tmp = ask(mid); if(abs(tmp * 2 - target) < res){ ans = mid; res = abs(tmp * 2 - target); } if(tmp * 2 == target){ break; } if(tmp * 2 < target){ l = mid + 1; }else{ r = mid - 1; } } answer(1, ans, n);}
I. Interrail Pass
题意
计划完成 天的旅行,票价分为单程票和通行证
张单程票,第 张单程票表示第 天旅行票价为
张通行证,第 张通行证表示购买价格为 的通行证后,接下来的 天内(包含当天),前 次旅行免费
求完成 天旅行的最少花费的价格
题解
令 表示第 天之前的最新的旅行日的索引,即 ,这个可以预处理
令 表示前 次旅行最少花费的价格
那么得到转移方程:
代码
void idol_produce(int testCase) { /*Code Here*/ int n, k; cin >> n >> k; vector<int> t(n + 1), f(n + 1); for (int i = 1; i <= n; i++) { cin >> t[i] >> f[i]; } vector<int> pre((int)1e6 + 5); int id = 0; for (int i = 0; i <= 1e6; i++) { while (id < n && t[id + 1] <= i) { id++; } pre[i] = id; } vector<int> p(k + 1), d(k + 1), c(k + 1); for (int i = 1; i <= k; i++) { cin >> p[i] >> d[i] >> c[i]; } auto get_pre = [&](int x) { if (x < 0) return 0; return pre[x]; }; vector<int> dp(n + 1); for (int i = 1; i <= n; i++) { dp[i] = dp[i - 1] + f[i]; for (int j = 1; j <= k; j++) { dp[i] = min(c[j] + dp[max(i - d[j], get_pre(t[i] - p[j]))], dp[i]); } } cout << dp[n] << '\n';}
F. Failing Factory
题意
给定 个步骤,这些步骤有依赖关系(可能存在循环依赖),步骤 有 概率失败,一个步骤不失败要求它及其前置步骤均不失败,问失败概率最小的步骤的不失败概率为多少
题解
对于非循环依赖的步骤,显然所有不存在依赖的步骤失败概率最小,对于循坏依赖的步骤,其组成的一个强连通分量中的每一个步骤的不失败概率均为 先求强连通分量,再对所有入度为 的步骤的失败概率取最小即可。
NOTE输出小数点后200位
代码
const int N = 1e5 + 5;int dfn[N], low[N], dfncnt, s[N], in_stack[N], tp;int scc[N], sc; // 结点 i 所在 SCC 的编号int sz[N]; // 强连通 i 的大小vector<vector<int>> e(N);void tarjan(int u) { low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1; for (auto v : e[u]) { if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (in_stack[v]) { low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { ++sc; do { scc[s[tp]] = sc; sz[sc]++; in_stack[s[tp]] = 0; } while (s[tp--] != u); }}
void idol_produce(int testCase) { /*Code Here*/ int n, m; cin >> n >> m; vector<double> p(n + 1); for(int i = 1;i <= n; i++){ double x; cin >> x; p[i] = 1.0 - x; } vector<pair<int,int> > ed; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; e[v].push_back(u); ed.push_back({u, v}); } for(int i = 1;i <= n; i++){ if(!dfn[i]) tarjan(i); } vector<double> ans(sc + 1, 1); vector<int> in(sc + 1); for(int i = 1;i <= n; i++){ ans[scc[i]] *= p[i]; } for(auto &[u, v] : ed){ if(scc[u] != scc[v]){ in[scc[u]]++; } } double res = 0; for(int i = 1;i <= sc; i++){ if(in[i] == 0){ res = max(res, ans[i]); } } cout << fixed << setprecision(200) << res << '\n';}
M. Museum Visit
题意
给定 每个整数的代价 , 选择一个整数集合使得对于共计 个区间的每一个区间 ,都有至少一个整数落在区间内,求最小的代价之和
题解
显然动态规划:
设 表示在选择整数 的前提下,所有 的区间均被满足的最小代价
其中 表示所有 ,即所有满足 的区间中,最大的
可以使用线段树维护 值
代码
void idol_produce(int testCase) { /*Code Here*/ int n, m; cin >> n >> m; vector<ll> dp(n + 2, 0); vector<ll> c(n + 2, 0); for (int i = 1; i <= n; i++) { cin >> c[i]; } vector<pair<int,int>> p(m + 1); for (int i = 1; i <= m; i++) { cin >> p[i].first >> p[i].second; } sort(p.begin() + 1, p.end(), [](const pair<int,int> &a, const pair<int,int> &b) { return a.second > b.second; }); SegmentMinTree st(vector<ll>(n + 2)); int l = 0; for(int i = 1;i <= n + 1; i++){ while(!p.empty() && p.back().second < i){ l = max(l, p.back().first); p.pop_back(); } if(l == 0) dp[i] = c[i]; else dp[i] = c[i] + st.query(l, i - 1); st.change(i, i, dp[i]); } cout << dp[n + 1] << '\n';}
K. Karaoke Compression
题意
给定一个字符串 ,选择一个非空字符串 ,将字符串 中的所有不相交的子串 均替换为单个字符,获得新的字符串 ,求 的最小值
题解
采用哈希计算每个不相交子字符串出现的数量,注意必须选择一个非空子串,所以初始
NOTE不要用 map or unordered_map 将哈希值直接作为下标存储对应的 集合,否则你的时间常数和空间常数会巨大
代码
int get_id(int i, int j){ return 5000 * i + j;}void idol_produce(int testCase) { /*Code Here*/ string s; cin >> s; int n = s.size(); vector<ull> hash_value(5005 * 5005); for(int i = 0; i < n; i++){ ull h = 0; for(int j = i; j < s.size(); j++){ h = h * 131 + s[j]; hash_value[get_id(i, j + 1)] = h; } } int ans = s.size() + 1; unordered_map<ull, int> count, next_pos; for(int len = 2; len < n; len++){ count.clear(); next_pos.clear(); for(int i = 0; i <= n - len; i++){ ull h = hash_value[get_id(i, i + len)]; if(next_pos[h] > i) continue; count[h]++; next_pos[h] = i + len; } for(auto [_,c]:count){ ans = min(ans, n - (len - 1) * (c - 1) + 1); } } cout << ans << "\n";}
B. Buggy Blinkers
题意
在未加权图中找到最短路径,但不能进行超过 个不间断的转弯。求最短路径
题解
以 路口位置,路口方向,信号灯状态,开启次数,步数 为节点状态进行广度优先搜索。
NOTE朝路口某段方向行驶时,不一定走直线。如样例,从路口 向南行驶,到达路口 时不处在其北侧。
代码
vector<vector<int> > a(5005, vector<int>(4, 0));
enum Light { left = -1, right = 1, up = 0 };struct node { int loc; int direction; int light; int time; int step;
node go_up() { node res = *this; if (res.light != Light::up) { res.light = Light::up; } res.loc = a[res.loc][(res.direction + 2) % 4]; res.step += 1; if (res.loc == 0) return res; for (int i = 0; i < 4; i++) { if (a[res.loc][i] == this->loc) res.direction = i; } return res; }
node go_right() { auto res = *this; if (res.light != Light::right) { res.light = Light::right; res.time += 1; } res.loc = a[res.loc][(res.direction + 3) % 4]; res.step += 1; if (res.loc == 0) return res; for (int i = 0; i < 4; i++) { if (a[res.loc][i] == this->loc) res.direction = i; } return res; }
node go_left() { auto res = *this; if (res.light != Light::left) { res.light = Light::left; res.time += 1; } res.loc = a[res.loc][(res.direction + 1) % 4]; res.step += 1; if (res.loc == 0) return res; for (int i = 0; i < 4; i++) { if (a[res.loc][i] == this->loc) res.direction = i; } return res; }
bool operator==(const node &x) const { return x.loc == loc && x.direction == direction && x.light == light && x.time == time; }};
struct NodeHasher { size_t operator()(const node &x) const { return (hash<int>()(x.loc) * 4) ^ (hash<int>()(x.direction) * 3) ^ (hash<int>()(x.light) * 2) ^ hash<int>()(x.time); }};void idol_produce(int testCase) { /*Code Here*/ int n, k; cin >> n >> k; for (int i = 1; i <= n; i++) { for (int j = 0; j < 4; j++) { cin >> a[i][j]; } } if (n == 1) { cout << 0 << '\n'; return; } queue<node> q; for (int i = 0; i <= 3; i++) { if (a[1][i] == 0) continue; int dir = 0; for (int j = 0; j < 4; j++) { if (a[a[1][i]][j] == 1) { dir = j; break; } } q.push({a[1][i], dir, up, 0, 1}); } unordered_set<node, NodeHasher> vis; while (!q.empty()) { auto t = q.front(); q.pop(); if (t.time > k) continue; if (vis.count(t)) continue; vis.insert(t); if (t.loc == n) { cout << t.step << '\n'; return; } auto res = t.go_up(); if (res.loc != 0) q.push(res); res = t.go_left(); if (res.loc != 0) q.push(res); res = t.go_right(); if (res.loc != 0) q.push(res); } cout << "impossible\n";}
C. Concurrent Contests
题意
将 个数 分为 组,每组的价值为 使得所有数均满足以下条件:
- 对于组别 的总和
- 属于组别 的数字 ,满足
题解
对数字从大到小排序,以此将每个数字放在其占比最大的一个分组中。
证明:
对于当前选择的数字 ,显然选择价值最大的组为最优解,设其选择的组别为 。
对于已经分好组的数字 ,满足
对于与 同组的 ,在 不加入 前满足:
因此,必然满足:
因此 移到其他组一定不优
对于与 不同组的 ,在 不加入前就已经选择好了最优组, 加入 只是让一个对于 不优的组变得更加不优而已
代码
void idol_produce(int testCase) { /*Code Here*/ int n, m; cin >> n >> m; vector<pair<ll, int>> a(n + 1); vector<ll> p(m + 1); for (int i = 1; i <= n; i++) cin >> a[i].first, a[i].second = i; for (int i = 1; i <= m; i++) cin >> p[i]; sort(a.begin() + 1, a.end(), greater<>()); vector<vector<int> > ans(m + 1); vector<ll> sum(m + 1); for(int i = 1;i <= n;i ++){ int id = 1; for(int j = 2;j <= m;j ++){ if(p[j] * (sum[id] + a[i].first) > p[id] * (sum[j] + a[i].first)){ id = j; } } ans[id].emplace_back(a[i].second); sum[id] += a[i].first; } for(int i = 1;i <= m;i ++){ cout << ans[i].size() << ' '; for(int j = 0;j < ans[i].size();j ++){ cout << ans[i][j] << ' '; } cout << '\n'; }}