4 November in ss

Contest

A. 输油管道问题

某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有 \(n\) 口油井的油田。从每口油井都要有一条输油管道沿最短路经 (或南或北) 与主管道相连。如果给定 \(n\) 口油井的位置, 即它们的 \(x\) 坐标(东西向)和 \(y\) 坐标(南北向), 应如何确定主管道的最优位置, 即使各油井到主管道之间的输油管道长度总和最小的位置? 证明可在线性时间内确定主管道的最优位置。 \(1\le n\le 10000\)

观察到 \(x\) 坐标与答案无关,题目转变为求 \(\sum |Y-y_i|\) 的最大值。

贪心。易证,中位数即为 \(Y\) 的值。

当然我在考场上是用玄学二分做出来的。)

B. ssoj3147 跑步(run)

小 A 要参加一次 1000 米跑步比赛。 一开始他的速度为 1 m/s,在跑步过程中他会越跑越累,所以会进行若干次减速。第一次减速后,速度变为 \(\frac{1}{2}\) m/s;第二次减速后,速度变为 \(\frac{1}{3}\) m/s,依次类推。你现在知道小 A 会在什么时间或者是哪里进行减速,有 \(n\) 个这样的事件:T x 表示小 A 会在比赛开始后 \(x\) 秒进行一次减速。D x 表示小 A 会在恰好跑了 \(x\) 米之后进行一次减速。现在给你这些事件。你要帮小 A 算出他用这种方式到达终点所需要的时间是多少秒。可能存在两个事件恰好同时发生,那么在这一时刻速度就会减小两次。

模拟即可。注意时间、位移、速度的先后顺序,以及哨兵变量自增的时机。考场上就是这点折腾了我好久。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
int n, t[10003], d[10003], vn=1;
double lt, ld, v=1;
 
int main() {
    scanf("%d", &n);
    for (int k=1; k<=n; ++k) {
        int a=0;
        char c=getchar(); while (c!='T' && c!='D') c=getchar(); scanf("%d", &a);
        if (c=='T') t[++t[0]]=a; else d[++d[0]]=a;
    }
    sort(t+1, t+t[0]+1); sort(d+1, d+d[0]+1);
    for (int i=1, j=1; i<=t[0] && j<=d[0]; ) {
        double dt=(t[i]-lt)*v + ld;
        if (dt<d[j]) lt=t[i++], ld=dt, v=1.0/(++vn);
        else if (dt>d[j]) lt+=(d[j]-ld)/v, ld=d[j++], v=1.0/(++vn);
        else lt=t[i++], ld=dt, ++j, vn+=2, v=1.0/vn;
        if (i>t[0]) { 
            for (; j<=d[0]; ++j) lt+=(d[j]-ld)/v, ld=d[j], v=1.0/(++vn);
            break;
        }
        if (j>d[0]) {
            for (; i<=t[0]; ++i) ld+=(t[i]-lt)*v, lt=t[i], v=1.0/(++vn);
            break;
        }
    }
    if (ld<1000.0) lt+=(1000.0-ld)/v;
    printf("%.0lf\n", lt);
    return 0;
}

C. ssoj2977 流图(flow)

如果一张有向图存在一个点可以到达所有的点,则称这个图是一个流图 (Flow Graph)。现在给定一张有向图,请判断这张图是不是流图;如果是,输出所有的点 \(r\) 满足点 \(r\) 可以到达图中的所有点。\(1\le n,m\le 1000\)

DFS?!! 注意到不重复 DFS 全图的时间复杂度是 \(O(m)\),所以可以采用 \(O(nm)\) 的极端暴力做法,即以每个点为起点跑 DFS。太暴力了考场上没想出来……

#include <cstdio>
#include <cstring>
 
int n, m, ant, ans[1005];
int head[1005], nex[1005], to[1005];
int v[1005], cnt;
 
inline void add(int x, int y) {
    nex[++head[0]]=head[x], head[x]=head[0], to[head[0]]=y;
}
void dfs(int x) {
    for (int i=head[x]; i; i=nex[i]) if (!v[to[i]])
        ++cnt, v[to[i]]=1, dfs(to[i]);
}
 
int main() {
    scanf("%d%d", &n, &m);
    for (int i=1, a, b; i<=m; ++i)
        scanf("%d%d", &a, &b), add(a, b);
    for (int i=1; i<=n; ++i) {
        memset(v, 0, sizeof v); cnt=1, v[i]=1;
        dfs(i); if (cnt==n) ans[++ant]=i;
    }
    if (!ant) {putchar('0'); return 0; }
    printf("%d\n", ant);
    for (int i=1; i<=ant; ++i) printf("%d ", ans[i]);
    return 0;
}

D. ssoj3146 物品选取

\(n\) 件物品,每件物品尺寸为 \(A_i\),价值为 \(B_i\)。令 \(A_\max\) 为所选物品中最大的尺寸,\(A_\min\) 为所选物品中最小的尺寸。令 \(sum\) 为选择物品的价值之和。选择一些物品,使得 \(sum−(A_\max−A_\min)\) 最大。\(2\le n\le 5\times 10^5, 1\le A_i\le 10^{15}, 1\le B_i\le 10^9\)

容易想到按照 \(A\) 排序,枚举最值。\(sum\) 预处理前缀和。可得 \(O(n^2)\) 做法。显然不是正解。

考虑用算式表示答案:令 \(i\) 为最大值,\(j\) 为最小值,最大化 \(sum(i)-sum(j-1)-A_i+A_j=sum(i)-A_i+A_j-sum(j-1)\)。当前状态答案只与 \(i,j\) 有关。

尺取法。枚举 \(i\),对于每个 \(i\),维护 \(A_j-sum(j-1)\) 的最大值。时间复杂度降为 \(O(n)\)

#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
 
int n; ll sum[500003], ans;
struct node {
    ll A, B;
    bool operator < (const node& a) const {return A<a.A; }
} p[500003];
 
int main() {
    scanf("%d", &n);
    for (int i=1; i<=n; ++i) scanf("%lld%lld", &p[i].A, &p[i].B);
    sort(p+1, p+n+1);
    for (int i=1; i<=n; ++i) sum[i]=sum[i-1]+p[i].B;
    ll m=0, t=0;
    for (int i=1; i<=n; ++i) {
        m=sum[i]-p[i].A;
        t=max(t, p[i].A-sum[i-1]);
        ans=max(ans, m+t);
    }
    printf("%lld\n", ans);
    return 0;
}

小结

都是简单的做法,但是因为时间复杂度估计失误,不敢于写出来。看来暴力、贪心写得不够多……是啊,既然都是 NOIP 范围内的知识,那怎么区分高手?还是依靠思维难度取胜!

posted @ 2018-11-06 14:43  greyqz  阅读(225)  评论(0编辑  收藏  举报