Regionals 2011, Asia - Dhaka 部分解题报告

来源:互联网 发布:唯美的爱情动作片 知乎 编辑:程序博客网 时间:2024/05/18 00:07

下午的一场选拔赛,题源是 2011 Dhaka的区域赛 。因为期末复习,好久时间没做题了,下午各种手残+脑残,浪费了不少时间


A. Binary Matrix

题意 : 给你一个n*m的01矩阵 , 要求通过交换矩阵中相邻位置的元素( 第一列和最后一列也算相邻 , 第一行和最后一行也算相邻 )来使每行中1的个数相同,每列中1的个数相同,当然不一定可以实现。所以当行列都能实现时,输出both,以及需要的最小操作数。否则,当行可以实现时,输出row,以及需要的最小操作数。否则,当列可以实现时,输出column( ... 比赛的时候我输出了col,简直逗 ) 。再不行就输出 impossible 。


思路 : 首先我们先判断可行性,直接取模就可以了。然后就是求最小操作数的问题了,首先我们可以把行列分开来考虑,分别求使每行满足要求和每列满足要求的最小值 ,both的情况就是这两个的和( 一定会有可行的顺序,使的both取到这两种情况最小值的和,不过没有证明 ) 。

那么如何求行和列的情况。 这里拿求行的情况举例,我们先求出初始时每行中1的个数,然后要通过最小操作使1的个数变成一样,问题就等同于 UVA11300 分金币 , 解析在LRJ的训练指南上面有,不赘述了。


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int n , m ;int ABS( int x ) {return x < 0 ? -x : x ;}char mp[1005][1005] ;int sum ;int c[1005] ;int cc[1005] , rr[1005] ;int main(){int cas ;int i , j ;scanf( "%d" , &cas ) ;int casn = 1 ;while( cas -- ) {scanf( "%d%d" , &n , &m ) ;sum = 0 ;memset( cc , 0 , sizeof(cc) ) ;memset( rr , 0 , sizeof(rr) ) ;for( i = 0 ; i < n ; i ++ ) {scanf( "%s" , mp[i] ) ;for( j = 0 ; j < m ; j ++ ) {sum += mp[i][j] - '0' ;cc[j+1] += mp[i][j] - '0' ;rr[i+1] += mp[i][j] - '0' ;}}bool both , row , col ;row = ( sum % n ) == 0 ;col = ( sum % m ) == 0 ;both = ( col && row ) ;int ans_col = 0 ;int M = sum / m ;c[0] = 0 ;for( i = 1 ; i < m ; i ++ ) c[i] = c[i-1] + cc[i] - M ;sort( c , c + m ) ;int x1 = c[m/2] ;ans_col = 0 ;for( i = 0 ; i < m ; i ++ ) ans_col += ABS( x1 - c[i] ) ;int ans_row = 0 ;M = sum / n ;c[0] = 0 ;for( i = 1 ;i < n ; i ++ ) c[i] = c[i-1] + rr[i] - M ;sort( c , c + n ) ;int x2 = c[n/2] ;for( i = 0 ; i < n ; i ++ ) ans_row += ABS( x2 - c[i] ) ;printf( "Case %d: " , casn ++ ) ;if( both ) {printf( "both %d\n" , ans_row + ans_col ) ;}else if( row ) {printf( "row %d\n" , ans_row ) ;}else if( col ){printf( "column %d\n" , ans_col ) ;}else{puts( "impossible" ) ;}}return 0 ;}


B. Candles

题意 : 有10个形状的蜡烛,表示0~9,以及一个'+'号。给你n个数,你需要通过一些蜡烛和加号(可用可不用,最多只有一个)组成这n个数。最后要把选择的蜡烛从大到小输出来,当然方案数可能不止一个,那么就要选择蜡烛数量最小的方案,还相同就要选择字典序最小的方案。


思路:因为数据组数很多,所以要预处理。预处理数组 dp[state][sum]表示选择蜡烛的状态为state的情况下,能否凑出sum,并且如果 dp[state][sum]为true , 且 state 为 state1 的子集,那么dp[state1][sum]必然为true 。 预处理的过程直接dfs一遍即可。


#include <stdio.h>#include <string.h>#include <stdlib.h>typedef  long long LL;#define MAXN 1<<11#define MAZE 101int DP[MAXN][MAZE];bool vis[MAXN][MAZE][MAZE];int num[]={0,1,2,3,4,5,6,7,8,9};void init(){memset(DP,false,sizeof(DP));}// state 表示状态 , sum 表示搜到现在的和 , pre 为式子上个+号到现在的数字 , cnt 表示前面式子加号的个数 void dfs(int state,int sum,int pre,int cnt ){//if(DP[state][sum])return;if( sum > 100 ) return ;if( pre > 100 ) return ;if( vis[state][sum][pre] ) return ;vis[state][sum][pre] = true ;int i=0,tmp0,tmp1,tmp2,tmp3;DP[state][sum] = true;    for(i=0;i<10;++i){if( pre == 0 && i == 0 ) continue ;tmp0 = state&(1<<i);if(!tmp0){tmp1 = state|(1<<i);tmp2 = sum + num[i];tmp3 = sum - pre +pre*10 +num[i];if( cnt < 2 )dfs(tmp1,tmp2,num[i],cnt+1);dfs(tmp1,tmp3,pre*10 +num[i],cnt);}}}LL Getans(int state){int i;LL ans = 0;    for(i=9;i>=0;--i){if((1<<i)&state){           ans = ans*10+ i;}}return ans;}int main(){init();    memset( vis , false , sizeof(vis) ) ;dfs(0,0,0,0);int n,age[20],i,j;LL ans;for( i = 0 ; i < 1024 ; i ++ ) {for( ans = 0 ; ans <= 100 ; ans ++ ) {if( DP[i][ans] ) {for( j = 0 ; j < 10 ; j ++ ) {if( ( i & ( 1 << j ) ) == 0 )DP[i|(1<<j)][ans] = true ;}}}}    int cas  =0;    while(scanf("%d",&n)!=EOF){if(!n)break;for(i=0;i<n;++i){scanf("%d",&age[i]);}bool isfirst = false;for( i=0;i< 1024;++i){bool flag = false;for(j=0;j<n;++j){if(!DP[i][age[j]]){flag = true;break;}}if(!flag){LL tmp4 = Getans(i);if(!isfirst){  ans = tmp4;  isfirst = true;}else if(ans>tmp4){                   ans = tmp4;}}}        printf("Case %d: %lld\n",++cas,ans);}return 0;}

C. Cards

题意 : 一副扑克牌,4种花色,每种花色13张,并且有2张王。现在随机抽出扑克牌放在桌面上,问期望抽多少张,桌面上至少有C张clubs, D张diamonds, H张hearts 和 S张spades。抽到王,可以代替任意一个花色,但是要求这个选择使期望张数最小。


思路 : 很明显的概率DP , 方程 dp[c][d][h][s][e] 表示还剩下 c张clubs,d张diamonds,h张hearts,s张spades,并且使用两张joker的情况为e,还期望需要在抽多少张满足条件。e是用两位5进制进行状态压缩,第一位表示第一次张joker的状态,第二位表示第二张joker的状态。每位如果是0表示这张还没有发出去,否则i就表示当做第i个花色。

显然当一个状态已经满足条件的话,那么dp值为0

否则 dp[c][d][h][s][e] = ( c / ( sum ) * dp[c-1][d][h][s][e] ) + ( d / sum ) * dp[c][d-1][h][s][e] + ( h / sum ) * dp[c][d][h-1][s][e] + ( h / sum ) * dp[c][d][h][s-1][e] + ( cnte / sum ) * min( ... )+1

其中 sum = c + d + h + s + cnte ; cnte 表示剩下joker的个数

( cnte / sum ) * min( ... ) 这个就是取 joker 分配给不同花色的不同情况的最小值,情况比较多,这里不写了,看代码就好了


#include <stdio.h>#include <string.h>#include <algorithm>#include <stdlib.h>#include <math.h>using namespace std;double dp[15][15][15][15][26] ;int C , D , H , S ;double min( double a , double b ) {return a < b ? a : b ;}bool check( int a , int b , int c , int d , int e ) {int cc = 13 - a , dd = 13 - b , hh = 13 - c , ss = 13 - d , jj = 2 - e ;if( cc < C )jj -= C - cc ;if( dd < D ) jj -= D - dd ;if( hh < H )jj -= H - hh ;if( ss < S ) jj -= S - ss ;return jj >= 0 ;}bool check2( int a, int b , int c , int d , int e ) {int cc = 13 - a , dd = 13 - b , hh = 13 - c , ss = 13 - d ;switch( e % 5 ) {case 1 : cc ++ ; break ;case 2 : dd ++ ; break ;case 3 : hh ++ ; break ;case 4 : ss ++ ; break ;}switch( e / 5 ) {case 1 : cc ++ ; break ;case 2 : dd ++ ; break ;case 3 : hh ++ ; break ;case 4 : ss ++ ; break ;}if( cc < C ) return false ;if( dd < D ) return false ;if( hh < H ) return false ;if( ss < S ) return false ;return true ;} double dfs( int a , int b , int c , int d , int e ) {if( dp[a][b][c][d][e] >= -0.5 ) return dp[a][b][c][d][e] ;if( check2( a , b , c , d , e ) )return dp[a][b][c][d][e] = 0.0 ;double ans = 0 ;int cnte ;if( e % 5 == 0 ) cnte = 2 ;else if( e / 5 == 0 ) cnte = 1 ;else cnte = 0 ;int sum = a + b + c + d + cnte ;if( a > 0 ) {ans += ( 1.0 * a / sum ) * dfs( a - 1 , b , c , d , e ) ;}if( b > 0 ) {ans += ( 1.0 * b / sum ) * dfs( a , b - 1 , c , d , e ) ; }if( c > 0 ) {ans += ( 1.0 * c / sum ) * dfs( a , b , c - 1 , d , e ) ;}if( d > 0 ) {ans += ( 1.0 * d / sum ) * dfs( a , b , c , d - 1 , e ) ;}if( e % 5 == 0 ) {//ans += ( 1.0 * e / sum ) * dfs( a , b , c , d , e - 1 ) ;double Min = ( 1.0 * cnte / sum ) * dfs( a , b , c , d , 1 ) ;Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , 2 ) );Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , 3 ) );Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , 4 ) );ans += Min ;}else if( e / 5 == 0 ){double Min = ( 1.0 * cnte / sum ) * dfs( a , b , c , d , e + 5 ) ;Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , e + 10 ) );Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , e + 15 ) );Min = min( Min , ( 1.0 * cnte / sum ) * dfs( a , b , c , d , e + 20 ) );ans += Min ;}return dp[a][b][c][d][e] = ans + 1.0 ;}int main(){int cas ;scanf( "%d" , &cas ) ;int casn = 1 ;while( cas -- ) {scanf("%d%d%d%d", &C , &D , &H , &S ) ;//memset( dp , -1 , sizeof(dp) ) ;int i , j , k , l , e ;for( i = 0 ; i <= 13 ; i ++ ) {for( j = 0 ; j <= 13 ; j ++ ) {for( k = 0 ; k <= 13 ; k ++ ) {for( l = 0 ; l <= 13 ; l ++ ) {for( e = 0 ; e <= 25 ; e ++ ) {dp[i][j][k][l][e] = -1.0 ;}}}}}double ans ;if( check( 0 , 0 , 0 , 0 , 0 ) == false ) {ans = -1.0 ;}else{ans = dfs( 13 , 13 , 13 , 13 , 0 ) ;}printf( "Case %d: %.3lf\n" , casn ++ , ans ) ;}return 0 ;}

F. Packing for Holiday

题意 : 就是问一个大小为H*W*L能不能任意角度放进一个大小为20*20*20的箱子里

思路: 大水题,判断H,W,L是不是都小于等于20就行了

#include <stdio.h>int main(){int cas , casn = 1 ;scanf( "%d" , &cas ) ;while( cas -- ) {int a , b , c ;scanf( "%d%d%d" , &a , &b , &c ) ;if( a <= 20 && b<= 20 && c <= 20 ) {printf( "Case %d: good\n" , casn ++ ) ;}else{printf( "Case %d: bad\n" , casn ++ ) ;}}return 0 ;}

G. Pair of Touching Circles

题意 : 有两个圆,要求圆的圆心是整点,而且半径也是整点,这两个圆要相切,这两个圆要放在一个 n*m 的矩形中 , 问有多少种方案?

思路 : 对于一种情况,我们先算出的这种情况的外接矩形的长和宽,假设长宽分别为 Δx 和 Δy , 那么这种情况的方案数有 ( n - Δx + 1 ) * ( m - Δy +1 )

然后就是要枚举出各种情况,做法是先枚举两个圆的圆心的连线的Δx 和 Δy ,那么可以计算出 R1 + R2 , 如果R1+R2是整数,那么这种情况就合法,计算即可。

当然在Δx == 0 或者 Δy == 0 的时之后一种情况 , 否则就有两种情况。

这题是最后做的,交上去之后就T了,然后就没有时间了。

赛后做了预处理 , 预处理出 cnt[i][j] 数组表示当Δx=i , Δy = j 有多少种情况 , 然后计算即可。

要是之前没有那么逗...这题应该是可以现场过的...太忧伤


#include <stdio.h>#include <string.h>#include <algorithm>#include <math.h>using namespace std;int cnt[1005][1005] ;void init(){for( int i = 0 ; i <= 1000 ; i ++ ) {for( int j = 0 ; j <= 1000 ; j ++ ) {if( i == 0 && j == 0 ) {continue ;}double k = sqrt( 1.0 * ( i * i + j * j ) ) ;if( k != (int) k ) continue ;int t = (int) k ;if( i + t > 1000 || j + t > 1000 ) continue ;for( int l = 1 ; l < t ; l ++ ) {int Minx = min( - l , i - ( t - l ) ) ;int Maxx = max( l , i + ( t - l ) ) ;int Miny = min( - l , j - ( t - l ) ) ;int Maxy = max( l , j + ( t - l ) ) ;int xx = Maxx - Minx , yy = Maxy - Miny ;if( xx > 1000 || yy > 1000 ) continue ;if( i == 0 || j == 0 )cnt[xx][yy] ++ ;elsecnt[xx][yy] += 2 ;}}}}int n , m ;int main(){int cas , casn = 1 ;scanf( "%d" , &cas ) ;init() ;while( cas -- ) {scanf( "%d%d" , &n , &m ) ;long long ans = 0 ;for( int i = 2 ; i <= n ; i ++ ) {for( int j = 2 ; j <= m ; j ++ ) {ans += (long long)cnt[i][j] * ( n - i + 1 ) * ( m - j + 1 ) ;}}printf("Case %d: %lld\n" , casn ++ , ans ) ;}return 0 ;}

J. As Long as I Learn, I Live

题意 : 给你一个无向图 , 每个点都有权值,问从0号点开始使用往权值最大的点走,最后权值和是多少,最终停在哪一点。( 并且保证图没有环,从每个点出发,不会有两个权值相同,且最大的点 ) 。

思路 : 直接处理出两个数组, Val[i] 表示第i个点的后继点中的最大权值 , To[i]表示第i个点的后继最大权值点是哪个点,然后按照题意模拟下就行了

#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int Map[105];int To[105];int Val[105] ;int main(){int n , m ;int cas ;int casn = 1 ;scanf( "%d" , &cas ) ;while( cas -- && scanf( "%d%d" , &n , &m ) != EOF ) {memset( Map , -1 , sizeof(Map) ) ;memset( To , -1 , sizeof(To) ) ;int i ;for( i = 0 ; i < n ;i ++ ) scanf( "%d" , &Val[i] ) ;for( i = 0 ; i < m ; i ++ ) {int a , b ;scanf( "%d%d" , &a , &b ) ;if( Val[b] > Map[a] ) {Map[a] = Val[b] ;To[a] = b ;}}int start = 0 , ans = Val[0] ;while( To[start] != -1 ) {start = To[start] ;ans += Val[start] ;}printf( "Case %d: %d %d\n" , casn ++ , ans , start ) ;}return 0 ;}


0 0
原创粉丝点击