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

下午的一场选拔赛,题源是 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 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:这段引用是一段C++代码,用于处理一个01序列的查询。代码中使用了前缀和和后缀和的思想,通过计算每个位置之前1的个数和之后1的个数,来处理查询区间内的操作。具体的实现细节可以参考代码注释。\[1\] 引用\[2\]:在处理多组询问时,为了不改变非连续1的计数,代码将区间分成两段处理。左半段都是1的情况单独处理,后半段第一个位置为0,可以通过前缀和相减来计算。这样可以保证在处理查询时不会影响到非连续1的计数。\[2\] 引用\[3\]:这段引用是对一个二进制循环序列的定义和问题描述。一个好的二进制循环序列是指通过一系列操作可以使其变为空序列。而一个二进制循环序列的美丽度被定义为使其变为好序列所需的最小修改次数。每次修改可以翻转序列中的任意一个元素,即0变为1,1变为0。给定一个长度为n的二进制字符串a和q个查询,每个查询给出两个整数li和ri,需要回答f(aliria\[li..ri\]),其中我们将子字符串aliria\[li..ri\]视为一个循环序列。\[3\] 问题: 请问这段代码的作用是什么? 回答: 这段代码的作用是处理一个01序列的查询。通过计算每个位置之前1的个数和之后1的个数,来处理查询区间内的操作。在处理多组询问时,为了不改变非连续1的计数,代码将区间分成两段处理,保证在处理查询时不会影响到非连续1的计数。最终输出每个查询区间的结果。\[1\]\[2\] #### 引用[.reference_title] - *1* *2* [ICPC22网络赛(1) - A 01 Sequence(前缀和,思维)](https://blog.csdn.net/Mr_dimple/article/details/126983251)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [The 2022 ICPC Asia Regionals Online Contest - A 01 Sequence](https://blog.csdn.net/qq_35339563/article/details/126959132)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值