代码改变世界

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

2026-01-01 17:33  tlnshuju  阅读(0)  评论(0)    收藏  举报

A 过马路

  • 难度 入门

思路,

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

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

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

−1-11000111

000111000

111000−1-11

000−1-11000

其余的情况均输出 NONONO

参考代码

#include
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+1nn+1

其次分类讨论。

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

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

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

代码参考

#include 
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, 因此需要 longlonglonglonglonglong

思路

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

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

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

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

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

参考代码

#include
#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<=need){
			ans+=ceil(need*1.0/e[i].a);//向上取整
			need=0;
			break;
		}
		else{
			ans+=e[i].b;
			need-=s;
		}
	}
	cout<

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
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<

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(py+n)modn

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

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

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

1.不用技能 (m=0)

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

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

因为

  • 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
using namespace std;
int n,k,a,b,x,y;
int dist[200010];
const int inf=0x3f3f3f3f;
int bfs(int start,int target){
	queue 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<

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_ififi+Rjf_{i+R_j}fi+Rj 处减去 fif_ifi,边求前缀和边转移即可。

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

参考代码

#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;
    }