机房水题欢乐赛 2016-04-17

GDOI校队选拔赛。。

T1: 巨额奖金

JSOI 2008 最小生成树计数
我在想BZOJ为什么要换题面。。

T2: 会场预约

【问题描述】A 大厦有一间空的礼堂,可以为企业或单位提供会议场所。这些会议中的大多
数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议
的时间申请不能冲突。也就是说,前一个会议结束日期必须在后一个会议的开始日期之前。
所以,如果要接受一个新的场地预约申请,就必须拒绝掉与这个申请相冲突的预约。
一般来说,如果 A 大夏方面事先已经接受了一个会场预约,例如从 10 日到 15 日,就不
会再接受与之相冲突的预约,例如从 12 日到 17 日。不过,有时出于经济利益,A 大夏方面
有时会接受一个新的会场预约,而拒绝掉一个甚至几个之前预定好的预约。
于是礼堂管理人员 Q 的笔记上经常记录着这样的信息:
本题中为方便起见,所有的日期都用一个数字表示。例如,如果一个为期 10 天的会议
从“90 日”到“99 日”,那么下一个会议最早只能在“100 日”开始。
最近,这个业务的工作量与日俱增,礼堂管理员 Q 希望参加 SGOI 的你替他设计一套计
算机系统来简化他的工作。这个系统只能执行下面两个操作:
A 操作:有一个新的预约是从“star 日”到“end 日”,并且拒绝所有与它相冲突的预
约。执行这个操作时候,你的系统应当返回为了这个新预约而拒绝掉的预约个数,以方便 Q
与自己的记录相核对。
B 操作:请你的系统返回当前的仍然有效的预约的总数。
【文件输入】输入文件:booking.in。第一行是一个整数 N,表示你的系统接受的操作总数。
接下去 N 行每行表示一个操作。每一行的格式为下面两者之一:“A star end”表示一个 A
操作;“B” 表示一个 B 操作。
【文件输出】输出文件:booking.out。输出有 N 行,每行依次对应一个输入。表示你的系
统对于该操作的返回值。
【输入输出样例】
booking.in
6
A 10 15
A 17 19
A 12 17
A 90 99
A 11 12
B
booking.out
0
0
2
0
1
2
【数据规模】
对于 10%的数据,N≤2500。
对于 60%的数据,N≤50000。
对于 100%的数据,N≤200000,1≤start,end≤100000。

水分姿势

考场上想了个 O(nlog2n) 水过的方法。。结果显示最大的数据点0.78s。。

二分查找+树状数组

树状数组:维护1~k有多少线段端点。
二分查找:查找区间内的所有端点。

思路:对于每个查询A,有两种情况:

  1. 有线段部分覆盖区间,此时存在1或2端点在区间内,
    二分查找利用树状数组找出这些端点,并删除。

  2. 有线段全部覆盖区间,肯定只有1条覆盖此区间,
    而且其左右端点最接近区间的左右端点(在区间外),
    因此二分答案最接近区间的端点,判断是否跨越区间。

因此二分查找可以写成找最靠近查询右端点的点。。
这样情况2就可以找左半区间了。

需要另外开数组在端点处保存对应线段,
显然一个坐标只会存1个线段。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
#define rep(i,j,k) for(i=j;i<k;++i)
#define test printf("FUCK");
#define ms(i,j) memset(i,j,sizeof(i))
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f, mod = 1000000007;

int read() {
    int s = 0, f = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
    for (; '0' <= ch && ch <= '9'; ch = getchar()) s = s * 10 + ch - '0';
    return s * f;
}

int qpow(int a, int b) {
    int s = 1;
    for (; b; b /= 2, a = a * a % mod)
        if (b & 1) s = s * a % mod;
    return s;
}

void getmax(int &a, int b) { if (a < b) a = b; }
void getmin(int &a, int b) { if (a > b) a = b; }

namespace Solve {
    const int N = 200005;

    int c[N];
    struct Seg { int l, r; } a[N];

    void addPRIVATE(int i, int x) {
        for (; i <= 100000; i += i & -i) c[i] += x;
    }

    void add(const Seg &s, int x) {
        addPRIVATE(s.l, x); addPRIVATE(s.r, x);
    }

    int sum(int i) {
        int s = 0;
        for (; i; i -= i & -i) s += c[i];
        return s;
    }

    int binary(int l, int r) {
        int base = sum(r), ans = -1;
        while (l <= r) {
            int mid = l + r >> 1, mm = sum(mid);
            if (mm == base && mm == sum(mid - 1)) r = mid - 1;
            else l = mid + 1, ans = mid;
        }
        return ans;
    }

    void solve() {
        int t = read(), tot = 0, l, r, x, del; char op[8];
        while (t--) {
            scanf("%s", op);
            if (op[0] == 'A') {
                l = read(), r = read(); del = 0;
                // Case 1
                while ((x = binary(l, r)) != -1)
                    add(a[x], -1), ++del;
                // Case 2
                x = binary(1, l - 1);
                if (x != -1 && a[x].l < l && a[x].r > r)
                    add(a[x], -1), ++del;
                add(a[l] = a[r] = (Seg) { l, r }, 1);
                tot += 1 - del;
                printf("%d\n", del);
            } else
                printf("%d\n", tot);
        }
    }
}

#define FILENAME "booking"
int main() {
    freopen(FILENAME".in","r",stdin);
    freopen(FILENAME".out","w",stdout);
    Solve::solve();
    return 0;
}

正确姿势

实际上从水分姿势中可以联想。。
我们一直要知道某个区间内有哪些点,最靠近区间边界的点是什么,那么set即可。。

好久没用过set,对set无感了。。以下程序为在线手打未测试正确性如有错误权当提供思路。

#include <cstdio>
#include <set>
using namespace std;
const int N = 100005;
int read() {
    int s = 0, f = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
    for (; '0' <= ch && ch <= '9'; ch = getchar()) s = s * 10 + ch - '0';
    return s * f;
}
struct Seg { int l, r; } a[N];
set<int> ss;
int main() {
    int t = read(), tot = 0, l, r, x, del; char op[8];
    while (t--) {
        scanf("%s", op);
        if (op[0] == 'A') {
            l = read(), r = read(); del = 0;
            set<int>::iterator x;
            // Case 1
            while (1) {
                x = ss.lower_bound(l);
                if (x != ss.end() && *x <= r)
                    ss.erase(a[x].l), ss.erase(a[x].r), ++del;
            }
            // Case 2
            x = ss.lower_bound(r + 1);
            if (x != ss.end() && a[x].l < l && a[x].r > r)
                    ss.erase(a[x].l), ss.erase(a[x].r), ++del;
            a[l] = a[r] = (Seg) { l, r };
            ss.insert(l); ss.insert(r);
            tot += 1 - del;
            printf("%d\n", del);
        } else
            printf("%d\n", tot);
    }
    return 0;
}

巴士线路

【问题描述】OIben 周围有 N 个小村庄。村庄与村庄之间有公路,每一条公路连接两个村庄。
因为这些村庄并不富裕,所以,当初建公路时,这些公路恰好将这些村庄连通,也就是说要
从一个村庄沿着公路走到另一个村庄的方式都是唯一的。于是,有时两个很近的村庄之间要
走很长的路。所以,村民们就希望能开通这些公路上的公共交通路线,方便大家出行。
巴士公司采纳了这个意见。同时,巴士公司认为这些巴士线路必须满足下述四个原则:
(1)每条巴士线路的起点和终点都在村庄内,巴士开行的线路都必须沿着现有的公路;
(2)每条公路都要有巴士线路覆盖,这样村民们的出行就只需要换乘巴士车就行了;
(3)每条公路只被一条巴士线路覆盖,且只被覆盖一次,否则,巴士公司觉得在成本
上不划算;
(4)巴士线路的总数应当最少,这样才便于管理。
如果 6 个村庄之间的 5 条公路如图表示:
那么,有 3 条巴士线路就能满足上面的条件:1-2-3;2-4;5-4-6。
不过,村民们自然认为“巴士换乘”是不方便的,因此他们希望从在一个村庄乘车去另
一个村庄的路上换乘次数的最大值尽可能少。例如上面这个线路安排中,从村庄 1 到村庄 6
需要换乘 2 次,是最大的换乘次数。
另一方面,巴士公司认为,一条公交线路越长意味着,如果巴士车发生故障,因此而受
影响耽误时间的乘客就越多。所以巴士公司希望,最长的一条线路尽可能短。所谓短,就是
经过的村庄少。
请参加过多次 SGOI 的 OIben 的你来帮助设计,在充分考虑这些条件(4 条)的前提下,
必须要考虑两方面的因素并计算出:(1)换乘次数的最大值的最小可能值;(2)最长的线路
途经村庄数的最小可能值。
【文件输入】输入文件:bus.in。第一行是一个整数 N,表示村庄的数目。接下去每行有两
个整数 x,y,分别描述一条公路,表示村庄 x,y 之间有一条公路。每条公路只会被描述一次。
【文件输出】输出文件:bus.out。输出有三行。第一行是一个整数,表示巴士线路安排中
包含的路线数。第二行是一个整数,表示换乘次数的最大值的最小可能值。第一行是一个整
数,表示最长的线路途经村庄数的最小可能值。
【输入输出样例】
Input
6
1 2
2 3
4 5
4 6
2 4
Output
3
2
3
【数据规模】
对于 70%的数据,N≤300。
对于 100%的数据,N≤100000。

题解

树的最少路径覆盖问题?
显然如果任意两条路径不共端点的方案即满足前4点要求。然后每个点就只有在端点上和不在端点上两个状态,不在端点上的点度必为偶数,在的必为奇数。于是最少路径数即奇数度点的个数除以2。
然后两个问很显然是二分答案。
第1问即任意两点间的路径至多包含mid+1条边。
第2问POI 2004 SZN

T4: 宝石纪念币

【问题描述】W 刚结束在 C 岛的度假,正准备离开的时候,他想送给他的好友 Y 一份特殊的
礼物—C 岛上的特别手工艺品宝石纪念币。
宝石纪念币的一面上刻者 C 岛的名字 C,或者收礼物的人,比如“to Y”。不过特别的
是,每枚纪念币的反面,依次均匀的镶着一圈共 N 颗彩色的宝石。如图 1 所示是一个 N=7
时的简单例子。

图 1 图 2(a) 图 2(b)
因为纪念币是圆的,所以如果两种“宝石颜色的排布”如果经过旋转后对应位置的颜色
相重合,就认为它们是相同的排列方式(请注意:纪念币只是一面镶宝石,所以,两种排布
若经过翻转以后是可以重合的,但只经过旋转无法使两者重合,则认为它们是不同的排布)。
例如,图 2 的(a)和(b)所示的两种排布方式就是相同的。
另外,由于 C 岛当地的习俗,每枚钱币上的宝石都只能嵌奇数颗宝石,不然就认为是不
吉利。
宝石纪念币是现场制作的,游客可以选择自己喜欢的颜色的宝石。所以,W 选择了他最
喜欢的 17 种颜色。他想知道,如果他要求纪念币把这 17 种颜色的宝石都用上的话,可以制
作出多少枚不同的纪念币。
由于答案可能很大,你只需要计算答案的最后 120 位就可以了。
【文件输入】输入文件:coin.in。一行一个正奇整数 N。1≤N≤ 10^9 。
【文件输出】输出文件:coin.out。输出一行,表示用全部的 17 种颜色的不同纪念币的枚
数,不过只需要输出答案的最后 120 位。这 120 位从高位到低位依次输出,位数不足的用 0在高位补足。
【样例】
Input
17
Output
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020922789888000
Input
21
Output
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000592012913384116224000
【数据范围】
60% 1<=N<=10^5
100% 1<=N<=10^9

题解

什么时候都不要先看数据范围。。
(除了状压以外)

60%的做法:
先不考虑环的问题。
fi,j 表示到位置i未知恰好有j种颜色的方案数。有

fi,j=fi1,j1+jfi1,j

如果没有要求不同的条件,答案显然就是
A1717fn,17

注意到 n 是质数的时候,循环串间不会有重复的问题。答案明了。

不是质数的时候呢?比如aabaabaab只存在同构baabaabaa和abaabaaba。
因此考虑分开处理各循环节
gm 表示长度为m的不循环的17种都出现的本质不同的总方案数,有

gm=17!m(fm,17d|mdgd)

则有
ans=d|ngd

满分做法并不会。。
Polya定理并不会。。

你可能感兴趣的:(机房水题欢乐赛 2016-04-17)