2018年安徽省赛小学组题解

T1:移动石子(stone)

题目描述

期待已久的“清明”假期终于到了。清明节是中华民族几千年来留下的优良传统,它有利于弘扬孝道亲情,唤醒家庭共同记忆,促进家庭成员乃至民族的凝聚力和认同感。

小学生卡卡西非常高兴,因为清明前后正是踏青的好时光,他终于可以和小伙伴们一起出去踏青了!然而,天公不作美,假期的第一天就下起了雨,卡卡西只能放弃出游计划,待在家里。

期间,无聊的卡卡西和小伙伴玩了一个博弈游戏:

n × n n\times n n×n 棋盘左上角放置了一块石头,两人轮流移动此石头,每次操作只能上下左右四个方向移动一格位置,并且已经走过的格子不能再次重复移动,若卡卡西先移动,且两人都用最优的策略移动,谁能赢?谁先不能移动了为输,其中: 1 ≤ n ≤ 10000 1≤n≤10000 1n10000

输入格式

多行数据,以最后一行为 0 0 0 时结束,每一行表示一个数表示 n n n

输出格式

多行数据,每行对应一个输出结果,若卡卡西能赢则输出 Kakashi,否则输出 Lost

样例

输入#1
2
0
输出#1
Kakashi

数据范围

对于 20 % 20\% 20% 的数据,保证 1 ≤ n ≤ 10 1≤n≤10 1n10;
对于 40 % 40\% 40% 的数据,保证 1 ≤ n ≤ 1000 1≤n≤1000 1n1000;
对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 10000 1≤n≤10000 1n10000


思路分析

想象一下,可以把整个棋盘拆成若干个 1 × 2 1\times 2 1×2 的格子,卡卡西先走,那么:

  • 很明显,先手的卡卡西只是从一个小格子的一侧走到了另一侧;
  • 而后手则需要找到一个新的 1 × 2 1\times 2 1×2 的格子。

因此,如果棋盘能被完整拆分成 1 × 2 1\times 2 1×2 的格子,那么一定是后手先找不到格子,然后输掉。

n × n n\times n n×n 为奇数时,总格子数为奇数,无法拆分,卡卡西必输。

n × n n\times n n×n 为偶数时,棋盘能完整拆分成 1 × 2 1\times 2 1×2 的格子,此时卡卡西必赢,必赢的策略就如上面分析的:只要保证剩下的可以走的格子数量一定是偶数,就可以保证他自己必赢。

注意多组测试数据。

code \texttt{code} code

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define QAQ cout<<"QAQ";
const int MAXN=1e5+5,inf=1e18,mod=1e9+7;

signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n;
	while(cin>>n&&n!=0){
		if(n%2==0){
			cout<<"Kakashi\n";
		}else{
			cout<<"Lost\n";
		}
	}
	return 0;
}

T2:列车路线(train)

题目描述

终于,卡卡西来到了一个叫“比特兰”的国家,“比特兰”是个很发达的国家,有着非常高科技的列车,和非常复杂的列车线路。具体来说,从理论上,我们可以假设这个国家的高科技列车可以不消耗时间的从 A A A 地瞬间转移到 B B B 地。同时,铁路线路复杂到,每对城市之间都有列车连接。但是不幸的是,由于这种列车运行需要很多维护工作,所以每天只能发出一次。从 i i i j j j 的列车( i ≠ j i \neq j i=j)会在 t i j t_{ij} tij 时间发出(保证 t i j t_{ij} tij 两两不同)。

如果有一条路径连接 A A A B B B 两个城市,并且满足路径上的每一条边的发车时间单调递增(也就是说经过的每段铁路的发出时间都要大于上一段的,因为我们需要从上一段铁路换乘下一段铁路),那么就是一条可行的从 A A A B B B 的路线。 现在“比特兰”的铁路局想要知道,一天之内,对于每一对 i i i j j j,如果想要从 i i i 到达 j j j,最早多早能到达呢?

输入格式

1 1 1行是一个整数 n n n,接下来 n n n 行,每行 n n n 个数表示 t i j ( i = j , t i j = 0 ) t_{ij}(i=j,t_{ij}=0) tiji=jtij=0

输出格式

n n n行,每行 n n n 个数表示 i i i j j j 最早的到达时间。

样例

输入#1
3
0 4 5
2 0 3
1 6 0
输出#1
0 4 5
2 0 3
1 4 0

数据范围

对于 20 % 20\% 20% 的数据, n ≤ 10 n≤10 n10
对于 40 % 40\% 40% 的数据, n ≤ 20 n≤20 n20
对于 60 % 60\% 60% 的数据, n ≤ 50 n≤50 n50
对于 100 % 100\% 100% 的数据, n ≤ 500 , t i j ≤ 1 0 9 n≤500, t_{ij}≤10^9 n500,tij109


思路分析

我们先看数据范围, n ≤ 500 n\leq 500 n500,很明显这个数据范围我们第一个想到的就是时间复杂度为 O ( n 3 ) O(n^3) O(n3) 的 Floyd,我们不难想到求全源最短路的 Floyd 算法,可是 WA 了/fn/fn/fn,原因是 Floyd 的思路是枚举每一个中间点去更新每两点之间的距离,必须满足具备最优子结构,可这道题不满足,因此我们无法使用 Floyd。

我们再想想,最短路还可以使用 dijkstra 算法,dijkstra 适用于单源最短路,它的的思路大致是每次选择一个最短路径长度已经确定的结点,然后通过从这个结点出发的边,更新其余还未完全确定最短路径长度的结点。这样反复确定 n n n 次,就确定了所有结点的最短路。对于这道题,我们将 dijkstra 的模板稍作修改即可。

我们知道,朴素版 dijkstra 求单源最短路的时间复杂度是 O ( n 2 ) O(n^2) O(n2),而利用堆优化的 dijkstra 这道题求单源最短路的时间复杂度是 O ( m log ⁡ m ) O(m\log m) O(mlogm),这里 m m m 表示边的数量。在这道题 m = n 2 m=n^2 m=n2 ,是稠密图,时间复杂度还不如朴素版。

由于这道题求的是全源,我们需要调用 n n n 次,每次的源点是 i i i ,所以最终的时间复杂度是 Θ ( n 3 ) \Theta(n^3) Θ(n3),可以通过此题。

AC   code \large \texttt{AC code} AC code

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define QAQ cout<<"QAQ";
const int MAXN=505,inf=1e18,mod=1e9+7;
int n;
int g[MAXN][MAXN];
int dist[MAXN][MAXN];
int vis[MAXN];
void dijkstra(int s){
	memset(vis,0,sizeof(vis));
	dist[s][s]=0;
	for(int i=1;i<=n;i++){
		int minn=-1,pos=2e9;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&dist[s][j]<pos){
				minn=j;
				pos=dist[s][j];
			}
		}
		vis[minn]=1;
		for(int j=1;j<=n;j++){
			if(dist[s][minn]<g[minn][j]){
				if(dist[s][j]>g[minn][j]){
					dist[s][j]=g[minn][j];
				}                
			}
		}
	}
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>g[i][j];
		}
	}
	memset(dist,0x7f,sizeof(dist));
	for(int i=1;i<=n;i++){
		dijkstra(i);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cout<<dist[i][j]<<" ";
		}
		cout<<"\n";
	}
	return 0;
}

T3:搭积木(block)

题目描述

积木对于大家来说应该很熟悉,我们可以用积木搭建出各种各样的模型,不同的人搭建出来的模型也会不一样。这不,小卡卡西正在和一群小伙伴玩积木呢!

铁人老师看见小朋友们在玩积木,就给大家出了一个难题:

积木的三边的边长是 X 、 Y 、 Z X、Y、Z XYZ, 每类积木可以有无限多个,搭积木时规定:每层只能放一个积木;上层积木的底部面积(积木的每个面都可以作为底面)必须小于其下层(上层积木的两条边必须严格小于下层积木)。

如何搭建能搭得最高。

输入格式

第一行 n n n表示积木种类数量,接下来 n n n 行,每行三个整数 X 、 Y 、 Z X、Y、Z XYZ

输出格式

最大高度。

样例

输入#1
1
10 20 30
输出#1
40
输入#2
4
1 6 1
10 1 1
2 6 10
1 2 3
输出#2
22

数据范围

n ≤ 1000 n\leq 1000 n1000
X , Y , Z ≤ 100 X,Y,Z\leq 100 X,Y,Z100


本题有多种解法。

思路分析

解法一:LIS

从“上层积木的底部面积(积木的每个面都可以作为底面)必须小于其下层(上层积木的两条边必须严格小于下层积木)”,不难看出这是一道 LIS(最长上升子序列)的 dp。

我们套一个 LIS 的板子,由于题目中没有规定积木摆放的方向,所以我们可以记录每块积木能摆成的三种方向,排一下序套 LIS 板子即可。

code \texttt{code} code

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define QAQ cout<<"QAQ";
const int MAXN=3e3+5,inf=1e18,mod=1e9+7;
int n,t;
struct hjmfdg{
	int x,y,z;
}a[MAXN];
int f[MAXN];
bool cmp(hjmfdg a,hjmfdg b){
	if(a.x!=b.x){
		return a.x<b.x;
	}
	return a.y<b.y;
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		int x,y,z;
		cin>>x>>y>>z;
		if(x>y)swap(x,y);
		if(y>z)swap(y,z);
		if(x>y)swap(x,y);
		a[++t]={x,y,z};
		a[++t]={x,z,y};
		a[++t]={y,z,x};
	}
	sort(a+1,a+t+1,cmp);
	int ans=0;
	for(int i=1;i<=t;i++){
		f[i]=a[i].z;
		for(int j=1;j<i;j++){
			if(a[j].x<a[i].x&&a[j].y<a[i].y){
				f[i]=max(f[i],f[j]+a[i].z);
			}
		}
		ans=max(ans,f[i]);
	}
	cout<<ans;
	return 0;
}

时间复杂度 O ( n 2 ) O(n^2) O(n2)


解法二:记忆化搜索

还是 dp,我们定义 d p i , j dp_{i,j} dpi,j 表示在 i × j i\times j i×j 的区域大小内搭出的最大高度。

因为一层只能放一块积木,所以我们每次记忆化搜索只需要找出搭上这块积木的答案的最大值即可,这块积木的答案即以这块积木为底的答案加上这块积木的高,即:

d p i , j = max ⁡ ( d p i , j , d f s ( x i , y i ) + z i ) dp_{i,j}=\max(dp_{i,j},dfs(x_i,y_i)+z_i) dpi,j=max(dpi,j,dfs(xi,yi)+zi)

如此一来,进行记忆化搜索,即可求得最终答案。记忆化搜索,相比于普通 dfs,用 d p i , j dp_{i,j} dpi,j 记录答案,避免多次求答案,直接使用记录的答案即可,时间复杂度便有了巨大的提升。

还是要记录每块积木能摆成的三种方向。

code \texttt{code} code

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define QAQ cout<<"QAQ";
const int MAXN=3e3+5,inf=1e18,mod=1e9+7;
int n;
int a[MAXN],b[MAXN],c[MAXN],dp[MAXN][MAXN];
int dfs(int x,int y){
	if(dp[x][y]!=-1){
		return dp[x][y];
	}
	dp[x][y]=0;
	for(int i=1;i<=n;i++){
		if(a[i]<x&&b[i]<y){
			dp[x][y]=max(dp[x][y],c[i]+dfs(a[i],b[i]));
		}
	}
	return dp[x][y];
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	int t=0;
	for(int i=1;i<=n;i++){
		int x,y,z;
		cin>>x>>y>>z;
		a[++t]=x;b[t]=y;c[t]=z;if(a[t]>b[t]) swap(a[t],b[t]);
		a[++t]=x;b[t]=z;c[t]=y;if(a[t]>b[t]) swap(a[t],b[t]);
		a[++t]=y;b[t]=z;c[t]=x;if(a[t]>b[t]) swap(a[t],b[t]);
	}
	n=t;
	memset(dp,-1,sizeof(dp));
	cout<<dfs(101,101);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值