CSP-S模拟赛4总结

T3

题面

野生皮卡丘

时间限制:1秒        内存限制:128M

题目描述

脸颊两边有着小小的电力袋。遇到危险时就会放电。会将尾巴竖起来,去感觉周围是否安全。没错这个就是皮卡丘,野生的皮卡丘大多群体行动,而且很少固定在一个地方。

在皮卡丘的家族中,有​ n 个房间,每个房间中有0~4只皮卡丘,且房间中最多居住4只皮卡丘,由于皮卡丘喜欢热闹,所以当某个房间中只有1只或2只皮卡丘时,皮卡丘就会感到孤独,所以要进行调整,协调皮卡丘换房间,最少让多少皮卡丘换房间才能使得所有的皮卡丘都不会感到孤独,如果无法做到请输出-1。

输入描述

第一行:一个数n,表示有n个房间  ​

第二行:n个数,第​个数​表示第​个房间中有多少只皮卡丘 ​[0,4] 

30%的数据:0≤ n≤ 100

100%的数据:0≤ n≤ 10^6

输出描述

一个数​ans,ans​只皮卡丘换房间后使得所有房间的皮卡丘都不会感到孤独,若没有可以满足的情况输出-1

样例

输入

5
2 2 1 3 4

输出

2

初次思路

越想越复杂,直接开摆

复盘思路

运用贪心,按投入次数/解决的数量一次解决

图例:

=:收益

->:吧……放到……中

基本思路

1->2//1=2
    剩下1,2没了
        (1,1)->1//2=3
            还剩下1 
                有3
                    1->3//1=1
                无3
                    4->1//2=1
    剩下2,1没了
        2->(2,2)//2=3
            有4
                4->2//1=1
            无4
                2->(3,3)//2=1
                
                2->2

code

#include<bits/stdc++.h>
using namespace std;
int n,a[1000005],num[10];
int main(){
	//freopen("room.in","r",stdin);
	//freopen("room.out","w",stdout);
	while(cin>>n){
		int ans=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			num[a[i]]++;
		}
		if(num[1]>num[2]){
			ans+=num[2];
			num[1]-=num[2];
			num[2]=0;
			ans+=num[1]/3;
			num[1]%=3;
			
			if(num[1]==1){
			    num[1]=0;
			    if(num[3]>0){
			        ans+=1;
			    }
			    else if(num[4]>1){
			        ans+=2;
			    }
			    else{
			        num[1]=1;
			    }
			}
			else if(num[1]==2){
			    num[1]=0;
			    if(num[3]>1){
			        ans+=1;
			    }
			    else if(num[3]>0){
			    	ans+=1;
			    	if(num[4]>1){
			        	ans+=2;
			    	}
				   	else{
			    	    num[1]=1;
			    	}
			    }
			    else{
			        num[1]=1;
			    }
			}
			
			/*if(num[1]>0){
				if(num[3]>0){
					if(num[3]>=num[1]){
						ans+=num[1];
						num[1]=0;
					}
					else{
						ans+=num[3];
						num[1]-=num[3];
						num[4]+=num[3];
						if(num[4]/2>=num[1]){
			                ans+=2*num[1];
			                num[1]=0;
			            }
					}
				}
				else{
					if(num[4]/2>=num[1]){
			            ans+=2*num[1];
			            num[1]=0;
			        }
				}
			}*/
		}
		else if(num[1]<num[2]){
			ans+=num[1];
			num[2]-=num[1];
			ans+=(num[2]/3);
			num[2]%=3;
			num[1]=0;
			
			if(num[2]==1){
			    num[2]=0;
			    if(num[4]>0){
			        ans+=1;
			    }
			    else if(num[3]>1){
			        ans+=2;
			    }
			    else{
			        num[2]=1;
			    }
			}
			else if(num[2]==2){
			    ans+=2;
			    num[2]=0;
			}
			
			/*if(num[4]>0){
				if(num[4]>=num[2]){
					ans+=num[2];
					num[2]=0;
				}
				else{
					ans+=num[4];
					num[2]-=num[4];
					num[3]+=2*num[4];
					if(num[3]/2>=num[2]){
			            ans+=2*num[2];
			            num[2]=0;
			        }
				}
			}
			else{
			    if(num[3]/2>=num[2]){
			        ans+=2*num[2];
			        num[2]=0;
			    }
			}*/
		}
		else{
			ans+=num[1];
			num[1]=0;
			num[2]=0;
		}
		if(num[1]>0||num[2]>0){
			cout<<-1<<'\n';
		}
		else{
			cout<<ans<<'\n';
		}
	}
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}
/*
1->2//1=2
	剩下1,2没了
		(1,1)->1//2=3
			还剩下1 
				有3
					1->3//1=1
				无3
					4->1//2=1
	剩下2,1没了
		2->(2,2)//2=3
			有4
				4->2//1=1
			无4
				2->(3,3)//2=1
				
				2->2
5
2 2 1 3 4
3
1 2 3
*/

T1

题面

树上博弈

时间限制:2秒        内存限制:256M

题目描述

小可和达达喜欢玩♂游戏。

他们再一棵树上玩游戏,已知,树有n个点,编号从 1~n,每条边都有一个正整数边权w​i​​。

小可和达达会在这棵树上玩很多次游戏。每次游戏开始前,丫丫都会给定一个点对 (u,v),

u和v是树上不同的两个点,然后把树上 u 到 v 的路径上所有边的边权拿出来,生成一个数组 s。

小可和达达轮流在 s 上以最优策略进行游戏,小可为先手。

游戏规则是这样的:

1.小可第一次取走一个数字。
2.记上一个人取走的数字是 x,当前的人需要从 s 中取走一个不大于 x 的数。
3.如果不能取数,则为输。

丫丫觉得这个游戏十分地有趣,她想知道,在所有可能的初始点对 (u,v) 中, 有多少种情况能使小可有必胜策略。

输入描述

第一行:输入一个正整数T,表示多组输入组数。

对于每一组数据:描述一棵树:

第一行:输入一个正整数 n,表示树的大小。

接下来 n-1 行,每行输入三个数 u,v,w,代表树上有一条权值为 w 的边,连接u,v两个点。

输出描述

对于每组数据,输出一行一个数,表示有多少种初始点对能使小可有必胜策略。

输入样例1

  1. 3
  2. 5
  3. 1 2 2
  4. 1 3 1
  5. 3 4 1
  6. 3 5 2
  7. 5
  8. 1 2 0
  9. 2 3 2
  10. 3 4 2
  11. 4 5 0
  12. 5
  13. 1 2 0
  14. 1 3 1
  15. 3 4 0
  16. 3 5 2

输出样例1

  1. 9
  2. 8
  3. 10

样例解释1

对于第一组数据,树的形态如下:

一共10中选点对的方案。

当选择(1,4)时,

s={1,1};

小可取1,达达取1

小可不能再取数。达达获胜。

其他的情况,小可都有必胜策略,答案为9。

数据描述

27%的数据:n≤5000
100%的数据:1≤T≤10,1≤∑n≤5<em>10e​5​​,1≤u,v≤n≤5</em>10​e5​​,0≤w≤10e​9​​

初次思路

啊?

复盘思路

数列最小值有奇数个必赢

                ↓

数列最小值有偶数个,数列次小值有奇数个,必赢

                ↓

有数出现奇数次,必赢

                ↓

数列所有数异或,不为0,必赢

大概思路就是这样,但要注意某些特殊情况,会使不赢的数列异或为0(如01,11,10),因此我们将权值替换为较大映射值

code

#include<bits/stdc++.h>
using namespace std;
int t,n;
long long dis[500005],base[500005],ans;
vector<pair<int,int> >g[500005];
map<long long,long long>mp,mpt;
void dfs(int u,int pre){
	for(int i=0;i<g[u].size();i++){
		if(g[u][i].first!=pre){
		    dis[u]=g[u][i].second^dis[u];
			dfs(g[u][i].first,u);
		}
	}
}
int main(){
	//freopen("game.in","r",stdin);
	//freopen("game.out","w",stdout);
	scanf("%d",&t);
	base[0]=1;
	for(int i=1;i<=500000;i++){
		base[i]=base[i-1]*13331;
	}
	while(t--){
		mp.clear();
		mpt.clear();
		memset(g,0,sizeof g);
		scanf("%d",&n);
		int u,v,w;
		for(int i=1;i<=n;i++){
			scanf("%d%d%d",&u,&v,&w);
			if(mp[w]==0){
				mp[w]=base[i];
			}
			g[u].push_back(make_pair(v,mp[w]) );
			g[v].push_back(make_pair(u,mp[w]) );
		}
		dfs(1,0);
		ans=n*(n-1)/2;
		for(int i=1;i<=n;i++){
			ans-=mpt[dis[i]];
			mpt[dis[i]]++;
		}
		cout<<ans<<'\n';
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}
//效率是代码的事,优雅是一辈子的事w~

T2

题面

左右横跳

时间限制:5秒        内存限制:256M

题目描述

小可在一个n个格子的场地左右横跳,每个格子都有一个权值,第 i 个格子的权值为a​i​​ 。

小可一开始在 1 位置,面向右侧,然后他要进行k次跳跃。

每次跳跃,小可都可以先他面向的方向,跳任意步,当然也可以跳0步,跳完之后立即转身。

也就是说如果这一次他向左跳,那么他下一次就会向右跳。

如果他从x格子跳到了y格子,那么小可的得分就会累加上x格子到y格子之间所有格子的权值之和,包括x和y。注意在k次跳跃的过程中,小可不可以跳出场地。

一开始小可的得分为 0,且任意时刻都要保证小可的得分非负。

请求出小可至多跳跃k次之和,得到的最大得分。

输入描述

第一行:输入一个正整数T,表示多组输入组数。

对于每组数据:

​ 第一行:输入两个正整数 n,k,分别代表格子长度,和小可最多的跳跃次数。

​ 接下来一行:输入n个整数 ,表示格子的权值 a​i​​ 。

输出描述

请求出小可至多跳跃k次之和,得到的最大得分。

输入样例

  1. 2
  2. 3 4
  3. 10 -100 80
  4. 15 400000000
  5. -100 1 2 3 4 5 6 7 8 9 10 11 12 13 14

输出样例

  1. 90
  2. 41999999900

样例解释

第一次跳跃:向右跳。注意到若从 1 跳到 2,则得分变为 0 + (10) + (−100) = −90,为负,不符合题意,同理不能从 1 跳到 3,所以只能从 1 跳 0 步,还是待在 1,分数变为 10

第二次跳跃:向左跳。发现 1 的左边没有格子了,所以还是只能待在 1,分数变 20

第三次跳跃:向右跳。从 1 直接跳到 3,分数变为 10。

第四次跳跃:向左跳。从 3 跳到 3(原地不动),分数变为 90。

数据描述

对于20%的数据:n,k≤10

对于50%的数据:n,k≤1000

对于100%的数据:1≤T≤100,0≤∣a​i​​∣≤10e​5​​,1≤n≤1000,0≤k≤10e​9​​

初次思路

爆搜拿20分

复盘思路

爆搜拿20分,逆天dp看不懂一点

T4

题面

循环排列

时间限制:1秒        内存限制:1024M

题目描述

你有一个长度为n的排列 a​1​​,a​2​​..a​n​​。 你需要对它进行 q 次操作:每次给定 l,r,k,代表把区间a[l,r] 循环右移 k 次。 每次移动过后,你需要回答整个排列是否存在三元上升子序列。 形式化地,即是否存在 i<j<k,使得 a​i​​ <a​j​​< a​k​​。

输入描述

第一行一个整数 n,代表排列长度。

接下来一行 n 个整数,表示序列 a。

接下来一行一个整数 q,代表询问次数。

接下来 q行,每行三个整数 l,r,k,表述如题。

输出描述

共 q 行。每次操作完毕后,若存在三元上升子序列,输出一行 ”YES“,否则输 出一行 ”NO“。

输入样例

  1. 7
  2. 7 5 6 3 4 2 1
  3. 5
  4. 4 5 1
  5. 2 5 2
  6. 2 6 3
  7. 2 7 3
  8. 1 7 4

输出样例

  1. 7
  2. 7 5 6 3 4 2 1
  3. 5
  4. 4 5 1
  5. 2 5 2
  6. 2 6 3
  7. 2 7 3
  8. 1 7 4

数据描述

对于 40% 的数据,n,q≤300。

对于 100% 的数据,1≤n,q≤120000。

保证每次询问的 l,r,k 都满足l≤r≤n,0≤k≤r−l+1。

初次思路

爆搜拿40分

爆搜思路:

1.模拟,更新数组

2.简单dp求最长上升子序列,>=3就YE5

code

#include<bits/stdc++.h>
using namespace std;
int n,q,l,r,k,a[100005],b[100005],c[100005],f[305];
int main(){
	freopen("circulate.in","r",stdin);
	freopen("circulate.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%d%d%d",&l,&r,&k);
		int mod=(r-l+1);
		k%=mod;
		int bruh=0,cruh=0;
		for(int j=l;j<=r-k;j++){
			b[++bruh]=a[j];
		}
		for(int j=r-k+1;j<=r;j++){
			c[++cruh]=a[j];
		}
		for(int j=l;j<=l+cruh-1;j++){
			a[j]=c[j-l+1];
		}
		for(int j=l+cruh;j<=r;j++){
			a[j]=b[j-l-cruh+1];
		}
		int cnt=0,fl=0;
		/*for(int j=1;j<=n;j++){
			cout<<a[j]<<' ';
		}*/
		//cout<<endl;
		for(int j=1;j<=n;j++){
			f[j]=1;
		}
		for(int j=1;j<=n;j++){
			for(int o=j-1;o>=1;o--){
				if(a[o]<a[j]&&f[o]+1>f[j]){
					f[j]=f[o]+1;
				}
			}
			if(f[j]>=3){
				fl=1;
				break;
			}
			//cout<<f[j]<<' ';
		}
		//cout<<endl;
		if(fl==1){
			printf("YES\n");
		}
		else{
			printf("NO\n");
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}
/*效率是代码的事,优雅是一辈子的事w~
7
7 5 6 3 4 2 1
5
4 5 1
2 5 2
2 6 3
2 7 3
1 7 4*/

复盘思路

爆搜拿40分就够了,要什么自行车

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值