XJOI 2018提高组训训练15 @2018-08-13 总结

前言

吴老师回家了,于是XY来了。突然从ACM调到OI的感觉真的一点都不好…‘


T1 士兵训练

题目描述

N N 个士兵排成一队进行军事训练,每个士兵的等级用1...K范围内的数来表示,长官每隔 1 1 小时就随便说出M个等级 a1,a2...,am a 1 , a 2 . . . , a m 1aiK 1 ≤ a i ≤ K M M 个等级中允许有重复),如果这M个等级组成的序列是排成一队的 N N 个士兵等级序列的子序列,那么训练继续;否则训练结束。长官想知道,M至少为多少时,训练才有可能结束。
例:士兵等级序列为1 5 3 2 5 1 3 4 4 2 5 1 2 3,长官说出的等级序列为5 4,那么训练继续,如果长官说出的等级序列为4 4 4,那么训练结束。

数据范围

1N100000,1K10000 1 ≤ N ≤ 100000 , 1 ≤ K ≤ 10000

分析

第一眼看,马上冒出一个十分naive的想法:找出现次数最少的等级,答案就是最少出现次数+1。然后马上发现这个思路错了…
例如序列为1 1 1 2 2的时候,若 K=3 K = 3 ,按原来的方法求得是3,而事实上1就可以了(等级序列3)。我们原来是尽可能让序列里的等级相同,而事实上,我们应该尽可能让序列里的等级不同,这样才是最优。因此我们把原序列分块,每块遍历 1 1 ~K所有等级并且最小,这样我们就可以推出答案即块数+1。我们设块数为 M M ,容易发现长度为M的序列肯定都是可以的,但是 M+1 M + 1 就不一定了。

参考程序

// XJOI 978 1
#include <cstdio>
const int MAXK = 10005;

int cnt[MAXK] = {0}, N, K;

int main() {
    int i, ai, ans = 0, k = 0;
    scanf("%d%d", &N, &K);
    for (i = 0; i < N; i++) {
        scanf("%d", &ai);
        if (cnt[ai] == ans) {
            ++cnt[ai];
            if (++k == K) ans++, k = 0;
        }
    }
    printf("%d\n", ++ans);
    return 0;
}

T2 三角形(TRINGLE)

题目描述

离圣诞节只有一个月了,家里要你准备一个很大的星星,然后把它粘在圣诞树的顶端。你已经准备好了一个三角形的银色包装纸来做星星,可忽然有一天你发现在这张大纸上被弄了好多的小洞,原来是你的弟弟妹妹已经从这张大纸上剪掉了许多小的三角形,做了很多小星星。现在,你只能将就了,看怎样才能从这残缺的大纸中,找出一块最大的完整的三角形。
如下图所示,假设给你一张大的三角形纸,里面黑的三角形是被你的弟弟妹妹剪掉了的,需要你在剩下的白色区域中找出一个最大的三角形区域。

分析

简单的DP,考虑两个不同的方向DP时,分别以某个点为三角形顶点的时候三角形最大的边长,方程很好列,详见程序。

参考程序

 // XJOI 978 2
#include <cstdio>
#include <algorithm>
const int MAXN = 105;

int DP[MAXN][MAXN << 1], N;
char tgnl[MAXN][MAXN << 1];

void solve();

int main() {
    int k = 0;
    while (scanf("%d", &N) == 1 && N) {
        printf("Triangle #%d\n", ++k);
        solve();
    }
    return 0;
}

void solve() {
    using std::max;
    using std::min;
    int i, j, lim;
    for (i = N; i > 0; i--) scanf("%s", tgnl[i]);
    int res = 0;
    for (i = 0, lim = (N << 1) - 1; i < lim; i++)
        if (!(i & 1)) res = max(res, DP[N][i] = tgnl[N][i] == 'O' ? 1 : 0);
    for (i = N - 1; i > 0; i--)
        for (j = 0, lim = (i << 1) - 1; j < lim; j++)
            if (!(j & 1)) {
                if (tgnl[i][j] == 'X') DP[i][j] = 0;
                else res = max(res, DP[i][j] = tgnl[i + 1][j + 1] == 'O' ? min(DP[i + 1][j], DP[i + 1][j + 2]) + 1 : 1);
            }
    for (i = 2; i <= N; i++)
        for (j = 3, lim = (i << 1) - 2, DP[i][1] = tgnl[i][1] == 'O' ? 1 : 0, DP[i][lim] = tgnl[i][lim] == 'O' ? 1 : 0; j < lim; j++)      // 边界很搞
            if (j & 1) {
                if (tgnl[i][j] == 'X') DP[i][j] = 0;
                else res = max(res, DP[i][j] = tgnl[i - 1][j - 1] == 'O' ? min(DP[i - 1][j], DP[i - 1][j - 2]) + 1 : 1);   // 一开始这里一个i-1打成i+1了...呜呜呜...直接爆零...
            }
    printf("The largest triangle area is %d\n", res * res);
}

T3 双子树

题目描述

这一天,随身带来的干粮豆吃玩了,必须有人去找些食物来吃才行。可Z4的懒惰是出了名的,谁会愿意去呢?经过一番商讨,大家最后决定以老规矩“猜十次”的方式决定人选。可怜的立方由于猜拳技术不过关,屡战屡败,只好踏上觅食的征途。
立方后来找到了两棵奇怪的树,由于它们的形态十分相像,双子座的立方一时兴起便把它们命名为“双子树”。可双子树的果实能不能吃呢?“管它呢,先摘了再说。”于是身手敏捷的立方便爬上其中一棵,摘下一个大大的果实。可就在此时,另一棵却有几个果实坠地。
立方细心一看,发现这双子树上的某些果实有一些细丝相连,只要摘下其中一棵树的某一个果实,另一棵树将会有相应的一个或多个(也可能没有)果实坠地而摔坏,这些果实都和摘下的果实用细丝相连。摔坏的果实当然不能吃了。而且,立方发现,那些细丝是没办法弄断的,除非摘下它两端的果实的其中一个。由于这时只有立方一人(好没义气的Z4啊……),他只能眼睁睁地看着它们摔坏。也就是说,立方无法同时摘取任一条细丝两端上的两个果实。于是,不同的摘法最后会得到不同数量的果实,而立方将会用随身带的一个容量为 V V (表示能装V个果实)的大袋子将它们装好。
馋嘴的立方当然希望摘得越多越好啦,那么,他最多可以得到多少个果实呢?
特别地,任两个果实间最多只会有一条细丝相连,同一棵树上的果实间不会有细丝相连,当袋子装满后,立方的口还可以塞进一个。

分析

第一眼看成树型DP了…然后很开心地打完发现好像连成的图不一定是树…再一看原来是个最大独立集,裸题裸题,注意立方嘴里还可以塞一个就好啦。

参考程序

// vjudge 978 3
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXV = 505;
const int MAXE = 250005;
struct Edge {
    int to, next;
} E[MAXE];

int tote = 0, V, N1, N2, M, last[MAXV] = {0}, match[MAXV] = {0};
bool vis[MAXV];

inline void add_edge(int u, int v) {
    E[++tote].to = v, E[tote].next = last[u], last[u] = tote;
}
bool dfs(int u);

int main() {
    scanf("%d%d%d%d", &V, &N1, &N2, &M);
    int i, ai, bi;
    for (i = 0; i < M; i++) {
        scanf("%d%d", &ai, &bi);
        add_edge(ai, bi);   // 一开始这里打了个bi+N1,爆RE了,其实直接写bi就好
    }
    int res = N1 + N2;
    for (i = 1; i <= N1; i++) {
        memset(vis, 0, sizeof(vis));
        if (dfs(i)) --res;
    }
    printf("%d\n", std::min(V + 1, res));
    return 0;
}

bool dfs(int u) {
    for (int i = last[u], v; i; i = E[i].next)
        if (!vis[v = E[i].to]) {
            vis[v] = true;
            if (!match[v] || dfs(match[v])) {
                match[v] = u;
                return true;
            }
        }
    return false;
}

T4 奶牛浴场

题目描述

在长为 P P ,宽为Q的长方形牧场中,有 N N 个产奶点,为了使奶牛产更多的奶,需要在牧场中建一个边与牧场的边平行的长方形浴场,使奶牛们生活得更愉快一些。当然浴场不能破坏产奶点,但产奶点在浴场边上是可以的。已知各产奶点的坐标,求建最大浴场的面积。

数据范围

1P,Q,N103

分析

求最大子矩形有多种方法,放到一篇独立的文章中介绍。这题使用的是复杂度为 O(N2) O ( N 2 ) 的算法。本题需注意,产奶点是在点上,而需要求的是格子的面积。

参考程序

// XJOI 978 4
#include <cstdio>
#include <utility>
#include <algorithm>
#define fir first
#define sec second
const int MAXN = 5010;
typedef std::pair<int, int> P;

P A[MAXN];
int N, R, C;

int main() {
    scanf("%d%d%d", &N, &R, &C);
    int i, j;
    for (i = 0; i < N; i++) scanf("%d%d", &A[i].fir, &A[i].sec);
    A[N++] = P(0, 0), A[N++] = P(0, C), A[N++] = P(R, 0), A[N++] = P(R, C);
    std::sort(A, A + N);
    int res = 0;
    for (i = 0; i < N; i++) {
        int lb = 0, ub = C;
        // printf("i=%d, (%d, %d)\n", i, A[i].fir, A[i].sec);
        for (j = i + 1; j < N; j++)
            if (A[i].fir != A[j].fir) {
                // printf("  j=%d, (%d, %d), lb=%d, ub=%d\n", j, A[j].fir, A[j].sec, lb, ub);
                res = std::max(res, (ub - lb) * (A[j].fir - A[i].fir));
                if (A[j].sec <= A[i].sec) lb = std::max(lb, A[j].sec);
                if (A[j].sec >= A[i].sec) ub = std::min(ub, A[j].sec);
            }
        std::swap(A[i].fir, A[i].sec);
    }
    std::sort(A, A + N);
    for (i = 0; i < N; i++) {
        int lb = 0, ub = R;
        for (j = i + 1; j < N; j++)
            if (A[i].fir != A[j].fir) {
                res = std::max(res, (ub - lb) * (A[j].fir - A[i].fir));
                if (A[j].sec <= A[i].sec) lb = std::max(lb, A[j].sec);
                if (A[j].sec >= A[i].sec) ub = std::min(ub, A[j].sec);
            }
        std::swap(A[i].fir, A[i].sec);
    }
    printf("%d\n", res);
    return 0;
}

T5 点和线

题目描述

平面上有一些点,你可以用线段将两点连接起来。那么有多少种方法可以把这些点连续地连起来,使得任何两个线段都不交叉。
显然,三个点只有一种方法。四个点最多只有3种方法。写一个程序计算方法总数。最多只有10个点。

分析

搜索即可。然后需要一点几何知识,反正我就直接把板子拉过来了…(逃…)
然后关于计数:注意到一个环上面会被统计多次,因此我们直接固定第一个点,但是又因为一个圈顺时针和逆时针会被重复计,最后答案还要除以2。

参考程序

#include <cstdio>
#include <cmath>
#include <algorithm>
const int MAXP = 12;
const double EPS = 1e-6;
struct Point {
    double x, y;
} P[MAXP];
// typedef Point Vector;

int N, ans = 0, A[MAXP];
bool used[MAXP];

template <typename T>
inline bool equal_real(T a, T b) { return fabs(a - b) <= EPS; }
inline double cross(const Point & a, const Point & b, const Point & c) {
    return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
inline bool seg_cross(const Point & p1, const Point & p2, const Point & q1, const Point & q2) {
    using std::min;
    using std::max;
    return  min(p1.x, p2.x) <= max(q1.x, q2.x) &&
            min(q1.x, q2.x) <= max(p1.x, p2.x) &&
            min(p1.y, p2.y) <= max(q1.y, q2.y) &&
            min(q1.y, q2.y) <= max(p1.y, p2.y) &&
            cross(p1, q2, q1) * cross(p2, q2, q1) <= 0 &&
            cross(q1, p2, p1) * cross(q2, p2, p1) <= 0;
    // 一开始这里一个2打成1了,又一题爆零...
}
void dfs(int dep);

int main() {
    for (ans = N = 0; scanf("%lf%lf", &P[N].x, &P[N].y) == 2; N++)
        if (equal_real(P[N].x, 0.0) && equal_real(P[N].y, 0.0)) break;
    N++;
    A[0] = 0, used[0] = true;
    dfs(1);
    printf("%d\n", ans >> 1);
    return 0;
}

void dfs(int dep) {
    bool fail;
    int i, j;
    if (dep == N) {
    //  注意判断最后首尾相连是否合法
        for (fail = false, j = dep - 2; j > 1; j--)
            if (seg_cross(P[A[0]], P[A[dep - 1]], P[A[j]], P[A[j - 1]])) { fail = true; break; }
        if (fail) return;
        ++ans;
        return;
    }
    for (i = 0; i < N; i++)
        if (!used[i]) {
        //  判断新线段是否与之前的线段相交
            for (fail = false, j = dep - 2; j > 0; j--)
                if (seg_cross(P[i], P[A[dep - 1]], P[A[j]], P[A[j - 1]])) { fail = true; break; }
            if (fail) continue;
            used[i] = true, A[dep] = i;
            dfs(dep + 1);
            used[i] = false;
        }
}

总结

现在回到OI赛制,做题需要冷静谨慎下来了。今天的题,三道都是因为忽视了一个很小的错误而爆零,这是打ACM留下的极大病状。并且,本次测验也发现了一些知识点还掌握得不熟练,有:极大化子矩形、计算几何、二分图的知识点,应立即补上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值