Codeforces Round #246 (Div. 2)

本文详细介绍了五道编程题目,包括选择队伍、足球装备、质数交换、前缀后缀与平方铺装,涉及算法优化、数据结构应用及编程技巧,旨在提升编程能力和解决实际问题的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. Choosing Teams

http://codeforces.com/contest/432/problem/A


题目意思: 现在有n个人 , 告诉你每个人已经参加过几次 world final , 而每个人最多只能参加5次 , 现在我们需要组几支队伍,让他们可以至少参加k次world final , 问最多可以组多少支队伍.


思路: 大水题 ... 只要数数有几个人还能打k年就可以了


#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int main(){
    int n , k ;
    while( scanf( "%d%d" , &n , &k ) != EOF ) {
        int peo = 0 ;
        for( int i = 0 ; i < n ; i ++ ) {
            int a ; scanf( "%d" , &a ) ;
            if( 5 - a >= k ) peo ++ ;
        }
        printf( "%d\n" , peo / 3 ) ;
    }
    return 0 ;
}

B. Football Kit

http://codeforces.com/contest/432/problem/B


题目意思 : 有n支队伍,每支队伍有两种球衣 , 一种是主场球衣,颜色为x , 一种是客场球衣 , 颜色为y ( x!=y ) 。 每支队伍都需要和其他队伍进行两场比赛,主客各一场,也就是说每支队伍总共要打 2 * ( n - 1 ) 场 。一般来说都是主场穿主场球衣,客场穿客场球衣 , 但问题是有些球队的客场球衣和对方的主场球衣会相撞,那么这种情况下 ,客场球队穿主场球衣。你要求的是所有球队打完比赛之后,穿过几次主场球衣,几次客场球衣。


思路 : 因为总场数是不变的,所以我们只要考虑主场的球衣穿多少遍就可以了。首先,n-1场主场必然是穿主场球衣的 , 那n-1场客场就不一定了,这取决于这只球队的客场球衣和多少球队主场球衣冲突了。因为 球衣颜色的范围不大 , 所以我们只要用一个数组统计下主场球衣每种颜色的个数就可以了。


#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define MAXN 100005

int cnt[MAXN] ;
int home[MAXN] , away[MAXN] ;
int main(){
    int n ;
    while( scanf( "%d" ,&n ) != EOF ) {
        memset( cnt  , 0 , sizeof(cnt) ) ;
        for( int i = 1 ; i <= n ; i ++ ) {
            scanf( "%d%d" , &home[i] , &away[i] ) ;
            cnt[home[i]] ++ ;
        }
        for( int i = 1 ; i <= n ; i ++ ) {
            int a = n - 1 + cnt[away[i]] ;
            printf( "%d %d\n" , a , 2 * ( n - 1 ) - a ) ;
        }
    }
    return 0 ;
}

C. Prime Swaps

http://codeforces.com/contest/432/problem/C


题目意思 : 给你一个 1 ... n 的排列 , 你需要把它变成升序 , 但是只能通过下面操作 :

交换 a[i] , a[j] , 并且 j - i + 1 是一个素数( j > i )

然后让你构造一个可行的方案( 不是最小的方案 ) , 不过这个方案的操作数不能超过 5*n


思路 : 看到题目完全没有其他想法 ... 所以只能坚信那句话 ... 暴力出奇迹 !!

直接从1开始,一个一个的把对应的数移到相应的位置上 。 至于如何找 , 因为相邻的两个素数还是比较靠近的 , 于是就每次去找小于 pos[i] - i + 1 的最大素数( pos[i] 是当前i的下标 ) , 感觉应该这个差会很快就减小( 至于为毛可以小于 5*n ... 我也不知道 , 纯粹的YY ) ...


#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <math.h>
using namespace std ;

#define MAXN 100005
int prime[MAXN+5] ;
bool isprime[MAXN+2005] ;

int cntP ;

void getPrime(){
    isprime[2] = true ;
    for( int i = 3 ; i <= MAXN + 2000 ; i ++ ) {
        isprime[i] = i % 2 ;
    }
    int k = sqrt( 1.0 * (MAXN + 2000 )) ;
    for( int i = 3 ; i <= k ; i += 2 ) {
        if( isprime[i] ) {
            for( int j = i * i , s = 2 * i ; j <= MAXN + 2000 ; j += s ) {
                isprime[j] = false ;
            }
        }
    }
    cntP = 0 ;
    for( int i = 2 ; i <= MAXN ; i ++ ) {
        if( isprime[i] ) {
            prime[cntP++] = i ;
        }
    }
}

int a[MAXN] ;
int pos[MAXN] ;

int cal( int n ) {
    int pos = upper_bound( prime , prime + cntP , n ) - prime ;
    return prime[--pos] ;
}
vector<pair<int,int> > ans ;

int main(){
    getPrime() ;
    int n ;
    while( scanf( "%d" , &n ) != EOF ) {
        for( int i = 1 ; i <= n ; i ++ ) { 
            scanf( "%d" , &a[i] ) ;
            pos[a[i]] = i ;         
        }
        ans.clear() ;
        for( int i = 1 ; i <= n ; i ++ ) {
            while( pos[i] != i ) {
                int tmp = cal( pos[i] - i + 1 ) ;
                int posa = pos[i] , posb = pos[i] - tmp + 1 ;
                pos[i] = posb ; pos[a[posb]] = posa ;
                tmp = a[posa] ;
                a[posa] = a[posb] ;
                a[posb] = tmp ;
                ans.push_back( pair<int,int>(posb,posa)) ;
            }
        }
        printf( "%d\n" , ans.size() ) ;
        for( int i = 0 ; i < ans.size() ; i ++ ) {
            printf( "%d %d\n" , ans[i].first , ans[i].second ) ;
        }
    }
    return 0 ;
}

D. Prefixes and Suffixes

http://codeforces.com/contest/432/problem/D


题目意思 : 给你一个字符串 T , 你需要找到所有这样的子串S , 满足 S 即是T 的前缀 , 又是T 的后缀 , 并且还要求出每个 S 在T串中出现的次数 。


思路: 为了解决这个问题 , 如果我们会快速的计算一个字符串公共的前后缀 , 然后又能计算所有前缀出现的次数 , 那么这个问题就迎刃而解了。

要解决这两个问题都需要利用KMP的next数组的性质 , next[i] 表示的是母串前i个字符组成的字符串的最长前后缀的长度是多少( 这里的前后缀不包括这个串本身 ) 。


用题目中第一个样例举个例子 :

   字符串          A    B    A    C    A    B    A
    next值            0     0     1     0     1      2     3  

上面的next值下标从1开始

可以看出 next[3] =1 表明了 母串前3个字符组成的前缀 ABA 的最长公共前后缀的长度是1,为子串"A" 

                 next[7] = 3 表明了母串前7个字符组成的前缀ABACABA 的最长公共前缀的长度是3 , 为子串"ABA"

那么回到题目上面,我们如何利用这个性质来求出母串的所有前后缀呢 ?

很显然根据next[7] = 3 我们可以知道这个字符串最长的前后缀是3,也就是子串"ABA" , 那么那些短一些的呢 ?

首先思考下这些短一些的前后缀的字符是来自于哪里的? 前缀必然来自前next[7]的字符,而后缀必然来自后next[7]个字符,那后next[7]个字符根据next 数组的定义,又和前next[7]个字符是一样的,那么我们可以得出这样的结论 : 母串那些短的前后缀就是前next[7]个字符组成的子串的前后缀。

接下来就只要根据这个结论“递归”的求解下去就能得到母串所有前后缀的长度了。

接下来的问题就是求解母串中所有前缀出现的次数了 ,这个问题的话,其实以前做到过 hdu 3336 利用KMP+DP求解。

dp[i] 表示长度为i的前缀在母串中出现了多少遍 , 我们计算的时候从后面往前计算

首先根据next数组的定义,如果前缀i出现了,那么前缀next[i]也必然会出现 , 所以我们就根据这个递推

假设我们现在要计算前缀i的dp值( 此时 dp[j] | j > i 已经全部计算完毕了 ) , 那么 dp[i] = Σ( dp[j] | next[j] == i ) + 1

那么问题也就迎刃而解了


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;

char str[100005] ;
int f[100005];
int dp[100005] ;

void getnext ( char *t ) {
    int len = strlen ( t );
    int i = 0, j = -1;
    f[0] = -1;
    while ( i < len ) {
        if ( j == -1 || t[i] == t[j] ) {
            i++;
            j++;
            f[i] = j;
        }
        else
            j = f[j];
    }
}

int main(){
	while ( scanf( "%s" , str ) != EOF ) {
		getnext( str ) ;
		vector<pair<int,int> > ans ;
		memset( dp , 0 , sizeof(dp) ) ;
		int len = strlen( str ) ;
		for( int i = len ; i >= 1 ; i -- ) {
			dp[i] ++ ;
			dp[f[i]] += dp[i] ;
		}
		ans.push_back( pair<int,int>( len , 1 ) ) ;
		while( f[len] != 0 ) {
			len = f[len] ;
			ans.push_back( pair<int,int>( len , dp[len] ) ) ;
		}
		printf( "%d\n" , ans.size() ) ;
		sort( ans.begin() , ans.end() ) ;
		for( int i = 0 ; i < ans.size() ; i ++ ) 
			printf( "%d %d\n" , ans[i].first , ans[i].second ) ;
	}
	return 0 ;
}

E. Square Tiling

http://codeforces.com/contest/432/problem/E

题目意思 : 给你一个n*m的矩形,你需要给这个矩形涂颜色,但是有以下要求 :

 1. 你只能涂正方形的颜色块

 2. 相邻的颜色块不能同色


颜色用大写字母表示 , 你需要求出一个符合上述要求的字典序最小的方案( 字典序是从左到右从上到下来比较的)


思路 : 一开始天真的以为只要每次找到左上角,然后尽量涂比较大的正方形颜色块就行了 , 然后就WA case6 了 ...

后来想想不能是尽量放得大 ,而是尽量放得小,这样才可以留下空间给后面的格子放字典序小的字母,所以我后来的做法是,给第一个未涂色的格子涂上颜色,然后根据周围的情况判断能不能涂这种颜色,能就涂上,这样就可以给后面的格子留下空间,让他们涂字典序比较小的


#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int n , m ;
char str[105][105] ;

bool check( int x , int y , char ch ) {
	if( str[x][y+1] == ch ) {
		return false ;
	}
	if( str[x-1][y] != ch && str[x][y-1] != ch ) {
		str[x][y] = ch ;
		return true ;
	}
	if( str[x-1][y] == ch ) {
		int len = 1 ;
		int xx = x - 1 , yy = y ;
		while( str[xx][y] == ch ) {
			len ++ ;
			xx -- ;
		}
		xx ++ ;
		while( str[xx][yy] == ch ) {
			yy -- ;
		}
		yy ++ ;
		if( xx + len - 1 > n || yy + len - 1 > m ) return false ;
		for( int i = 1 ; i <= len ; i ++ ) {
			for( int j = 1 ; j <= len ; j ++ ) {
				if( str[xx + i - 1 ][ yy + j - 1 ] != ch && str[xx + i - 1 ][yy + j - 1 ] != 0 ) return false ;
			}
		}
		for( int i = 1 ; i <= len ; i ++ ) {
			for( int j = 1 ; j <= len ;j ++ ) {
				str[ xx + i - 1 ][ yy + j - 1 ] = ch ;
			}
		}
		return true ;
	}else{
		int len = 1 ;
		int xx = x , yy = y - 1 ;
		while( str[xx][yy] == ch ) {
			len ++ ;
			yy -- ;
		}
		yy ++ ;
		while( str[xx][yy] == ch ) {
			xx -- ;
		}
		xx ++ ;
		if( xx + len - 1 > n || yy + len - 1 > m ) return false ;
		for( int i = 1 ; i <= len ; i ++ ) {
			for( int j = 1 ; j <= len ; j ++ ) {
				if( str[xx + i - 1 ][ yy + j - 1 ] != ch && str[xx + i - 1 ][yy + j - 1 ] != 0 ) return false ;
			}
		}
		for( int i = 1 ; i <= len ; i ++ ) {
			for( int j = 1 ; j <= len ;j ++ ) {
				str[ xx + i - 1 ][ yy + j - 1 ] = ch ;
			}
		}
		return true ;
	}
}

int main(){
	while( scanf( "%d%d" , &n , &m ) != EOF ) {
		memset( str , 0 , sizeof(str) ) ;
		for( int i = 1 ; i <= n ; i ++ ) {
			for( int j = 1 ; j <= m ; j ++ ) {
				if( str[i][j] == 0 ) {
					for( char ch = 'A' ; ch <= 'Z' ; ch ++ ) {
						if( check( i , j , ch ) ) {
							break;
						}
					}
				}
			}
		}
		for( int i = 1 ; i <= n; i  ++ ) {
			for( int j = 1 ; j <= m ; j ++ ) 
				putchar( str[i][j] ) ;
			puts( "" ) ;
		}
	}
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值