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