T1 圆形谷仓
注意到题目中没有给出无解情况的输出,所以可知 Σ x ∗ y ≤ n \Sigma_{x*y} \leq n Σx∗y≤n。
于是输入时就可以直接暴力求出每个房间被喜欢的奶牛头数,然后再暴力推两遍房间,因为是环形。
最后暴力找出最小的空余房间号,时间复杂度 O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 3000005;
const LL MAXK = 10005;
LL n, k;
LL r[MAXN];
int main() {
scanf("%lld%lld", &n, &k);
for (LL i = 1; i <= k; i++) {
LL x, y, a, b;
scanf("%lld%lld%lld%lld", &x, &y, &a, &b);
for (LL j = 1; j <= y; j++) {
LL num = (a * j % n + b % n) % n;
r[num] += x;
}
}
for (LL i = 0; i < n; i++) {
if (r[i] != 0) {
r[i + 1] += (r[i] - 1);
r[i] = 1;
}
}
if (r[n] != 0) {
r[0] += r[n];
for (LL i = 0; i < n; i++) {
if (r[i] != 0) {
r[i + 1] += (r[i] - 1);
r[i] = 1;
}
}
}
for (LL i = 0; i < n; i++) {
if (r[i] == 0) {
printf("%lld", i);
return 0;
}
}
}
T2 拥挤的奶牛
依据题意,只需要维护每头奶牛左右两边 d d d距离以内的最大值即可。
每头奶牛好办,直接枚举就好了。左右两边也好办,优先队列维护,每次更新一下堆顶,只要保证堆顶元素在 d d d距离以内就行,队列里其他的元素不影响。
枚举是 O ( n ) O(n) O(n),由于每个元素进队出队各一次,所以也是 O ( n ) O(n) O(n),再加上优先队列的 O ( log n ) O(\log n) O(logn),总时间复杂度大概是 O ( n log n ) O(n \log n) O(nlogn),可以通过本题。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 50005;
LL n, d;
struct node {
LL x, h;
bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
priority_queue<pair<LL, LL> > q;
LL cnt[MAXN];
int main() {
// freopen("B.in","r",stdin);
// freopen("B.out","w",stdout);
scanf("%lld%lld", &n, &d);
for (LL i = 1; i <= n; i++) {
scanf("%lld%lld", &a[i].x, &a[i].h);
}
sort(a + 1, a + n + 1);
// for(LL i=1;i<=n;i++){
// cout<<a[i].x<<" "<<a[i].h<<endl;
// }
q.push({ a[1].h, a[1].x });
for (LL i = 2; i <= n; i++) {
while (!q.empty()) {
LL t1 = q.top().first;
LL t2 = q.top().second;
q.pop();
if (t2 + d >= a[i].x) {
q.push({ t1, t2 });
break;
}
}
if (!q.empty()) {
LL temp = q.top().first;
if (temp >= 2 * a[i].h) {
cnt[i]++;
}
}
q.push({ a[i].h, a[i].x });
}
while (!q.empty()) {
q.pop();
}
q.push({ a[n].h, a[n].x });
for (LL i = n - 1; i >= 1; i--) {
while (!q.empty()) {
LL t1 = q.top().first;
LL t2 = q.top().second;
q.pop();
if (t2 - d <= a[i].x) {
q.push({ t1, t2 });
break;
}
}
if (!q.empty()) {
LL temp = q.top().first;
if (temp >= 2 * a[i].h) {
cnt[i]++;
}
}
q.push({ a[i].h, a[i].x });
}
LL ans = 0;
for (LL i = 1; i <= n; i++) {
if (cnt[i] == 2) {
ans++;
}
// cout<<cnt[i]<<" ";
}
// cout<<endl;
printf("%lld", ans);
}
T3 弹簧踩高跷
首先想到的肯定是 O ( n 3 ) O(n^3) O(n3)的 D P DP DP,设 f [ i ] [ j ] f[i][j] f[i][j]表示当前从 i i i跳到 j j j以后的最大收益,转移方程也很好想。
但是 n ≤ 1000 n \leq 1000 n≤1000,无法通过,于是加上一点小优化,先修改一下循环范围,让循环次数变少(下面代码中最大循环循环次数为 166167000 166167000 166167000),然后再修改一下循环,让代码访问数组时尽量一行一行地访问,这样可以加快读取速度。于是就在速度极快的 O J OJ OJ上通过了。
然后就有了下面的赛时代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 1005;
struct node {
LL x, p;
bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
LL f[MAXN][MAXN];
LL n;
int main() {
scanf("%lld", &n);
LL ans = 0;
for (LL i = 1; i <= n; i++) {
scanf("%lld%lld", &a[i].x, &a[i].p);
ans = max(ans, a[i].p);
}
sort(a + 1, a + n + 1);
for (LL i = 1; i <= n; i++) {
for (LL j = i + 1; j <= n; j++) {
f[i][j] = f[j][i] = a[i].p + a[j].p;
}
}
for (LL i = 1; i <= n; i++) {
for (LL j = i + 1; j <= n; j++) {
for (LL k = n; k >= j + 1; k--) {
LL d1 = a[k].x - a[j].x;
LL d2 = a[j].x - a[i].x;
if (d1 >= d2) {
f[j][k] = max(f[i][j] + a[k].p, f[j][k]);
} else {
break;
}
}
}
}
for (LL i = n; i >= 1; i--) {
for (LL j = i - 1; j >= 1; j--) {
for (LL k = 1; k < j; k++) {
LL d1 = a[i].x - a[j].x;
LL d2 = a[j].x - a[k].x;
if (d2 >= d1) {
f[j][k] = max(f[i][j] + a[k].p, f[j][k]);
} else {
break;
}
}
}
}
for (LL i = 1; i <= n; i++) {
for (LL j = 1; j <= n; j++) {
ans = max(ans, f[i][j]);
}
}
printf("%lld", ans);
}
但是,这是一道2013年的题目,所以正解的时间复杂度应为 O ( n 2 ) O(n^2) O(n2),我们不能止步于此。
重新定义一下,设 f [ i ] [ j ] f[i][j] f[i][j]表示到达第 i i i个点,并且是从第 j j j个点转移过来的最大得分。
于是 f [ i ] [ j ] = max ( f [ j ] [ k ] + a [ i ] . p ) f[i][j]=\max(f[j][k]+a[i].p) f[i][j]=max(f[j][k]+a[i].p)
显然 f [ i ] [ j ] = max ( f [ j ] [ k ] ) + a [ i ] . p f[i][j]=\max(f[j][k])+a[i].p f[i][j]=max(f[j][k])+a[i].p
又因为 f [ i − 1 ] [ j ] = max ( f [ j ] [ k ] ) + a [ i − 1 ] . p f[i-1][j]=\max(f[j][k])+a[i-1].p f[i−1][j]=max(f[j][k])+a[i−1].p
和原来式子非常像,所以可以 f [ i ] [ j ] = f [ i − 1 ] [ j ] − a [ i − 1 ] . p + a [ i ] . p f[i][j]=f[i-1][j]-a[i-1].p+a[i].p f[i][j]=f[i−1][j]−a[i−1].p+a[i].p,但是这个方程的定义范围是 a [ j ] . x − a [ k ] . x ≤ a [ i − 1 ] . x − a [ j ] . x a[j].x-a[k].x \leq a[i-1].x-a[j].x a[j].x−a[k].x≤a[i−1].x−a[j].x,又因为 a [ i − 1 ] . x < a [ i ] . x a[i-1].x < a[i].x a[i−1].x<a[i].x,满足条件的 k k k会变多,所以我们需要先将拓展到的 k k k取一个最大值再转移。
正反各做一遍 D P DP DP就行。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 1005;
struct node {
LL x, p;
bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
LL f[MAXN][MAXN];
LL n;
int main() {
scanf("%lld", &n);
LL ans = 0;
for (LL i = 1; i <= n; i++) {
scanf("%lld%lld", &a[i].x, &a[i].p);
ans = max(ans, a[i].p);
}
sort(a + 1, a + n + 1);
for (LL i = 1; i <= n; i++) {
for (LL j = 1; j <= n; j++) {
if (i != j)
f[i][j] = a[i].p + a[j].p;
else
f[i][j] = a[i].p;
}
}
for (LL j = 1; j <= n; j++) {
for (LL i = j + 1, xx = j + 1; i <= n; i++) {
f[i][j] = f[i - 1][j] - a[i - 1].p;
while (xx > 1 && a[j].x - a[xx - 1].x <= a[i].x - a[j].x) {
f[i][j] = max(f[i][j], f[j][--xx]);
}
f[i][j] += a[i].p;
ans = max(ans, f[i][j]);
}
}
for (LL j = n; j >= 1; j--) {
for (LL i = j - 1, xx = j - 1; i >= 1; i--) {
f[i][j] = f[i + 1][j] - a[i + 1].p;
while (xx < n && a[xx + 1].x - a[j].x <= a[j].x - a[i].x) {
f[i][j] = max(f[i][j], f[j][++xx]);
}
f[i][j] += a[i].p;
ans = max(ans, f[i][j]);
}
}
printf("%lld", ans);
}