下午的一场选拔赛,题源是 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 101
int 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] ++ ;
else
cnt[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 ;
}