潍坊一中第四届编程挑战赛(初赛)题解

A 过马路

  • 难度 入门

思路,

根据题意,红灯之后是绿灯,绿灯之后是红灯,不允许连续出现相同颜色的灯,且每次在红绿间切换的间隔都要以黄灯为警示。

因此,灯切换的规律只能是...红 (−1)(-1)(−1) --- 黄 (0)(0)(0) ---绿 (1)(1)(1) ---黄 (0)(0)(0) ---红 (−1)(-1)(−1) ---黄 (0)(0)(0) ---绿 (1)(1)(1)...循环往复。

只有三个灯的情况下,可以直接枚举所有合法序列(输出 YESYESYES):

−1-1−1 000 111

000 111 000

111 000 −1-1−1

000 −1-1−1 000

其余的情况均输出 NONONO。

参考代码

复制代码
#include<bits/stdc++.h> 
using namespace std;
int main(){
    int a,b,c;
    cin>>a>>b>>c;
    if(a==-1&&b==0&&c==1){
        cout<<"YES";
    }
    else if(a==0&&b==-1&&c==0){
        cout<<"YES";
    }
    else if(a==0&&b==1&&c==0){
        cout<<"YES";
    }
    else if(a==1&&b==0&&c==-1){
        cout<<"YES";
    }
    else{
        cout<<"NO";
    } 
    return 0;
}

B 葱油饼

  • 难度 入门

思路

一道数学和分类讨论题。

由于他自己也要一份,将 n←n+1n←n+1n←n+1。

其次分类讨论。

当 n=1n =1n=1 时,不用切,输出 000。

如果是偶数,因为每一刀可以直接穿过整个披萨,输出 n/2n/2n/2。

如果是奇数,因为每一刀只能切一半,输出 nnn。

代码参考

复制代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
    long long n;
    cin >> n;
    n++;
    if (n == 1)
    {
        cout << 0;
        return 0;
    }
    if (n % 2 == 0)
        n /= 2;
    cout << n;
}

C 零食大作战

  • 难度 普及-

问题分析

有:

  • nnn 个吃货,食量为 aia_iai
  • 每个吃货最多可以被模仿 bib_ibi 次
  • 模仿者的食量与被模仿者相同
  • 需要吃掉的目标总量至少 t
  • 输出最少需要的救援队员(模仿者)数量

注意:

  • ttt 很大,可能到 101810^{18}1018, 因此需要 longlonglong longlonglong

思路

1.先判断是否需要救援:如果吃货的总食量已经达到 ttt ,输出 000。

2.判断可行性:如果所有吃货加上所有的救援队员都达不到 ttt ,输出 −1-1−1 。

3.其他情况,也就是要用最少的救援队员来填补 t−t-t− 吃货总食量 的缺口 needneedneed 。

用到贪心策略:为了用最少的人数,优先选择食量大的吃货来模仿,因为一个大食量的救援队员可以顶多个小食量的救援队员。

对每个吃货按照食量大小排序,对每个吃货,计算他的模仿者能提供的总食量。如果能一次性满足需求,就只加上需要的模仿者数量,否则,需要使用这个吃货对应的全部模仿者,将他们的食量加入已填补量,继续考虑下一个食量稍小的吃货。

参考代码

复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,t;
int total; 
int max_total;//吃货加上救援队员最大能提供的总食量 
struct node{
	int a;//食量
	int b;//模仿次数 
}e[200010];
bool cmp(node x,node y){
	return x.a>y.a;
}
signed main(){
	cin>>n>>t;
	for(int i=1;i<=n;i++){
		cin>>e[i].a;
		total+=e[i].a;//计算吃货总食量 
	}
	max_total+=total;
	for(int i=1;i<=n;i++){
		cin>>e[i].b;
		max_total+=e[i].a*e[i].b;
	}
	if(total>=t){//如果不需要救援队员 
		cout<<0<<endl;
		return 0;
	}
	if(max_total<t){//如果最大也达不到目标 
		cout<<-1<<endl;
		return 0;
	} 
	sort(e+1,e+1+n,cmp);//按食量从大到小排序
	int need=t-total;//还需要多少食量 
	int ans=0;//需要的救援队员的数量 
	//贪心,先模仿食量大的吃货,才能使用的人数最少 
	for(int i=1;i<=n;i++){
		if(need<=0)break;
		if(e[i].a==0)continue;
		int s=e[i].a*e[i].b;
		if(s>=need){
			ans+=ceil(need*1.0/e[i].a);//向上取整
			need=0;
			break; 
		}
		else{
			ans+=e[i].b;
			need-=s;
		}
	}
	cout<<ans;
	return 0;
}

D 幸运串

  • 难度 普及

思路

幸运串的定义是:count(count(count("01") + count(count(count("10") = 3。

在01串中,count(count(count("01") + count(count(count("10") 实际上等于相邻字符不同的位置数。

比如:

  • "0000":没有相邻不同,和为0
  • "0101":位置(0,1)、(1,2)、(2,3)都不同,和为3
  • "0011":只有位置(1,2)不同,和为1

所以问题转化为:将原串变成恰好有3个相邻字符不同的位置的最少操作次数

那么一个长度为n的01串,有恰好3个相邻字符不同的位置,这个字符串将被分成4段,每段内部字符相同,相邻段字符不同。有两个可能:

0...01...10...01...1

1...10...01...10...0

考虑枚举三个分割点的位置,对分割后形成的4段,计算将其全变成0或全变成1需要的修改次数。

利用前缀和:

复制代码
int sum0[505];//前i个字符里0的个数
int sum1[505];//前i个字符里1的个数

参考代码

复制代码
#include<bits/stdc++.h>
using namespace std;
int n;
string s;
int ans=INT_MAX;
int sum0[505];//前缀和,前i个字符里0的个数
int sum1[505];//前缀和,前i个字符里1的个数
int main(){
	cin>>n>>s;
	for(int i=1;i<=n;i++){
		sum0[i]=sum0[i-1]+(s[i-1]=='0'?1:0);
		sum1[i]=sum1[i-1]+(s[i-1]=='1'?1:0);
	} 
	//枚举3个分割点位置,分成4段,0101或1010 
	//(0,i],(i,j],(j,k],(k,n]
	for(int i=1;i<=n-3;i++){
		for(int j=i+1;j<=n-2;j++){
			for(int k=j+1;k<=n-1;k++){
				//情况1:0101
				int cnt1=0;
				cnt1+=sum1[i]-sum1[0];
				cnt1+=sum0[j]-sum0[i]; 
				cnt1+=sum1[k]-sum1[j];
				cnt1+=sum0[n]-sum0[k];
				//情况2:1010 
				int cnt2=0;
				cnt2+=sum0[i]-sum0[0];
				cnt2+=sum1[j]-sum1[i]; 
				cnt2+=sum0[k]-sum0[j];
				cnt2+=sum1[n]-sum1[k];
				ans=min(ans,min(cnt1,cnt2));
			}
			
		}
	} 
	cout<<ans;
	return 0; 
} 

E 太阳系

  • 难度 普及

思路

题目要求从 aaa 行星移动到 bbb 行星的最少时间,考虑bfs。

长度为 nnn 的环,那么每个点顺时针编号 000~nnn,使取模时不会出错。

三种移动方式:

1.顺时针移动 xxx ,即从点 ppp 可以到点 (p+x)mod  n(p+x)\mod n(p+x)modn 。

2.逆时针移动 yyy ,即从点 ppp 可以到点 (p−y+n)mod  n(p-y+n)\mod n(p−y+n)modn 。

关键在于第3种移动方式,因为环的长度是 nnn,nnn保证是偶数,跳到对面的位置就是当前位置加上 n/2(取模 n)。

两次跳到"对面"等于回到原点,因此,用偶数次技能等于没用,用奇数次技能相当于只用一次。

那么,只有两种情况有意义:

1.不用技能 (m=0)

2.用一次技能(m=1),前提是 k≥1k≥1k≥1。

为什么不用考虑 m≥2m≥2m≥2的情况?

因为

  • m = 2 和 m = 0 效果一样,但多花了 2 步时间,显然不会是最优解
  • m = 3 和 m = 1 效果一样,但多花了 2 步时间,也不会最优

所以真正要计算的就是两种情况:

1.不用技能,只用 +x 和 -y两种操作从a到b。------用bfs求解,计答案为 ans1ans1ans1。

2.用一次技能跳到对面(耗时1),再用 +x 和 -y两种操作从对面位置到b。------用bfs求解,计答案为 ans2ans2ans2。

取min(ans1,ans2+1)min(ans1,ans2+1)min(ans1,ans2+1)即为答案。

参考代码

复制代码
#include<bits/stdc++.h>
using namespace std;
int n,k,a,b,x,y;
int dist[200010];
const int inf=0x3f3f3f3f;
int bfs(int start,int target){
	queue<int> q;
	memset(dist,-1,sizeof(dist));
	dist[start]=0;
	q.push(start);
    
	while(!q.empty()){
		int f=q.front();
        q.pop();
		if(f==target){
			return dist[target];
		}
		int nxt=(f+x)%n;
		if(dist[nxt]==-1){
			dist[nxt]=dist[f]+1;
			q.push(nxt);
		}
		nxt=(f-y+n)%n;
		if(dist[nxt]==-1){
			dist[nxt]=dist[f]+1;
			q.push(nxt);
		}
	}
	return inf; 
}
int main(){
	cin>>n>>k>>a>>b>>x>>y;
	a=a-1;
	b=b-1;
	int ans1=bfs(a,b);
	int ans2=bfs((a+n/2+n)%n,b);
	if(k==0){
		if(ans1==inf)cout<<-1;
		else cout<<ans1;
	}
	else{
		if(ans1==inf&&ans2==inf)cout<<-1;
		else cout<<min(ans1,ans2+1);
	} 

	return 0;
	
} 

F 空间站

  • 难度 普及+

思路

考虑动态规划:设 fif_ifi 表示恰好到达第 iii 个点的方案数。

先将所有的 Li,RiL_i,R_iLi,Ri 进行区间合并,重复的部分只留一个即可。

考虑从位置 iii 可以到达哪些位置。我们可以枚举 mmm 个区间,对于每个区间 [Lj,Rj][L_j,R_j][Lj,Rj],我们可以从 iii 到 \[i+L_j,i+R_j\]。所以在转移时,。所以在转移时,。所以在转移时,f_{i+L_j},f_{i+L_j+1},\\cdots,f_{i+R_j} 都要加上 fif_ifi。

暴力加法时间复杂度为 O(n2m)O(n^2m)O(n2m),需要优化。由于区间加是从前往后依次处理,不需要动态查询,因此可以使用差分。只需在 fi+Ljf_{i+L_j}fi+Lj 处加上 fif_ifi,fi+Rjf_{i+R_j}fi+Rj 处减去 fif_ifi,边求前缀和边转移即可。

时间复杂度 O(nm)O(nm)O(nm),空间复杂度 O(n)O(n)O(n)。

参考代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,mod=998244353;
int n,m,dp[N];
struct node{
    int l,r;
    bool operator<(const node&a)const{
        return l<a.l;
    }
}nd[210],nd2[210];
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++)cin>>nd[i].l>>nd[i].r;
    //区间合并,变成cnt个新区间
    sort(nd+1,nd+m+1);
    int cnt=0,ed=-1;
    for(int i=1;i<=m;i++){
        if(ed<nd[i].l){
            nd2[cnt].r=ed;
            nd2[++cnt].l=nd[i].l;
            ed=nd[i].r;
        }
        else ed=max(nd[i].r,ed);
    }
    nd2[cnt].r=ed;
    
    //dp求解,差分优化
    dp[0]=1;dp[1]=-1;
    for(int i=0;i<=n;i++){
        if(i>0)dp[i]=(dp[i]+dp[i-1])%mod;
        for(int j=1;j<=cnt;j++){
            int l=i+nd2[j].l,r=i+nd2[j].r;
            if(l>n)continue;
            if(r>n)r=n;
            dp[l]=(dp[l]+dp[i])%mod;
            dp[r+1]=((dp[r+1]-dp[i])%mod+mod)%mod;
        }
    }
    cout<<dp[n];
    return 0;
}
相关推荐
松涛和鸣1 小时前
25、数据结构:树与二叉树的概念、特性及递归实现
linux·开发语言·网络·数据结构·算法
Han.miracle1 小时前
数据结构--初始数据结构
算法·集合·大o表示法
List<String> error_P2 小时前
C语言联合体:内存共享的妙用
算法·联合体
little~钰2 小时前
可持久化线段树和标记永久化
算法
獭.獭.2 小时前
C++ -- 二叉搜索树
数据结构·c++·算法·二叉搜索树
TOYOAUTOMATON2 小时前
自动化工业夹爪
大数据·人工智能·算法·目标检测·机器人
im_AMBER2 小时前
Leetcode 67 长度为 K 子数组中的最大和 | 可获得的最大点数
数据结构·笔记·学习·算法·leetcode
feifeigo1233 小时前
MATLAB实现两组点云ICP配准
开发语言·算法·matlab
fengfuyao9853 小时前
粒子群算法(PSO)求解标准VRP问题的MATLAB实现
开发语言·算法·matlab