寒假集训03day

题目链接

取火柴游戏

题目描述

输入 k k k k k k 个整数 n 1 , n 2 , ⋯   , n k n_1,n_2,\cdots,n_k n1,n2,,nk,表示有 k k k 堆火柴棒,第 i i i 堆火柴棒的根数为 n i n_i ni;接着便是你和计算机取火柴棒的对弈游戏。取的规则如下:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不允许跨堆取,也不允许不取。

谁取走最后一根火柴为胜利者。

例如: k = 2 k=2 k=2 n 1 = n 2 = 2 n_1=n_2=2 n1=n2=2,A 代表你,P 代表计算机,若决定 A 先取:

  • A: ( 2 , 2 ) → ( 1 , 2 ) (2,2) \rightarrow (1,2) (2,2)(1,2),即从第一堆中取一根。
  • P: ( 1 , 2 ) → ( 1 , 1 ) (1,2) \rightarrow (1,1) (1,2)(1,1),即从第二堆中取一根。
  • A: ( 1 , 1 ) → ( 1 , 0 ) (1,1) \rightarrow (1,0) (1,1)(1,0)
  • P: ( 1 , 0 ) → ( 0 , 0 ) (1,0) \rightarrow (0,0) (1,0)(0,0)。P 胜利。

如果决定 A A A 后取:

  • P: ( 2 , 2 ) → ( 2 , 0 ) (2,2) \rightarrow (2,0) (2,2)(2,0)
  • A: ( 2 , 0 ) → ( 0 , 0 ) (2,0) \rightarrow (0,0) (2,0)(0,0)。A 胜利。

又如 k = 3 k=3 k=3 n 1 = 1 n_1=1 n1=1 n 2 = 2 n_2=2 n2=2 n 3 = 3 n_3=3 n3=3 A A A 决定后取:

  • P: ( 1 , 2 , 3 ) → ( 0 , 2 , 3 ) (1,2,3) \rightarrow (0,2,3) (1,2,3)(0,2,3)
  • A: ( 0 , 2 , 3 ) → ( 0 , 2 , 2 ) (0,2,3) \rightarrow (0,2,2) (0,2,3)(0,2,2)
  • A 已将游戏归结为 ( 2 , 2 ) (2,2) (2,2) 的情况,不管 P 如何取 A 都必胜。

编一个程序,在给出初始状态之后,判断是先取必胜还是先取必败,如果是先取必胜,请输出第一次该如何取。如果是先取必败,则输出 lose

输入格式

第一行,一个正整数 k k k

第二行, k k k 个整数 n 1 , n 2 , ⋯   , n k n_1,n_2,\cdots,n_k n1,n2,,nk

输出格式

如果是先取必胜,请在第一行输出两个整数 a , b a,b a,b,表示第一次从第 b b b 堆取出 a a a 个。第二行为第一次取火柴后的状态。如果有多种答案,则输出 ⟨ b , a ⟩ \lang b,a\rang b,a 字典序最小的答案( 即 b b b 最小的前提下,使 a a a 最小)。

如果是先取必败,则输出 lose

样例 #1

样例输入 #1

3
3 6 9

样例输出 #1

4 3
3 6 5

样例 #2

样例输入 #2

4
15 22 19 10

样例输出 #2

lose

提示

数据范围及约定

对于全部数据, k ≤ 500000 k \le 500000 k500000 n i ≤ 1 0 9 n_i \le 10^9 ni109

算法思路

标准的nim(尼姆)游戏,可以算成一个模版,推荐视频:
视频链接
nim游戏的结论便是x1xorx2…=s,看s是否等于0,如果等于0,那么先手必败,反之必胜。
这个题在标准的nim(尼姆)游戏上还有一个要求,如果先手必胜,那么需要输出先手该如何拿,我们可以依次遍历1-n,然后判断sum[i]xor s的值是否减小,如果减少了,那么我们就拿掉减少的火柴个数即可。
详细的证明请看推荐视频。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+7;
int t[N],n;
int main(){
	cin>>n;
	int ans=0;
	for(int i=1;i<=n;i++) {
		cin>>t[i];
		ans^=t[i];
	}
	if(ans==0) {
		cout<<"lose";
		return 0;
	}
	for(int i=1;i<=n;i++){
		if((t[i]^ans)>=t[i]) continue;
		cout<< (t[i]-(t[i]^ans)) << " " <<i<<endl;
		t[i]-=(t[i]-(t[i]^ans));
		break; 
	}
	for(int i=1;i<=n;i++) cout<<t[i]<<" ";
	return 0;
} 

题目链接

[SHOI2015] 自动刷题机

题目背景

曾经发明了信号增幅仪的发明家 SHTSC 又公开了他的新发明:自动刷题机——一种可以自动 AC 题目的神秘装置。

题目描述

自动刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写程序。每秒,自动刷题机的代码生成模块会有两种可能的结果:

1.写了 x x x 行代码
2.心情不好,删掉了之前写的 y y y 行代码。(如果 y y y 大于当前代码长度则相当于全部删除。)

对于一个 OJ,存在某个固定的正整数长度 n n n,一旦自动刷题机在某秒结束时积累了大于等于 n n n 行的代码,它就会自动提交并 AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC 在某个 OJ 上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个 OJ 的 n n n 究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 k k k 道题,希望你计算 n n n 可能的最小值和最大值。

输入格式

第一行两个整数 l , k l , k l,k,表示刷题机的日志一共有 l l l 行,一共了切了 k k k 题。

接下来 l l l 行,每行一个整数 x i x_i xi,依次表示每条日志。若 x i ≥ 0 x_i \geq 0 xi0,则表示写了 x i x_i xi 行代码,若 x i < 0 x_i \lt 0 xi<0,则表示删除了 − x i -x_i xi 行代码。

输出格式

输出一行两个整数,分别表示 n n n 可能的最小值和最大值。
如果这样的 n n n 不存在,请输出一行一个整数 − 1 -1 1

样例 #1

样例输入 #1

4 2
2
5
-3
9

样例输出 #1

3 7

提示

数据规模与约定
  • 对于 20 % 20\% 20% 的数据,保证 l ≤ 10 l \le 10 l10
  • 对于 40 % 40\% 40% 的数据,保证 l ≤ 100 l \le 100 l100
  • 对于 60 % 60\% 60% 的数据,保证 l ≤ 2 × 1 0 3 l \le 2 \times 10^3 l2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ l ≤ 1 0 5 1 \leq l \le 10^5 1l105 − 1 0 9 ≤ x i ≤ 1 0 9 -10^9 \le x_i \le 10^9 109xi109

算法思路

初看是枚举+模拟,但如果枚举肯定会超时,那么我们可以在枚举的基础上进行优化。
二分便是枚举优化的不二选择,但二分的前提便是证明数据的单调性,我们完全可以判断出来当n越大,我们所能ac的题目少,结论具有单调性,那么我们便可以进行二分。
二分难的便是check()函数和判断边界情况,check()我们直接进行模拟判断,找最小值的情况:当我们check()模拟以后,如果大于mid,那么我们就可以增大l(这是重点,大于是改变l),小于mid,我们减少r,等于便更新数据,最大值的情况基本一致,请看代码。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+7;
ll n,k,sum[N];
int check(ll x){
	ll t=0,size=0;
	for(int i=1;i<=n;i++){
		t+=sum[i];
		if(t<0) t=0;
		if(t>=x) t=0,size++;
	}
	return size;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>sum[i];
	if(k>n) {
		cout<<-1;
		return 0;
	}	
	ll l=1,r=1e15,mid,ans=1e15+1;
	//找最小值 
	while(l<=r){
		mid=(l+r)/2;
		if(check(mid)>k) l=mid+1;
		else if(check(mid)<k) r=mid-1;
		else ans=mid,r=mid-1;
	}
	if(ans!=1e15+1) cout<<ans;
	else {
		cout<<-1;
		return 0;
	} 
	cout<<" ";
	l=1,r=1e15,mid,ans=1e15+1;
	while(l<=r) {
		mid=(l+r)/2;
		if(check(mid)>k) l=mid+1;
		else if(check(mid)<k) r=mid-1;
		else ans=mid,l=mid+1;
	}
	cout<<ans;
	return 0;
} 

题目链接

[NOIP2017 普及组] 棋盘

题目背景

NOIP2017 普及组 T3

题目描述

有一个 m × m m \times m m×m 的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在要从棋盘的最左上角走到棋盘的最右下角。

任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的), 你只能向上、下、左、右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费 $1 $ 个金币。

另外, 你可以花费 2 2 2 个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个魔法不能连续使用, 而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法; 只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。

现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?

输入格式

第一行包含两个正整数 $ m, n$,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。

接下来的 $ n $ 行,每行三个正整数 $ x, y, c$, 分别表示坐标为 ( x , y ) (x,y) (x,y) 的格子有颜色 $ c$。

其中 $ c=1$ 代表黄色,$ c=0$ 代表红色。 相邻两个数之间用一个空格隔开。 棋盘左上角的坐标为 ( 1 , 1 ) (1, 1) (1,1),右下角的坐标为 ( m , m ) ( m, m) (m,m)

棋盘上其余的格子都是无色。保证棋盘的左上角,也就是 ( 1 , 1 ) (1, 1) (1,1) 一定是有颜色的。

输出格式

一个整数,表示花费的金币的最小值,如果无法到达,输出 -1

样例 #1

样例输入 #1

5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0

样例输出 #1

8

样例 #2

样例输入 #2

5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0

样例输出 #2

-1

提示

样例 1 说明

棋盘的颜色如下表格所示,其中空白的部分表示无色。

红 \color{red}\text{红} 红 \color{red}\text{红}
黄 \color{yellow}\text{黄}
黄 \color{yellow}\text{黄} 红 \color{red}\text{红}
黄 \color{yellow}\text{黄}
红 \color{red}\text{红}

( 1 , 1 ) (1,1) (1,1) 开始,走到 ( 1 , 2 ) (1,2) (1,2) 不花费金币。

( 1 , 2 ) (1,2) (1,2) 向下走到 ( 2 , 2 ) (2,2) (2,2) 花费 1 1 1 枚金币。

( 2 , 2 ) (2,2) (2,2) 施展魔法,将 ( 2 , 3 ) (2,3) (2,3) 变为黄色,花费 2 2 2 枚金币。

( 2 , 2 ) (2,2) (2,2) 走到 ( 2 , 3 ) (2,3) (2,3) 不花费金币。

( 2 , 3 ) (2,3) (2,3) 走到 ( 3 , 3 ) (3,3) (3,3) 不花费金币。

( 3 , 3 ) (3,3) (3,3) 走到 ( 3 , 4 ) (3,4) (3,4) 花费 1 1 1 枚金币。

( 3 , 4 ) (3,4) (3,4) 走到 ( 4 , 4 ) (4,4) (4,4) 花费 1 1 1 枚金币。

( 4 , 4 ) (4,4) (4,4) 施展魔法,将 ( 4 , 5 ) (4,5) (4,5) 变为黄色,花费 $ 2$ 枚金币。

( 4 , 4 ) (4,4) (4,4) 走到 ( 4 , 5 ) (4,5) (4,5) 不花费金币。

( 4 , 5 ) (4,5) (4,5) 走到 ( 5 , 5 ) (5,5) (5,5) 花费 1 1 1 枚金币。

共花费 $8 $ 枚金币。

样例 2 说明

棋盘的颜色如下表格所示,其中空白的部分表示无色。

红 \color{red}\text{红} 红 \color{red}\text{红}
黄 \color{yellow}\text{黄}
黄 \color{yellow}\text{黄}
  \color{white}\text{ }  
红 \color{red}\text{红}

( 1 , 1 ) ( 1, 1) (1,1) 走到 ( 1 , 2 ) ( 1, 2) (1,2),不花费金币。

( 1 , 2 ) ( 1, 2) (1,2) 走到 ( 2 , 2 ) ( 2, 2) (2,2),花费 $ 1 $ 金币。

施展魔法将 ( 2 , 3 ) ( 2, 3) (2,3) 变为黄色,并从 ( 2 , 2 ) ( 2, 2) (2,2) 走到 ( 2 , 3 ) ( 2, 3) (2,3) 花费 $ 2$ 金币。

( 2 , 3 ) ( 2, 3) (2,3) 走到 ( 3 , 3 ) ( 3, 3) (3,3) 不花费金币。

( 3 , 3 ) ( 3, 3) (3,3) 只能施展魔法到达 ( 3 , 2 ) , ( 2 , 3 ) , ( 3 , 4 ) , ( 4 , 3 ) ( 3, 2),( 2, 3),( 3, 4),( 4, 3) (3,2),(2,3),(3,4),(4,3)

而从以上四点均无法到达 ( 5 , 5 ) ( 5, 5) (5,5),故无法到达终点,输出 − 1 -1 1

数据规模与约定

对于 30 % 30\% 30% 的数据, 1 ≤ m ≤ 5 , 1 ≤ n ≤ 10 1 ≤ m ≤ 5, 1 ≤ n ≤ 10 1m5,1n10

对于 60 % 60\% 60% 的数据, 1 ≤ m ≤ 20 , 1 ≤ n ≤ 200 1 ≤ m ≤ 20, 1 ≤ n ≤ 200 1m20,1n200

对于 100 % 100\% 100% 的数据, 1 ≤ m ≤ 100 , 1 ≤ n ≤ 1 , 000 1 ≤ m ≤ 100, 1 ≤ n ≤ 1,000 1m100,1n1,000

算法思路

初看便可以判断是搜索了,就正常写一个dfs深搜。
对于颜色的判别,1代表黄色,0代表红色,我们最好是在原有基础上+1,这样的话0代表无色,2代表黄色,1代表红色,这样我们就不需要对数组重新赋值。
重点便是走到无色方块以后,我们如何改变颜色,其实无非就两种情况,与当前颜色不相等(钱+3),与当前颜色相等(钱+2),不用考虑后面的颜色是什么,我们直接进行贪心是最好的。
然后便是结束条件,正常考虑dfs走迷宫的结束条件便是到达终点,但这样的话时间复杂度会比较高,会超时,那么我们可以再创建一个数组p,这个p数组代表的是从起点到i,j的最小花费,这样我们就可以到每一个点的花费都是最小的,可以提前结束,剪枝的效果比正常深搜好很多。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N =1e5+7;
//minsum记录的是从起点到i j 的最小花费 
//判断是否更新数据 否则就不管了 
int sum[110][110],p[110][110],ms[110][110];
int m,n;
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
void dfs(int x,int y,int co,int mo){
	//如果当前的花费已经超过了我们记录的最小值 那么我们就不管了 
	if(mo<ms[x][y]) ms[x][y]=mo;
	else return;
	if(x==m && y==m) return;
	for(int i=0;i<4;i++){
		int tx=x+dx[i];
		int ty=y+dy[i];
		//判断是否越界 
		if(tx>0 && tx<m+1 && ty>0 && ty<m+1 && p[tx][ty]==0) {
			//如果都没有颜色 
			if(sum[tx][ty]==0 && sum[x][y]==0) continue;
			//如果走到了无色的砖块
			//贪心思考 直接变成颜色一样 
			if(sum[tx][ty]==0) {
				p[tx][ty]=1;
				dfs(tx,ty,co,mo+2);
				p[tx][ty]=0;
			}
			else if(co == sum[tx][ty]) {
				p[tx][ty]=1;
				dfs(tx,ty,sum[tx][ty],mo);
				p[tx][ty]=0;
			} else {
				p[tx][ty]=1;
				dfs(tx,ty,sum[tx][ty],mo+1);
				p[tx][ty]=0;
			}
		}
	}
}
int main(){
	
	cin>>m>>n;
	for(int i=0,x,y,c;i<n;i++){
		cin>>x>>y>>c;
		sum[x][y]=c+1;
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			ms[i][j]=INT_MAX;
		}
	} 
	p[1][1]=1;
	dfs(1,1,sum[1][1],0);
	if(ms[m][m]==INT_MAX) cout<<-1;
	else cout<<ms[m][m];
} 

题目链接

足球

题目描述

我们当中有很多热爱中国足球的同学,我们都知道中超(中国足球超级联赛)的规则:

一场比赛中,若获胜(即你的得分严格大于对手得分)则获得 3 3 3 的积分,若打平(即你的得分等于对手得分)则获得 1 1 1 分,若失败(即你的得分严格小于对手得分)获得 0 0 0 积分。

这个问题很简单,假设 N N N 轮比赛中你一共攻入 S S S 个球,丢掉 T T T 个球,那么你可能获得的最大得分和最小得分是多少?

输入格式

多组数据,每组数据一行三个整数 S , T , N S,T,N S,T,N 1 0 9 ≥ S , T ≥ 0 10^9\ge S,T \ge 0 109S,T0 1 0 9 ≥ N ≥ 1 10^9\ge N \ge 1 109N1)。每个测试点内数据组数不超过 3 × 1 0 4 3\times 10^4 3×104 组。

输出格式

对于每组数据输出一行,两个整数表示最大得分和最小得分。

样例 #1

样例输入 #1

1 1 1
1 1 2

样例输出 #1

1 1
3 2

算法思路

其实这题没有什么可以说的,有几种情况,依次考虑便好了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N =1e5+7;
//最大值 
//如果要得分多 那我们只能多赢
//但赢得前提就是多场比赛 有多的进球
//如果有多场比赛 那么我们就保证(s >= n-1)除第一把以外(至少是平局) 输掉的球都在第一场 然后剩下的所有球来进打第一场  
//最小值
//有两种比赛方法 
//第一种 跟求最大值同理 但反向来求(最多赢一把加上N场平局) 
//还有就是直接梭哈 然后平局 (最多赢一把 然后N场平局) 
//取最小值即可 
int main(){
	ll s,t,n,mn,mx;
	while(cin>>s>>t>>n){
		mx = mn = 0;
		if(s < n)
		{
			mx += s * 3;
			mx += n - s - 1;
			if(!t)
				mx++;
			cout<<mx;
		}
		else
		{
			mx += (n - 1) * 3;
			if(s - (n - 1) > t)
				mx += 3;
			else if(s - (n - 1) == t)
				mx++;
			cout<<mx;
		}
		cout<<" "; 
		if(s > t)
		{
			mn += 3;
			if(t < n - 1)
				mn += n - 1 - t;
			cout<<mn;
		}
		else
		{
			long long a = 3, b = 0;
			if(t < n - 1)
				a += n - 1 - t;
			if(n > t - s)
				b = n - (t - s);
			cout<<min(a,b);
		}
		cout<<endl;
	}
	return 0;
} 

题目链接

Single-use Stones

题面翻译

有许多的青蛙要过河,可惜的是,青蛙根本跳不过河,他们最远只能跳 L 单位长度,而河宽 W 单位长度。

在河面上有一些石头,距离i远的地方有ai个石头,每个石头只能使用一次,求最大能有多少青蛙过河。

输入的第一行为两个整数 W,L (1<l<w<10^5)

第二行有 W-1 个整数a1,a2…aw-1(0<ai<10^4)

输出为一个整数,即能过河的最大青蛙数

题目描述

A lot of frogs want to cross a river. A river is $ w $ units width, but frogs can only jump $ l $ units long, where $ l < w $ . Frogs can also jump on lengths shorter than $ l $ . but can’t jump longer. Hopefully, there are some stones in the river to help them.

The stones are located at integer distances from the banks. There are $ a_i $ stones at the distance of $ i $ units from the bank the frogs are currently at. Each stone can only be used once by one frog, after that it drowns in the water.

What is the maximum number of frogs that can cross the river, given that then can only jump on the stones?

输入格式

The first line contains two integers $ w $ and $ l $ ( $ 1 \le l < w \le 10^5 $ ) — the width of the river and the maximum length of a frog’s jump.

The second line contains $ w - 1 $ integers $ a_1, a_2, \ldots, a_{w-1} $ ( $ 0 \le a_i \le 10^4 $ ), where $ a_i $ is the number of stones at the distance $ i $ from the bank the frogs are currently at.

输出格式

Print a single integer — the maximum number of frogs that can cross the river.

样例 #1

样例输入 #1

10 5
0 0 1 0 2 0 0 1 0

样例输出 #1

3

样例 #2

样例输入 #2

10 3
1 1 1 1 2 1 1 1 1

样例输出 #2

3

提示

In the first sample two frogs can use the different stones at the distance $ 5 $ , and one frog can use the stones at the distances $ 3 $ and then $ 8 $ .

In the second sample although there are two stones at the distance $ 5 $ , that does not help. The three paths are: $ 0 \to 3 \to 6 \to 9 \to 10 $ , $ 0 \to 2 \to 5 \to 8 \to 10 $ , $ 0 \to 1 \to 4 \to 7 \to 10 $ .

算法思路

我们可以知道每一个青蛙的跳跃能力l,那么我们可以求每一个区间长度为l的石头个数,如果这个区间的石头个数小于其他的石头个数,那么在这个区间的石头个数的青蛙走过以后,这个区间就不能再有青蛙能过去了。
所以重点便是求区间长度为l的石头个数的最小值。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N =1e5+7;
//我们只能看l长度的石头个数
//取最小值 
ll sum[N],ans=INT_MAX;
int main(){
	ll w,l;
	cin>>w>>l;
	for(int i=1,t;i<w;i++){
		cin>>t;
		sum[i]=sum[i-1]+t;
	}
	for(int i=l;i<w;i++){
		ans=min(sum[i]-sum[i-l],ans);
	}
	cout<<ans;
	return 0;
} ```


  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值