2019河北省大学生程序设计竞赛(重现赛)

本文解析了2019年河北省大学生程序设计竞赛中的ABBattleofBalls、BIceboundandSequence、C分治、FTakeApples、J舔狗、LSmartRobot等题目,涵盖并查集、二分+快速幂取模、区间DP、记忆化搜索、博弈、拓扑排序及暴力深搜等多种算法。详细介绍了每道题目的题意、解题思路及代码实现。

比赛链接—> 2019河北省大学生程序设计竞赛

A Battle of Balls(并查集)

题意: 有一个100×100的平面,给出圆的半径和n个点的坐标,问该圆能否从底部y=0到达顶部y=100。
题解: 计算每两点之间的距离,如果距离大于圆的半径时将两点连起来(用并查集并起来),若最后将顶部和底部分隔开(同一个集合)则说明不能到达,在这里要注意输入x的时候要判断x和边界x=0 x=100之间能否通过,如果不能通过则并在一起,还有这里坐标都是double判断的时候要注意精度的问题,判断a大于b不是简单的a > b,而是a - b > eps(eps=1e-8)。

#include <cstdio>
#include <cmath>
using namespace std ; 
const int N = 1e3 + 50 ; 
const double eps = 1e-8 ; 
int s[N] ;
double x[N] , y[N] ; 
int find(int x){
	if(x!=s[x])	s[x]=find(s[x]) ;
	return s[x] ; 
}
double distance(int i,int j){
	return sqrt((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j])) ;
}
void link(int a ,int b){
	a = find(a) , b = find(b) ;
	if(a != b)	s[a]=b ; 
}
int main(){
	int t ; scanf("%d",&t) ;
	while(t--){
		int n ;   double r ; 
		scanf ("%d%lf",&n,&r) ;
        for (int i=0;i<=n+1;++i)    s[i]=i;
		r *= 2 ; 
		for (int i=1 ; i<=n ; ++i){
			scanf("%lf%lf",&x[i],&y[i]) ; 
			if(x[i]-r < eps)	link(0,i) ;	//边界是否能通过 
			if(100.00-x[i]-r < eps)	link(i,n+1) ;
		}
		for(int i=1 ; i<=n ; ++i)
			for (int j=i+1 ; j<=n ;++ j)	//每两点间的距离小于圆的直径则不能过,将两点连起来 
				if(distance(i,j)-r < eps)		link(i,j) ; 
		if (find(0) == find(n+1))	printf ("No\n") ;
		else	printf ("Yes\n") ; 
	} 
	return 0 ; 
} 
B-Icebound and Sequence(二分+快速幂取模)

题意:给出q,n,p要求输出 ∑ i = 1 n \sum_{i=1}^{n} i=1nqi mod p。
题解:这道题本来是可以用等比数列求和公式然后用快速幂取模,但是因为不取模long long会超,但是取模了去除于(q-1)又不准确。所以可以直接计算连加,但是如果直接计算n<=1e9会超时,所以用到二分的思想。
当n为奇数时:s(n) = s( n 2 \frac{n}{2} 2n) + s( n 2 \frac{n}{2} 2n)*fastpow(q, n 2 \frac{n}{2} 2n,mod) + fastpow(q,n,mod) ;
当n为偶数时:s(n) = s( n 2 \frac{n}{2} 2n) + s( n 2 \frac{n}{2} 2n)*fastpow(q, n 2 \frac{n}{2} 2n,mod) ;

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std ;
typedef long long ll ;
ll n , q , p ; 
ll fastpow(ll a , ll b , ll mod){
	ll res = 1 ; 
	while(b){
		if (b&1)
			res = res*a%mod ; 
		a = a*a%mod ; 
		b >>= 1 ; 
	}
	return res ; 
}
ll fun(ll t){
	if (t == 1)		return q%p ; 
	if (t%2== 0)	return (fun(t/2)+fun(t/2)*fastpow(q,t/2,p))%p ;
	else	return  (fun(t/2)+fun(t/2)*fastpow(q,t/2,p)+fastpow(q,t,p))%p ;
}
int main(){
	int t ; cin >> t ; 
	while(t --){
		cin >> q >> n >> p ;
		cout << fun(n) << endl ; 
	} 
	return 0;
} 

C 分治(区间dp / 记忆化搜索)

题意: 国王要攻打n个连续的城市,每个城市有赔偿金ai,攻打第i个城市要赔偿给旁边两个城市各ai赔偿金,当攻打一个国家 i 时,为了防止国家间的联合对抗,需要给该国家周围,所有未被攻占的国家支付ai​ 个金币,即对于国家 i,它左侧第一个已被攻打的国家为 l,右侧第一个已被攻打的国家为 r,则他需要给[l+1,i-1] 和 [i+1,r-1] 之间的国家支付金币。如果 l 不存在,则需要给 [1, i-1] 之间的所有国家支付金币;若 r 不存在,则需要给 [i+1,n] 之间的所有国家支付金币。
题解:大佬题解

区间dp解法:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std ; 
const int N = 105 ; 
typedef long long ll ; 
ll a[N] , dp[N][N] ;
ll INF = 0x3f3f3f3f ; 
int main(){
	int t ; scanf ("%d",&t) ; 
	while(t --){
		int n ; scanf ("%d",&n);
		memset(dp,INF,sizeof(dp)) ; 
		for (int i=1 ; i <= n ;++i)	dp[i][i]=0;
		for (int i=1 ; i<=n ; ++i)	scanf("%d",&a[i]) ;
		for (int len=1 ; len<n ; ++ len){
			for (int i=1 ; i<=n-len ; ++i){
				int j = i+len ; 
				for (int k=i ; k <= j ; ++ k)
					dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]*len) ;
				dp[i][j]=min(dp[i][j],dp[i+1][j]+a[i]*len) ;//边界情况
				dp[i][j]=min(dp[i][j],dp[i][j-1]+a[j]*len) ; 
			}
		}
		printf ("%lld\n",dp[1][n]) ;
	}
	return 0 ; 
}

记忆化搜索解法:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std ;
typedef long long ll ;
const int N = 105 ;
ll a[N] , dp[N][N] ;
ll dfs(int l , int r){
    if (dp[l][r] != -1) return dp[l][r] ; //记忆化搜索
    if (r <= l)      return 0 ;
    ll ans =  ((ll)1<<63)-1 ;
    for (int i=l ; i<=r ; ++i)   //枚举攻打点
        ans = min(ans,dfs(l,i-1)+dfs(i+1,r)+a[i]*(r-l)) ;
    return dp[l][r]=ans ;
}
int main(){
    int t ; scanf("%d",&t);
    while(t--){
        int n ; scanf("%d",&n) ;
        for (int i=1 ; i<=n ; ++i)   scanf("%d",&a[i]);
        memset(dp,-1,sizeof(dp)) ;
        printf ("%lld\n",dfs(1,n)) ;
    }
    return 0;
}
F Take Apples(博弈)

题解
emmmmmm说实话我还是不懂博弈,题解看了半天还是不懂emmmmm。。。。。。。题是偷偷参考大佬的

#include <cstdio>
int main(){
    int m , n , s ;
    while(~scanf("%d%d%d",&m,&n,&s)){
        if(n <= s && m%(s+1) == 0)
            printf ("Bob\n") ;
        else    printf ("Alice\n") ;
    }
    return 0 ; 
}
J 舔狗(拓扑排序)

题意: 你需要给这 n 只舔狗配对,对于舔狗 i,他可以和他朝思暮想的人 ai配对。另外,喜欢 i 的其他舔狗也可以和他配对。你需要让没有被配对的舔狗尽量少。
题解: 一个用到优先队列的拓扑排序,一般的拓扑排序是要求没有环的因为要从入度为0开始进队出队,但是这道题是从入度最小的出队,先将没有人喜欢的配对然后用vis数组标记已经配对。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std ; 
const int N = 1e6+5 ; 
struct node{
	int id , in , out ;
	bool operator < (const node & a)const {//运算符重载
		return in > a.in ; //入度小的优先
	}
}v[N] ; 
int in[N] ; 
priority_queue<node> q ;  
bool vis[N] ; 
int main(){
	int n ;	scanf("%d",&n);
	for (int i=1;i<=n;++i){
		int x ;	scanf("%d",&x) ; 
		v[i].id=i , v[i].out=x ;
		++in[x] ;
	}
	//将全部点都进队
	for (int i=1 ; i<=n ; ++i)	v[i].in=in[i] , q.push(v[i]) ;
	int cnt = 0 ; 
	while(!q.empty()){
		node t = q.top() ; q.pop() ;
		if (vis[t.id])	continue ; //已配对的跳过 
		if (!vis[t.out]){
			vis[t.out] = true ; vis[t.id]=true ; ++ cnt ;	//配对成功 
			int next = v[t.out].out ; 
			//修改指向的入度再重新入队,因为是优先队列而且有标记是否配对所以多入队几次也没关系
			in[next] -- , v[next].in = in[next] ;
			q.push(v[next]) ; 
		}
	}
	printf ("%d\n",n-cnt*2) ;
	return 0 ; 
}

L Smart Robot(暴力深搜)

题意: n × n 的矩阵中每个格子的数为0 ~ 9 , 机器人能走到的地方为魔法数字,例如走了三个先后为0 7 8 那么该魔法数字为78,问机器人不能搜索到的最小数为多少。
题解: 直接暴力搜索,不会超时。。。。。。。。然后能搜索到的标记下,最后顺序找一下哪个没有标记然后输出。

#include <cstdio>
const int N = 55 ; 
int a[N][N] , n ; 
bool vis[100050] ; 
int dir[4][2] = {{-1,0},{0,-1},{1,0},{0,1}} ;
void dfs(int x ,int y,int step,int val){	//坐标,位数 ,当前数的值 
	vis[val] = true ;  //标记能够搜索到 
	if (step == 5)	return ; //位数超过五就不用再搜索了
	for (int i=0 ; i<4 ; ++i){
		int dx = x + dir[i][0] ;
		int dy = y + dir[i][1] ;
		if (dx>=1&&dx<=n&&dy>=1&&dy<=n)
			dfs(dx,dy,step+1,val*10+a[dx][dy]) ;	//搜索下一个数 
	}
}
int main(){
	scanf ("%d",&n) ;
	for (int i=1 ; i<=n ; ++i)
		for (int j=1 ; j<=n ; ++ j)
			scanf ("%d",&a[i][j]) ;
	for (int i=1 ; i<=n ; ++i)
		for (int j=1 ; j<=n ; ++j)
			dfs(i,j,1,a[i][j]) ; 
	int i = 0 ;
	while(vis[i] && i < 100050)	++ i ;
	printf ("%d\n",i) ;
	return 0 ; 
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值