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

1001.追星族  Accepts: 21   Submissions: 122
 Time Limit: 6000/3000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
度度熊最近迷上了S明星,恰逢她正在巡回演出,得知所有的演出安排后,它希望一场不落的看完所有的演出。

每场演出的地点 (Xi,Yi) 以及时间Ti,这些信息事先都已经公布。但唯一的问题是,单位时间内度度熊的移动速度只有可怜的1。它当然希望离自己的偶像越近越好,所以它希望在所有的演出时刻,它距离演出地点距离的最大值可以最小。度度熊在时间点0时刻出发,并且它可以选择任何一个位置作为起点。

生活在一个格子化的二次元中,度度熊是这样计算距离的:|x1−x2|+|y1−y2|
Input
第一行一个整数T,表示T组数据。

每组样例的第一行有一个整数N(1N50000),表示演出的场数。

接下来的N行,每行包括三个整数Xi,Yi,Ti(−109≤Xi,Yi≤109,0≤Ti≤109),描述一场演出的地点与时间。数据中存在演出时间相同或者演出地点相同的数据。
Output
对于每组数据,输出两行:

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

第二行输出最小的最大距离。为了尽量精确,用分数A/B的形式表示,其中A和B不可继续化简。
Sample Input
2
2
1 1 2
1 5 1
2
1 1 2
1 5 10
Sample Output
Case #1:
3/2
Case #2:
0/1

题目分析:
注意到,一个点的曼哈顿距离范围实际上是一个旋转了45°的正方形,并且同样方向的矩形的交依旧是一个矩形,这样我们这道题就可以做了。

先将所有点按照时间排序。
然后,我们二分答案,也就是半径d,然后我们将每个点扩展d的范围变成一个正方形,然后我们从最后一个正方形往前面的矩形求交。
先将最后一个矩形再扩展 tntn1 的范围,这就是最后我们能处于的合法位置的范围,再和第 n1 个正方形求交,得到的就是上一步的合法位置范围,这是一个矩形。不断重复上述过程直到到达第一个矩形,中间如果出现不合法情况,就说明这个距离不够,调整下界。否则调整上界。

关键部分其实是如何方便的表达一个矩形。考虑到直线y=x,y=-x,我们可以将一个边长为2d的正方形表示为:x+y-d,x+y+d,y-x-d,y-x+d。这四条直线代表的半平面求出的交就是我们的正方形了,然后矩形的表示也是类似的。对于矩形的求交,实际上就是对四条直线表示的值更新。

最后要注意的是,答案只可能是x/1或者x/2的形式,所以一开始所有数*2来使得我们的过程不涉及小数。

my  code:

#include 
#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 , n

const int MAXN = 100005 ;

struct Rectangle {
    LL x1 , x2 , y1 , y2 ;
    LL t ;
    void print () {
        printf ( "%I64d %I64d %I64d %I64d\n" , x1 , x2 , y1 , y2 ) ;
    }
    void input () {
        LL x , y ;
        scanf ( "%I64d%I64d%I64d" , &x , &y , &t ) ;
        x *= 2 ;
        y *= 2 ;
        t *= 2 ;
        x1 = x + y ;
        x2 = x + y ;
        y1 = y - x ;
        y2 = y - x ;
    }
    bool operator < ( const Rectangle &a ) const {
        return t < a.t ;
    }
    Rectangle get ( LL d ) {
        Rectangle tmp ;
        tmp.x1 = x1 - d ;
        tmp.x2 = x2 + d ;
        tmp.y1 = y1 - d ;
        tmp.y2 = y2 + d ;
        return tmp ;
    }
} ;

Rectangle a[MAXN] ;
int n ;

int check ( LL d ) {
    Rectangle now = a[n] ;
    now = now.get ( d ) ;
    for ( int i = n - 1 ; i >= 1 ; -- i ) {
        int t = a[i + 1].t - a[i].t ;
        now = now.get ( t ) ;
        Rectangle tmp = a[i].get ( d ) ;
        if ( now.x2 < tmp.x1 || tmp.x2 < now.x1 ) return 0 ;
        if ( now.y2 < tmp.y1 || tmp.y2 < now.y1 ) return 0 ;
        now.x1 = max ( now.x1 , tmp.x1 ) ;
        now.x2 = min ( now.x2 , tmp.x2 ) ;
        now.y1 = max ( now.y1 , tmp.y1 ) ;
        now.y2 = min ( now.y2 , tmp.y2 ) ;
    }
    return 1 ;
}

void solve () {
    scanf ( "%d" , &n ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        a[i].input () ;
    }
    sort ( a + 1 , a + n + 1 ) ;
    LL l = 0 , r = 1e10 ;
    while ( l < r ) {
        LL m = ( l + r ) >> 1 ;
        if ( check ( m ) ) r = m ;
        else l = m + 1 ;
    }
    if ( l % 2 ) printf ( "%I64d/2\n" , l ) ;
    else printf ( "%I64d/1\n" , l / 2 ) ;
}

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

1002.连接的管道  Accepts: 739   Submissions: 3405
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
老 Jack 有一片农田,以往几年都是靠天吃饭的。但是今年老天格外的不开眼,大旱。所以老 Jack 决定用管道将他的所有相邻的农田全部都串联起来,这样他就可以从远处引水过来进行灌溉了。当老 Jack 买完所有铺设在每块农田内部的管道的时候,老 Jack 遇到了新的难题,因为每一块农田的地势高度都不同,所以要想将两块农田的管道链接,老 Jack 就需要额外再购进跟这两块农田高度差相等长度的管道。

现在给出老 Jack农田的数据,你需要告诉老 Jack 在保证所有农田全部可连通灌溉的情况下,最少还需要再购进多长的管道。另外,每块农田都是方形等大的,一块农田只能跟它上下左右四块相邻的农田相连通。
Input
第一行输入一个数字T(T≤10),代表输入的样例组数

输入包含若干组测试数据,处理到文件结束。每组测试数据占若干行,第一行两个正整数 N,M(1N,M≤1000),代表老 Jack 有N行*M列个农田。接下来 N 行,每行 M 个数字,代表每块农田的高度,农田的高度不会超过100。数字之间用空格分隔。
Output
对于每组测试数据输出两行:

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

第二行输出 1 个正整数,代表老 Jack 额外最少购进管道的长度。
Sample Input
2
4  3
9 12 4
7 8 56
32 32 43
21 12 12
2  3
34 56 56
12 23 4
Sample Output
Case #1:
82
Case #2:
74

题目分析:
裸的最小生成树……我比较愚蠢,把所有的边都保存了下来,然后排序,然后机智的学弟用了计数排序。

my  code:

#include 
#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 , n

const int MAXN = 1005 ;
const int MAXE = 2000005 ;

struct Edge {
    int u , v , c ;
    Edge () {}
    Edge ( int u , int v , int c ) : u ( u ) , v ( v ) , c ( c ) {} ;
    bool operator < ( const Edge &e ) const {
        return c < e.c ;
    }
} ;

Edge E[MAXE] ;
int cntE ;
int G[MAXN][MAXN] ;
int idx[MAXN][MAXN] ;
int p[MAXE] ;
int n , m ;

int find ( int x ) {
    int o = x ;
    while ( p[o] != o ) o = p[o] ;
    int ans = o , tmp ;
    while ( p[x] != ans ) {
        tmp = p[x] ;
        p[x] = ans ;
        x = tmp ;
    }
    return ans ;
}

void solve () {
    int cnt = 0 ;
    cntE = 0 ;
    scanf ( "%d%d" , &n , &m ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        for ( int j = 1 ; j <= m ; ++ j ) {
            idx[i][j] = ++ cnt ;
            p[cnt] = cnt ;
            scanf ( "%d" , &G[i][j] ) ;
            if ( i > 1 ) E[cntE ++] = Edge ( idx[i - 1][j] , idx[i][j] , abs ( G[i][j] - G[i - 1][j] ) ) ;
            if ( j > 1 ) E[cntE ++] = Edge ( idx[i][j - 1] , idx[i][j] , abs ( G[i][j] - G[i][j - 1] ) ) ;
        }
    }
    sort ( E , E + cntE ) ;
    int ans = 0 ;
    for ( int i = 0 ; i < cntE ; ++ i ) {
        int u = find ( E[i].u ) ;
        int v = find ( E[i].v ) ;
        if ( u != v ) {
            ans += E[i].c ;
            p[u] = v ;
        }
    }
    printf ( "%d\n" , ans ) ;
}

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

1003.棋盘占领  Accepts: 937   Submissions: 2201
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
百小度最近迷恋上了一款游戏,游戏里有一个n*m的棋盘,每个方格代表一个城池。
一开始的时候我们有g支军队,驻扎并占领了其中某些城池。然后我们可以在这些被占领城池的基础上,吞并占领周围的城池。


而其吞并占领的规则是这样的——一旦一个城池A相邻的上下左右四个城池中至少存在两个被占领,且这两个被占领的城池有公共点,那么城池A也将被占领。
比如我们用1表示初始的占领状态,0表示初始的未占领状态。
那么——


1001

会最终会变成

1111

而101则保持为101不变

现在告诉你一张地图一开始所有被占领城池的信息,问你最后多少个城池会被我们占领。
Input
第一行为T,表示输入数据组数。

下面T组数据,对于每组数据,
第一行是两个数n,m(1≤n,m≤500),表示国土的大小为n*m。


第二行是一个整数g(1≤g≤1000),表示我们一开始占领的城池数。
然后跟随g行,第i行一对整数x,y(1≤x≤n,1≤y≤m),表示占领的第i个城池的坐标。 
Output
对第i组数据,输出

Case #i:

然后输出一行,仅包含一个整数,表示最终有多少个城池被占领。
Sample Input
4
2 2
2
1 1
2 2
3 3
3
1 1
2 3
3 2
2 4
5
1 1
1 1
1 2
1 3
1 4
2 4
2
1 1
2 4
Sample Output
Case #1:
4
Case #2:
9
Case #3:
4
Case #4:
2

题目分析:bfs就好……

my  code:

#include 
#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 , n

const int MAXN = 505 ;

struct Node {
    int x , y ;
    Node () {}
    Node ( int x , int y ) : x ( x ) , y ( y ) {}
} ;

Node Q[MAXN * MAXN] ;
int G[MAXN][MAXN] ;
int head , tail ;
int n , m , k ;
int path[4][2] = { { 1 , 1 } , { 1 , -1 } , { -1 , 1 } , { -1 , -1 } } ;

int check ( int x , int y ) {
    return x >= 1 && x <= n && y >= 1 && y <= m ;
}

void bfs () {
    while ( head != tail ) {
        Node u = Q[head ++] ;
        for ( int i = 0 ; i < 4 ; ++ i ) {
            int x = u.x + path[i][0] ;
            int y = u.y + path[i][1] ;
            if ( check ( x , y ) == 0 || G[x][y] == 0 ) continue ;
            if ( G[x][u.y] == 0 ) {
                G[x][u.y] = 1 ;
                Q[tail ++] = Node ( x , u.y ) ;
            }
            if ( G[u.x][y] == 0 ) {
                G[u.x][y] = 1 ;
                Q[tail ++] = Node ( u.x , y ) ;
            }
        }
    }
}

void solve () {
    int x , y ;
    head = tail = 0 ;
    scanf ( "%d%d%d" , &n , &m , &k ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        for ( int j = 1 ; j <= m ; ++ j ) {
            G[i][j] = 0 ;
        }
    }
    for ( int i = 1 ; i <= k ; ++ i ) {
        scanf ( "%d%d" , &x , &y ) ;
        G[x][y] = 1 ;
        Q[tail ++] = Node ( x , y ) ;
    }
    bfs () ;
    int ans = 0 ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        for ( int j = 1 ; j <= m ; ++ j ) {
            ans += G[i][j] ;
        }
    }
    printf ( "%d\n" , ans ) ;
}

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

1004.魔法因子  Accepts: 215   Submissions: 1208
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
有人说:人类是自己一步步进化的,而数学是上帝亲手创造的。度度熊最近也正沉醉于数学之美中,它发现了一种神奇的数字,取名曰:魔法因子。

将因子记为X,如果有一些整数与这些因子做乘法后,结果仍然是整数,同时,结果数字的首位和末位会换交换位置,而其他位置上的数字恰好不变!这时X被认为是一个魔法因子。需要注意的是,用来相乘的这些整数不会含有前导0,但是如果交换的结果有前导0,又恰好是乘法的结果,这时仍然认为X是这个整数的魔法因子。度度熊认为1不是个魔法因子,因为所有的数都可以符合这个条件,这一点都不好玩。

比如,X = 3.1312,有1875 * 3.1312 = 5871。

度度熊现在希望知道对于一个因子X,究竟有多少个整数可以满足这个条件,使其成为魔法因子。由于它还不会长度超过10位数的乘法,只需要求出长度不超过10的整数即可。
Input
第一行一个整数T,表示包含T组数据。 每组数据包含一个实数X(010,X≠1),同时为了避免精度问题,小数点后的数字不会超过6位。
Output
每组数据,对于每组数据,先输出一行

Case #i:

然后输出符合条件的整数的个数,如果个数不为0,在第二行输出所有符合条件的整数,按数字大小升序排列,用空格隔开,如果个数为0,只输出一行。
Sample Input
3
3.1312
3.1415
0.3
Sample Output
Case #1:
3
1875 1876875 1876876875 
Case #2:
0 
Case #3:
2
1428570 2857140

题目分析:
首先要将输入的小数转化成分数的形式(直接用double输入转化成整数不行,听说要加一个0.0000005,然后我就是因为这个跪了好几发,最后换成字符串输入转化就过了)。假设分母为y1,分子为x1(假设是最简形式,除以gcd过)。

我们只要,枚举一个首位a,一个个位c,以及数的位数x,然后假设中间未知的部分为b,则我们可以列出一个方程:

(a10x+b10+c)x1=(c10p+b10+a)y1

除了b,其他都是已知量,因此我们可以解出b来,只要b满足 b0 10b<10p 即可。

my  code:

#include 
#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 , n

const int L = 1000000 ;

LL p[15] ;
LL S[L] , top ;

LL gcd ( LL a , LL b ) {
    return b ? gcd ( b , a % b ) : a ;
}

LL f ( LL pp , LL a , LL c , LL x1 , LL y1 ) {
    LL x = a * pp * x1 - c * pp * y1 + c * x1 - a * y1 ;
    LL y = ( y1 - x1 ) * 10 ;
    if ( x == 0 ) return 0 ;
    else if ( x > 0 && y < 0 || x < 0 && y > 0 ) return -1 ;
    else {
        if ( x % y ) return - 1 ;
        else return x / y ;
    }
}

char s[100] ;

void solve () {
    int x = 0 , y = 1 , loc = 0 ;
    top = 0 ;
    scanf ( "%s" , s ) ;
    int n = strlen ( s ) ;
    for ( int i = 0 ; i < n ; ++ i ) {
        if ( s[i] >= '0' ) {
            x = x * 10 + s[i] - '0' ;
            if ( loc ) y *= 10 ;
        } else loc = 1 ;
    }
    int g = gcd ( x , y ) ;
    int x1 = x / g ;
    int y1 = y / g ;
    for ( int i = 1 ; i <= 9 ; ++ i ) {
        for ( int a = 1 ; a <= 9 ; ++ a ) {
            for ( int c = 0 ; c <= 9 ; ++ c ) {
                LL b = f ( p[i] , a , c , x1 , y1 ) ;
                if ( b < 0 || b * 10 >= p[i] ) continue ;
                LL t = a * p[i] + b * 10 + c ;
                S[top ++] = t ;
            }
        }
    }
    printf ( "%d\n" , top ) ;
    sort ( S , S + top ) ;
    for ( int i = 0 ; i < top ; ++ i ) {
        printf ( "%I64d%c" , S[i] , i < top - 1 ? ' ' : '\n' ) ;
    }
}


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

1005.序列变换  Accepts: 695   Submissions: 3322
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
我们有一个数列A1,A2...An,你现在要求修改数量最少的元素,使得这个数列严格递增。其中无论是修改前还是修改后,每个元素都必须是整数。 请输出最少需要修改多少个元素。
Input
第一行输入一个T(1T10),表示有多少组数据

每一组数据:

第一行输入一个N(1≤N≤105),表示数列的长度

第二行输入N个数A1,A2,...,An。

每一个数列中的元素都是正整数而且不超过106。
Output
对于每组数据,先输出一行

Case #i:

然后输出最少需要修改多少个元素。
Sample Input
2
2
1 10
3
2 5 4
Sample Output
Case #1:
0
Case #2:
1

题目分析:对所有ai -= i,然后求最长不下降序列就行了。

my  code:

#include 
#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 , n

const int MAXN = 100005 ;

int a[MAXN] ;
int S[MAXN] , top ;
int n ;

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

void solve () {
    scanf ( "%d" , &n ) ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        scanf ( "%d" , &a[i] ) ;
        a[i] -= i ;
    }
    top = 0 ;
    for ( int i = 1 ; i <= n ; ++ i ) {
        if ( !top || S[top] <= a[i] ) S[++ top] = a[i] ;
        else {
            int x = search ( a[i] ) ;
            S[x] = a[i] ;
        }
    }
    printf ( "%d\n" , n - top ) ;
}

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

1006.翻转游戏  Accepts: 15   Submissions: 143
 Time Limit: 2000/1000 MS (Java/Others)   Memory Limit: 32768/32768 K (Java/Others)
Problem Description
度度熊最近迷上一个小游戏:Flip it。游戏的规则很简单,在一个N*M的格子上,有一些格子是黑色,有一些是白色。每选择一个格子按一次,格子以及周围边相邻的格子都会翻转颜色(边相邻指至少与该格子有一条公共边的格子),黑变白,白变黑。

度度熊希望把所有格子都变成白色的。不幸的是,有一些格子坏掉了,无法被按下。这时,它可以完成游戏吗?
Input
第一行一个整数T,表示T组数据。

每组数据开始于三个整数N,M,K(1N,M,K256),分别表示格子的高度和宽度、坏掉格子的个数。接下来的N行,每行一个长度M的字符串,表示格子状态为’B’或‘W’。最后K行,每行两个整数Xi,Yi(1≤Xi≤N,1≤Yi≤M),表示坏掉的格子。
Output
对每组样例,对于每组数据,先输出一行Case #i: (1≤i≤T)
如果可以成功,输出YES,否则输出NO。
Sample Input
2
3 3 0
WBW
BBB
WBW
3 3 2
WBW
BBB
WBW
2 2
3 2
Sample Output
Case #1:
YES
Case #2:
NO

题目分析:1006不会

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