2015年百度之星程序设计大赛 - 初赛(1) 【题解】

1001.超级赛亚ACMer  Accepts: 867   Submissions: 5329
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)

Problem Description
百小度是一个ACMer,也是一个超级赛亚人,每个ACMer都有一个战斗力,包括百小度。
所谓超级赛亚人的定义,是说如果在对抗中刚好接近极限状态,那就会激发斗志,实力提升。


具体来说,就是百小度现在要接受一些ACMer的挑战了,这些ACMer有n个人,第i个人的战斗力是a[i]。


百小度接下来可以自主安排与这n个ACMer的PK顺序,他要想在PK赛中赢过另外一个ACMer,就必须使得自己的战斗力不小于对方(平局情况他会按照百小度字典上的规则把自己排在第一)。


如果百小度的战斗力大于对方,那么百小度就会轻易获胜,得不到锻炼并且骄傲起来,他以后的战斗力将保持在这个值,再也不会发生改变。
如果百小度的战斗力等于对方,那么百小度在获胜的同时也会感到很吃力,但是这会激发百小度的斗志,使得他刻苦刷题,在下场PK赛之前,战斗力最多提升k点(即可以提升0~k点任意值)。



k是百小度的潜力提升上限,会被给定一个初始值,这个潜力提升上限k在后面的比赛中会下降。


每战胜一个ACMer,这个潜力上限k将减少1(因为超级赛亚人百小度也会感到累),但k最低只会减少到0,即不会出现战斗力下降的情况
。也就是第一次比赛如果激发了百小度的斗志,他能把战斗力提升0~k的任一值,如果第二次比赛继续被激发斗志,他能在第一次提升后的基础上,把战斗力再提升0 max(0,k−1),依次类推……


m是百小度的初始战斗力上限,也就是百小度第一次进行PK赛的时候,可以选择0~m的任意一个值作为他的战斗力。



现在希望你编写程序,判断一下百小度是否战胜所有的ACMer。
Input
输入包含多组数据(数据不超过500组)

第一行一个整数T,表示T组数据


对于每组数据,第一行包括三个整数n,m,k(1≤n≤10^4,1≤m,k≤10^8)


第二行包括n个正整数,表示彪形大汉的战斗力(战斗力为不超过10^12的正整数)
Output
对于每组数据,先输出一行Case #i: (1≤i≤T)


如果百小度能打败所有的ACMer,再输出"why am I so diao?"


否则再输出"madan!"
Sample Input
2
5 11 3
15 13 10 9 8
5 11 3
8 9 10 13 16
Sample Output
Case #1:
why am I so diao?
Case #2:
madan!

Hint
第一组样例解释
5个ACMer,初始战斗力选择范围是[0,11],接下来每场战斗力提升上限是3,2,1,0,0,...,0
百小度首先使得自己的初始战斗力为10,打败战斗力为10的第一个ACMer,
然后选择战斗力提升3,变成13,打败战斗力为13的第二个ACMer,
然后选择战斗力提升2,变成15,打败战斗力为15的第三个ACMer,
之后再以任意顺序打败剩下的ACMer

题目分析:
直接模拟即可,每次选择小于等于m+k的最大的a[i],然后m变成a[i],–k。最后看m是不是等于a[n]即可。

my  code:

#include 
#include 
#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define mid ( ( l + r ) >> 1 )
#define root 1 , 1 , cnt

const int MAXN = 100005 ;
const int INF = 0x3f3f3f3f ;

LL a[MAXN] ;
int n ;
LL m , k ;


void solve () {
    scanf ( "%d%I64d%I64d" , &n , &m , &k ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        scanf ( "%I64d" , &a[i] ) ;
    }
    sort ( a + 1 , a + n + 1 ) ;
    if ( m < a[1] ) {
        printf ( "madan!\n" ) ;
        return ;
    }
    ++ k ;
    m -= k ;
    for ( int i = 0 ; i <= n ; ) {
        while ( i < n && a[i + 1] <= m + k ) ++ i ;
        m = a[i] ;
        -- k ;
        ++ i ;
        if ( i <= n && m + k < a[i] ) {
            printf ( "madan!\n" ) ;
            return ;
        }
    }
    printf ( "why am I so diao?\n" ) ;
}

int main () {
    int T ;
    scanf ( "%d" , &T ) ;
    for ( int i = 1 ; i <= T ; ++ i ) {
        printf ( "Case #%d:\n" , i ) ;
        solve () ;
    }
    return 0 ;
}

1002.找连续数  Accepts: 401   Submissions: 1911
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
小度熊拿到了一个无序的数组,对于这个数组,小度熊想知道是否能找到一个k 的区间,里面的 k 个数字排完序后是连续的。

现在小度熊增加题目难度,他不想知道是否有这样的 k 的区间,而是想知道有几个这样的 k 的区间。
Input
输入包含一组测试数据。

第一行包含两个整数n,m,n代表数组中有多少个数字,m 代表针对于此数组的询问次数,n不会超过104次方,m 不会超过1000。第二行包含n个正整数,第 I 个数字代表无序数组的第 I 位上的数字,数字大小不会超过231次方。接下来 m 行,每行一个正整数 k,含义详见题目描述,k 的大小不会超过1000。
Output
第一行输"Case #i:"。(由于只有一组样例,只输出”Case #1:”即可)

然后对于每个询问的 k,输出一行包含一个整数,代表数组中满足条件的 k 的大小的区间的数量。
Sample Input
6 2
3 2 1 4 3 5
3
4
Sample Output
Case #1:
2
2

题目分析:
1002我目前有三种nk的解法。
1.rmq预处理。
2.单调栈求极值。
3.暴力。

前两种比较好懂,第二种就是维护单调升的栈(求最小值,最大值类似),这里我就不废话了。
我比赛的时候就是写的第三种。具体操作是,枚举一个点作为起点,然后我们向右移动k步,分别表示k个不同长度的区间,然后我们可以很方便的求最大值和最小值,就是添加一个数就更新。
然后数这么大我们该怎么更新,这是一个问题。考虑到我们可以离线排序,这样找到了映射后,我们开一个长度为n的vis数组标记一个数出现过没有就好,向右扩展时出现重复就break,然后扩展好后就判断最大值减去最小值是不是等于区间长度-1。为了保证复杂度,vis数组要用时间戳。

PS:02还有一个坑点,貌似出题人没给这样的数据,就是2^31已经爆了int了,然而用int的都过了= =

my  code:

#include 
#include 
#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 10005 ;
const LL INF = 1e17 ;

LL a[MAXN] ;
LL val[MAXN] ;
int ans[MAXN] ;
int vis[MAXN] , Time ;
int n , m ;

int search ( LL x , int l = 1 , int r = m ) {
    while ( l < r ) {
        int m = ( l + r ) >> 1 ;
        if ( a[m] >= x ) r = m ;
        else l = m + 1 ;
    }
    return l ;
}

void solve () {
    int x ;
    Time = 0 ;
    clr ( ans , 0 ) ;
    clr ( vis , 0 ) ;
    printf ( "Case #1:\n" ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        scanf ( "%I64d" , &val[i] ) ;
        a[i] = val[i] ;
    }
    sort ( a + 1 , a + n + 1 ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        val[i] = search ( val[i] ) ;
    }
    for ( int i = 1 ; i <= n ; ++ i ) {
        LL maxv = -INF , minv = INF ;
        ++ Time ;
        for ( int j = i , k = 1 ; j <= n && k <= 1000 ; ++ j , ++ k ) {
            if ( vis[val[j]] == Time ) break ;
            vis[val[j]] = Time ;
            maxv = max ( maxv , a[val[j]] ) ;
            minv = min ( minv , a[val[j]] ) ;
            if ( maxv - minv + 1 == k ) ans[k] ++ ;
        }
    }
    while ( m -- ) {
        scanf ( "%d" , &x ) ;
        printf ( "%d\n" , ans[x] ) ;
    }
}

int main () {
    while ( ~scanf ( "%d%d",  &n , &m ) ) solve () ;
    return 0 ;
}

1003.序列变换  Accepts: 816   Submissions: 3578
 Time Limit: 4000/2000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
给定序列A={A1,A2,...,An}, 要求改变序列A中的某些元素,形成一个严格单调的序列B(严格单调的定义为:Bi1,1≤i1≤i≤N)。

请求出满足条件的最小代价。

注意,每个元素在变换前后都是整数。
Input
第一行为测试的组数T(1T10).

对于每一组: 第一行为序列A的长度N(1≤N≤10^5),第二行包含N个数,A1,A2,...,An. 序列A中的每个元素的值是正整数且不超过10^6。
Output
对于每一个测试样例,输出两行:

第一行输出:"Case #i:"。i代表第 i 组测试数据。

第二行输出一个正整数,代表满足条件的最小代价。
Sample Input
2
2
1 10
3
2 5 4
Sample Output
Case #1:
0
Case #2:
1

题目分析:
二分一个可以变化的值,然后每个数都尽可能的减小,判断合法性即可。最后取一个合法的最小值。

my  code:

#include 
#include 
#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define mid ( ( l + r ) >> 1 )
#define root 1 , 1 , cnt

const int MAXN = 100005 ;
const int INF = 0x3f3f3f3f ;

int a[MAXN] , n ;

int check ( int cost ) {
    int pre = -INF ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        if ( a[i] + cost <= pre ) return 0 ;
        pre = max ( min ( pre + 1 , a[i] + cost ) , a[i] - cost ) ;
    }
    return 1 ;
}

void solve () {
    scanf ( "%d" , &n ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        scanf ( "%d" , &a[i] ) ;
    }
    int l = 0 , r = 1000000 ;
    while ( l < r ) {
        int m = ( l + r ) >> 1 ;
        if ( check ( m ) ) r = m ;
        else l = m + 1 ;
    }
    printf ( "%d\n" , l ) ;
}

int main () {
    int T ;
    scanf ( "%d" , &T ) ;
    for ( int i = 1 ; i <= T ; ++ i ) {
        printf ( "Case #%d:\n" , i ) ;
        solve () ;
    }
    return 0 ;
}

1004.KPI  Accepts: 517   Submissions: 2185
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
你工作以后, KPI 就是你的全部了. 我开发了一个服务,取得了很大的知名度。数十亿的请求被推到一个大管道后同时服务从管头拉取请求。让我们来定义每个请求都有一个重要值。我的KPI是由当前管道内请求的重要值的中间值来计算。现在给你服务记录,有时我想知道当前管道内请求的重要值得中间值。
Input
有大约100组数据。

每组数据第一行有一个n(1≤n≤10000),代表服务记录数。

接下来有n行,每一行有3种形式 "in x": 代表重要值为x(0≤x≤10^9)的请求被推进管道。 "out": 代表服务拉取了管道头部的请求。 "query: 代表我想知道当前管道内请求重要值的中间值. 那就是说,如果当前管道内有m条请求, 我想知道,升序排序后第floor(m/2)+1th 条请求的重要值.

为了让题目简单,所有的x都不同,并且如果管道内没有值,就不会有"out""query"操作。
Output
对于每组数据,先输出一行

Case #i: 然后每一次"query",输出当前管道内重要值的中间值。
Sample Input
6
in 874
query
out
in 24622
in 12194
query
Sample Output
Case #1:
874
24622

题目分析:
这题就随便搞都行……treap,set都行……我个人用的线段树,首先离散,然后具体的中位数我就在线段树中二分了,线段树记录区间内存在的数的个数。

值得吐槽的是……这题一开始数据范围都不知道……直到我AC后才有提示。

my  code:

#include 
#include 
#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define mid ( ( l + r ) >> 1 )
#define root 1 , 1 , cnt

const int MAXN = 100005 ;

struct Node {
    int x , y ;
} ;

Node q[MAXN] ;
int val[MAXN << 2] ;
int a[MAXN] , cnt ;
int Q[MAXN] , head , tail ;
int n ;


int unique ( int n ) {
    int cnt = 1 ;
    sort ( a + 1 , a + n + 1 ) ;
    for ( int i = 2 ; i <= n ; ++ i ) {
        if ( a[i] != a[cnt] ) a[++ cnt] = a[i] ;
    }
    return cnt ;
}

int search ( LL x , int l = 1 , int r = cnt ) {
    while ( l < r ) {
        int m = ( l + r ) >> 1 ;
        if ( a[m] >= x ) r = m ;
        else l = m + 1 ;
    }
    return l ;
}

void build ( int o , int l , int r ) {
    val[o] = 0 ;
    if ( l == r ) return ;
    int m = mid ;
    build ( lson ) ;
    build ( rson ) ;
}

void update ( int x , int v , int o , int l , int r ) {
    if ( l == r ) {
        val[o] = v ;
        return ;
    }
    int m = mid ;
    if ( x <= m ) update ( x , v , lson ) ;
    else update ( x , v , rson ) ;
    val[o] = val[ls] + val[rs] ;
}

int query ( int x , int o , int l , int r ) {
    while ( l < r ) {
        int m = mid ;
        if ( val[ls] >= x ) {
            o = ls ;
            r = m ;
        } else {
            x -= val[ls] ;
            o = rs ;
            l = m + 1 ;
        }
    }
    return l ;
}

void solve () {
    int m = 0 ;
    char op[10] ;
    cnt = 0 ;
    head = tail = 0 ;
    for ( int i = 0 ; i < n ; ++ i ) {
        scanf ( "%s" , op ) ;
        if ( op[0] == 'i' ) {
            q[i].x = 0 ;
            scanf ( "%d" , &q[i].y ) ;
            a[++ cnt] = q[i].y ;
        } else if ( op[0] == 'q' ) q[i].x = 1 ;
        else q[i].x = 2 ;
    }
    cnt = unique ( cnt ) ;
    build ( root ) ;
    for ( int i = 0 ; i < n ; ++ i ) {
        if ( q[i].x == 0 ) {
            ++ m ;
            int t = search ( q[i].y ) ;
            Q[tail ++] = t ;
            update ( t , 1 , root ) ;
        } else if ( q[i].x == 1 ) {
            printf ( "%d\n" , a[query ( m / 2 + 1 , root )] ) ;
        } else {
            int t = Q[head ++] ;
            update ( t , 0 , root ) ;
            -- m ;
        }
    }
}

int main () {
    int cas = 0 ;
    while ( ~scanf ( "%d",  &n ) ) {
        printf ( "Case #%d:\n" , ++ cas ) ;
        solve () ;
    }
    return 0 ;
}

1005.三阶魔方  Accepts: 61   Submissions: 287
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
给定三阶魔方的基本操作:



现给出一个由基本操作组合成的操作序列,求问:对一个初状态(六个面都是拼好的)的魔方进行多少次连续的序列操作后,魔方会恢复到初状态。
Input
第一行为T,表示输入数据组数。

下面T行,每行给出一个合法的操作序列字符串,每行不超过100个字符。
Output
对第i组数据,输出

Case #i:

然后输出一个整数,表示答案。若魔方不会恢复则输出-1。
Sample Input
3
R2
R'R'U2
RU
Sample Output
Case #1:
2
Case #2:
6
Case #3:
105

题目分析:
这题……只要对每种操作打个置换的表,然后最后对操作中每个循环节取个lcm就好了= =由于我太懒就没写……


1006.矩形面积  Accepts: 717   Submissions: 1619
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
小度熊有一个桌面,小度熊剪了很多矩形放在桌面上,小度熊想知道能把这些矩形包围起来的面积最小的矩形的面积是多少。
Input
第一行一个正整数 T,代表测试数据组数(1≤T≤20),接下来 T 组测试数据。

每组测试数据占若干行,第一行一个正整数 N(1N<≤1000),代表矩形的数量。接下来 N 行,每行 8 个整数x1,y1,x2,y2,x3,y3,x4,y4,代表矩形的四个点坐标,坐标绝对值不会超过10000。
Output
对于每组测试数据,输出两行:

第一行输出"Case #i:",i 代表第 i 组测试数据。 第二行包含1 个数字,代表面积最小的矩形的面积,结果保留到整数位。
Sample Input
2
2
5 10 5 8 3 10 3 8
8 8 8 6 7 8 7 6
1
0 0 2 2 2 0 0 2
Sample Output
Case #1:
17
Case #2:
4

题目分析:
传说中的板子题……题目就是求一个可以斜着放的矩阵的最小面积,使得这个矩阵能完全覆盖题目给的所有矩阵。

这题十分坑爹,竟然是四舍五入,而不是向上取整……

my  code:

#include 
#include 
#include 
#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define mid ( ( l + r ) >> 1 )
#define root 1 , 1 , cnt

const int MAXN = 10005 ;
const double eps = 1e-8 ;

int sgn ( double x ) {
    if ( fabs ( x ) < eps ) return 0 ;
    if ( x < 0 ) return -1 ;
    else return 1 ;
}

struct Point {
    double x , y ;
    Point ( double x = 0 , double y = 0 ) : x ( x ) , y ( y ) {}
    Point operator - ( const Point &b ) {
        return Point ( x - b.x , y - b.y ) ;
    }
    double operator * ( const Point &b ) {
        return x * b.x + y * b.y ;
    }
    double operator ^ ( const Point &b ) {
        return x * b.y - y * b.x ;
    }
    bool operator == ( Point b ) const {
        return sgn ( x - b.x ) == 0 && sgn ( y - b.y ) == 0 ;
    }
    bool operator < ( Point b ) const {
        return sgn ( x - b.x ) == 0 ? sgn ( y - b.y ) < 0 : x < b.x ;
    }
    double len () {
        return hypot ( x , y ) ;
    }
    double len2 () {
        return x * x + y * y ;
    }
    double distance ( Point p ) {
        return hypot ( x - p.x , y - p.y ) ;
    }
    void input () {
        scanf ( "%lf%lf" , &x , &y ) ;
    }
} ;

struct polygon {
    int n ;
    Point p[MAXN] ;

    void input ( int _n ) {
        n = _n ;
        for ( int i = 0 ; i < n ; ++ i ) {
            p[i].input () ;
        }
    }

    struct cmp {
        Point p ;
        cmp ( const Point &p0 ) {
            p = p0 ;
        }
        bool operator () ( const Point &aa , const Point &bb ) {
            Point a = aa , b = bb ;
            int d = sgn ( ( a - p ) ^ ( b - p ) ) ;
            if ( d == 0 ) {
                return sgn ( a.distance ( p ) - b.distance ( p ) ) < 0 ;
            }
            return d > 0 ;
        }
    } ;

    void norm () {
        Point mi = p[0] ;
        for ( int i = 1 ; i < n ; ++ i ) {
            mi = min ( mi , p[i] ) ;
        }
        sort ( p , p + n , cmp ( mi ) ) ;
    }

    void getconvex ( polygon &convex ) {
        sort ( p , p + n ) ;
        convex.n = n ;
        for ( int i = 0 ; i < min ( n , 2 ) ; ++ i ) {
            convex.p[i] = p[i] ;
        }
        if ( convex.n == 2 && ( convex.p[0] == convex.p[1] ) ) convex.n -- ;
        if ( n <= 2 ) return ;
        int &top = convex.n ;
        top = 1 ;
        for ( int i = 2 ; i < n ; ++ i ) {
            while ( top && sgn ( ( convex.p[top] - p[i] ) ^ ( convex.p[top - 1] - p[i] ) ) <= 0 ) -- top ;
            convex.p[++ top] = p[i] ;
        }
        int tmp = top ;
        convex.p[++ top] = p[n - 2] ;
        for ( int i = n - 3 ; i >= 0 ; -- i ) {
            while ( top != tmp && sgn ( ( convex.p[top] - p[i] ) ^ ( convex.p[top - 1] - p[i] ) ) <= 0 ) -- top ;
            convex.p[++ top] = p[i] ;
        }
        if ( convex.n == 2 && ( convex.p[0] == convex.p[1] ) ) convex.n -- ;
        convex.norm () ;
    }
} ;

double cross ( Point A , Point B , Point C ) {
    return ( B - A ) ^ ( C - A ) ;
}

double dot ( Point A , Point B , Point C ) {
    return ( B - A ) * ( C - A ) ;
}

double minRectangleCover ( polygon A ) {
    if ( A.n < 3 ) return 0.0 ;
    A.p[A.n] = A.p[0] ;
    double ans = -1 ;
    int r = 1 , p = 1 , q ;
    for ( int i = 0 ; i < A.n ; ++ i ) {
        while ( sgn ( cross ( A.p[i] , A.p[i + 1] , A.p[r + 1] ) - cross ( A.p[i] , A.p[i + 1] , A.p[r] ) ) >= 0 ) r = ( r + 1 ) % A.n ;
        while ( sgn ( dot ( A.p[i] , A.p[i + 1] , A.p[p + 1] ) - dot ( A.p[i] , A.p[i + 1] , A.p[p] ) ) >= 0 ) p = ( p + 1 ) % A.n ;
        if ( i == 0 ) q = p ;
        while ( sgn ( dot ( A.p[i] , A.p[i + 1] , A.p[q + 1] ) - dot ( A.p[i] , A.p[i + 1] , A.p[q] ) ) <= 0 ) q = ( q + 1 ) % A.n ;
        double d = ( A.p[i] - A.p[i + 1] ).len2 () ;
        double tmp = cross ( A.p[i] , A.p[i + 1] , A.p[r] ) * ( dot ( A.p[i] , A.p[i + 1] , A.p[p] ) - dot ( A.p[i] , A.p[i + 1] , A.p[q] ) ) / d ;
        if ( ans < 0 || ans > tmp ) ans = tmp ;
    }
    return ans ;
}

int n ;
polygon t , tmp ;

void solve () {
    scanf ( "%d" , &n ) ;
    t.input ( n * 4 ) ;
    t.getconvex ( tmp ) ;
    double ans = minRectangleCover ( tmp ) ;
    int ans1 = ans ;
    if ( sgn ( ans - ans1 ) > 0 ) ++ ans1 ;
    printf ( "%d\n" , ( int ) ( ans + 0.5 ) ) ;
}

int main () {
    int T ;
    scanf ( "%d" , &T ) ;
    for ( int i = 1 ; i <= T ; ++ i ) {
        printf ( "Case #%d:\n" , i ) ;
        solve() ;
    }
    return 0 ;
}

你可能感兴趣的:(题解)