[CodeForces]Codeforces Round #433
854A. Fraction
题意
给定$n$,求分子分母和为$n$且分母最大的最简分数。
题解
暴力枚举。
代码
#include <bits/stdc++.h>
using namespace std;
inline int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int main() {
int n; scanf("%d", &n);
for (int a = n / 2; a >= 1; a--) {
int b = n - a;
if (gcd(a, b) == 1) return 0 * printf("%d %d\n", a, b);
}
return 0;
}
854B. Maxim Buys an Apartment
题意
给定$n$和$k$,表示$n$个连续位置中已经放了$k$个点。现在继续在剩余位置放点,必须与$k$个点中至少一个相邻。求能放的点的数量最小值和最大值。
题解
最小值,除非$k=0$或$k=n$时为$0$,其他情况都为$1$。
最大值,考虑将$k$个点相隔两个放置一定最优,也就是一个点能管包括本身在内的三个点,画个图就明白了。
代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main() {
ll n, k; scanf("%lld%lld", &n, &k);
if (k == n || k == 0) return 0 * puts("0 0");
return 0 * printf("1 %lld\n", k * 3 >= n ? n - k : k * 2);
}
853A. Planning
题意
给定$n$架飞机$1-n$,本来第$i$架对应在第$i$分钟起飞。由于延误,起飞时间区间改为$[k+1,k+n]$。第$i$架飞机延迟一分钟损失$c_i$。调整起飞时间,要求不能同时起飞,不能比原来早。求最小损失。
题解
贪心:对于$[k+1,k+n]$内的每个时刻,我们尽可能让能起飞且损失最大的飞机起飞。
下面我们简单证明一下:
设每架飞机新的起飞时间为$t_i$,则总损失为$\sum_{i=1}^{n}c_i\times (t_i-i)=\sum_{i=1}^{n}c_i\times t_i-\sum_{i=1}^{n}c_i\times i$。
我们注意到$\sum_{i=1}^{n}c_i\times i$是个常数,要求总损失最小,即让$\sum_{i=1}^{n}c_i\times t_i$最小。联想排序不等式,容易证明贪心的正确性。
在贪心过程中,我们需要维护当前时间能起飞飞机的损失最大值,使用$Heap$、$STL$中的$set$或$priority\_queue$即可。
时间复杂度:$\Theta(n\times logn)$。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pir;
const int N = 3e5+9;
int c[N], res[N];
priority_queue<pir> pq;
inline int read() {
int s = 1, a = 0; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') s = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {a = a * 10 + ch - '0'; ch = getchar();}
return s * a;
}
int main() {
int n = read(), k = read();
for (int i = 1; i <= n; i++) c[i] = read();
for (int i = 1; i <= k; i++) pq.push(make_pair(c[i], i));
ll cost = 0;
for (int i = k + 1; i <= k + n; i++) {
if (i <= n) pq.push(make_pair(c[i], i));
int t = pq.top().second;
pq.pop();
res[t] = i;
cost += 1ll * (i - t) * c[t];
}
printf("%lld\n", cost);
for (int i = 1; i < n ; ++i) printf("%d ", res[i]);
return 0 * printf("%d\n", res[n]);;
}
853B. Jury Meeting
题意
给定$n$个城市的人要去城市$0$开会,有$m$架从城市$0$出发或者到达城市$0$的飞机,要求每个城市的人来了还要回去且$n$个城市的人呆在一起的时间大于等于$k$天。求最小花费。
题解
首先,每个城市的人都需要一架去、一架回的飞机,那么在时间轴上,即要选$n$个线段使它们重合部分长度$\geq k$。
然后,我们考虑若第$i$天所有城市的人都在城市$0$,第$i$天之前要有$n$架不同飞机到达城市$0$,之后也要有$n$架不同飞机离开城市$0$。对于第$i$天,预处理前缀和$pre_i,suf_i$,分别表示第$i$天前、后,$n$架不同城市的飞机到达、离开城市$0$所需要的最少花费。
最后,按天数扫一遍,取$min_{1\leq i\leq d_{max}}pre_i+suf_{i+k+1}$即可。
时间复杂度:$\Theta(m\times logm+n)$。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pir;
const int N = 1e5+9;
const int D = 1e6+9;
const ll INF = 1e12;
vector<pir> fr[N], to[N];
ll pre[D], suf[D];
inline int read() {
int s = 1, a = 0; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') s = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {a = a * 10 + ch - '0'; ch = getchar();}
return s * a;
}
int main() {
int n = read(), m = read(), k = read();
for (int i = 1; i <= m; i++) {
int d = read(), f = read(), t = read(), c = read();
if (f) fr[f].push_back(make_pair(d, c));
if (t) to[t].push_back(make_pair(d, c));
}
for (int i = 1; i <= n; i++) {
sort(fr[i].begin(), fr[i].end(), less<pir>());
ll l1 = INF; pre[0] += l1;
for (auto p : fr[i]) {
pre[p.first] += min(0ll, p.second - l1);
l1 += min(0ll, p.second - l1);
}
sort(to[i].begin(), to[i].end(), greater<pir>());
ll l2 = INF; suf[D - 1] += l2;
for (auto p : to[i]) {
suf[p.first] += min(0ll, p.second - l2);
l2 += min(0ll, p.second - l2);
}
}
for (int j = 1; j < D; j++) pre[j] += pre[j - 1];
for (int j = D - 2; j; j--) suf[j] += suf[j + 1];
ll res = n * INF;
for (int j = 0; j < D - k - 1; j++) res = min(res, pre[j] + suf[j + k + 1]);
return 0 * (res >= INF ? puts("-1\n") : printf("%lld\n", res));
}
853C. Boredom
题意
给定一个$n\times n$的点阵,每列有一个标记点;定义“美丽矩形”为任意两个标记点构成的矩形,显然一共有$\frac{n\times(n-1)}{2}$个。有$q$个询问,每次询问一个矩形与多少个“美丽矩形”相交。
题解
我们考虑每个询问的矩形,将它的四条边延长下去,会将原来的点阵最多划分成$9$个矩形,统计每个小矩形中标记点的数量,简单容斥一下就能得到答案。
下面主要任务是预处理出前$i$列$[l,r]$行中标记点的数量,这个用可持久化线段树即可。
时间复杂度:$\Theta(n\times logn+8q\times logn)$。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+9;
int tot, root[N];
struct Tree {
int l, r, sum;
} tree[N * 40];
inline int read() {
int s = 1, a = 0; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') s = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {a = a * 10 + ch - '0'; ch = getchar();}
return s * a;
}
inline ll f(int x) {
return 1ll * x * (x - 1) / 2;
}
inline void update(int m, int l, int r, int t, int tt) {
tree[t].sum++;
if (l == r) return;
else {
int mid = (l + r) >> 1;
if (m <= mid) {
tree[t].l = ++tot;
tree[ tree[t].l ] = tree[ tree[tt].l ];
update(m, l, mid, tree[t].l, tree[tt].l);
}
else {
tree[t].r = ++tot;
tree[ tree[t].r ] = tree[ tree[tt].r ];
update(m, mid+1, r, tree[t].r, tree[tt].r);
}
}
}
inline int query(int ql, int qr, int l, int r, int t, int tt) {
if (ql > qr) return 0;
if (ql <= l && r <= qr) return tree[t].sum - tree[tt].sum;
else {
int ret = 0, mid = (l + r) >> 1;
if (ql <= mid) ret += query(ql, qr, l, mid, tree[t].l, tree[tt].l);
if (qr > mid) ret += query(ql, qr, mid+1, r, tree[t].r, tree[tt].r);
return ret;
}
}
int main() {
int n = read(), q = read();
for (int i = 1; i <= n; i++) {
int p = read();
root[i] = ++tot;
tree[ root[i] ] = tree[ root[i - 1] ];
update(p, 1, n, root[i], root[i - 1]);
}
for (int i = 1; i <= q; i++) {
int l = read(), d = read(), r = read(), u = read();
ll res = f(n);
res -= f( query(1, d-1, 1, n, root[n], root[0]) );
res -= f( query(u+1, n, 1, n, root[n], root[0]) );
res -= f( query(1, n, 1, n, root[l-1], root[0]) );
res -= f( query(1, n, 1, n, root[n], root[r]) );
res += f( query(1, d-1, 1, n, root[l-1], root[0]));
res += f( query(1, d-1, 1, n, root[n], root[r]));
res += f( query(u+1, n, 1, n, root[n], root[r]));
res += f( query(u+1, n, 1, n, root[l-1], root[0]));
printf("%lld\n", res);
}
return 0;
}
853D. Michael and Charging Stations
题意
给定$n$个商品价格为$a_i=1000$或$a_i=2000$。每件商品若付全款,则将返回$10\%$的钱来抵现金。求最小花费。
题解
吾本以为$Div1.D$必有高论。。。
裸$DP$。
首先,我们用$dp[i][j]$表示前$i$天可以抵现金的钱为$j$的最小花费。
然后,我们把价格都除以$100$,发现当前状态能抵现金的钱不可能超过$30$,否则显然可以用来买下一个商品,那么$j\leq 30$。
最后,$dp$方程就很显然了:
$\begin{equation}
dp[i][j]=min
\begin{cases}
dp[i-1][j-\frac{a_i}{10}]+a_i&,付全款\newline
dp[i-1][j+max(0,a_i-j)]+max(0,a_i-j)&,抵现金\newline
\end{cases}
\end{equation}$
时间复杂度:$\Theta(30\times n)$。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+9;
const int INF = 1e9+7;
int a[N], dp[2][31];
inline int read() {
int s = 1, a = 0; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') s = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {a = a * 10 + ch - '0'; ch = getchar();}
return s * a;
}
int main() {
int n = read();
for (int i = 1; i <= n; i++) a[i] = read() / 100;
for (int i = 1; i <= 30; i++) dp[0][i] = INF;
for (int i = 1, b = 1; i <= n; i++, b ^= 1) {
for (int j = 0; j <= 30; j++) dp[b][j] = INF;
for (int j = 0; j <= 30; j++) {
dp[b][ j + a[i] / 10 ] = min(dp[b][ j + a[i] / 10 ], dp[b ^ 1][j] + a[i]);
dp[b][ j - a[i] + max(a[i] - j, 0) ] = min(dp[b][ j - a[i] + max(a[i] - j, 0) ], dp[b ^ 1][j] + max(a[i] - j, 0));
}
}
int res = INF;
for (int i = 0; i <= 30; i++) res = min(res, dp[n % 2][i]);
return 0 * printf("%d00\n", res);
}