集训6 20250213
集训6 20250213
牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
A:
题目大意:给定一个非空数组 a
,可以选择任意次 a
的一个连续子数组,将这个子数组的每个数都复制一份在原数后面,现给出操作后的数组 b
判断原数组至少包含几个元素
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; void solve(void){ int n; cin>>n; vector<int> b(n+5); for (int i=1;i<=n;i++) cin>>b[i]; int ans=0; for (int i=1;i<=n;i++){ if (b[i]!=b[i-1]) ans++; } cout<<ans<<endl; } int main() { int T; cin>>T; while (T--) solve(); return 0; }
陷阱题,找连续字符子段的个数即可
K:
题目大意:
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; void solve(void){ int x,y; cin>>x>>y; int mid=y/2; if (y%2==0){ cout<<"NO"<<endl; return; } if (mid%2==x%2) cout<<"YES"<<endl; else cout<<"NO"<<endl; return; } int main() { int T; cin>>T; while (T--) solve(); return 0; }
当且仅当 \(y\) 为奇数时可以被表示为 \(n+(n+1)\) 符合两个连续页码相加
并且 \(n\) 与 \(x\) 的奇偶性相同时满足题意,因为左页与给出 \(x\) 奇偶性相同,这里的 \(n\) 即为摊开的左页页码
L:
题目大意:给出一个字符串,每次操作可以删去两个不同的字符,问经过任意次操作后能否将字符串变为 CHICKEN
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; void solve(void){ int n; cin>>n; vector<char> s(n+5); map<char,int> mp; string t="CHICKEN"; int pos=0; for (int i=0;i<n;i++){ cin>>s[i]; mp[s[i]]++; if (s[i]==t[pos]) pos++; } if (pos!=7){ cout<<"NO"<<endl; return; } int sum=0,maxm=0; for (auto [key,val]:mp){ int cnt=0; if (key=='C') cnt=val-2; else if (key=='H'||key=='I'||key=='K'||key=='E'||key=='N') cnt=val-1; else cnt=val; maxm=max(maxm,cnt); sum+=cnt; } if (maxm>sum-maxm){ cout<<"NO"<<endl; return; } if (sum%2==0) cout<<"YES"<<endl; else cout<<"NO"<<endl; return; } int main() { int T; cin>>T; while (T--) solve(); return 0; }
比较字符串CHICKEN
,判断原字符串中是否能被变为 CHICKEN
,用 map
存不同的字符个数
因为只能一次消除两个不同的字符,所以需要判断 maxm>sum-maxm
当字符串中最多的字符比其他字符还要多时,那么消除字符时必然会剩下相同的字符无法消去(用其他的字符消去最多的字符)
并且仅当除开 CHICKEN
剩下的字符为偶数个时才能两两消去,注意减去 CHICKEN
的字符贡献部分
C:
题目大意:数组 a
满足 \(a_i=2\times i-2\) ,求出了这个数组所有长度大于 \(1\) 的连续子数列的和,去重后按从小到大排列,找到第 \(k\) 个数
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; long long k; bool judge(long long x){ if (pow(2,x)-x>=k) return 0; else return 1; } void solve(void){ cin>>k; long long l=0,r=64; while (l+1!=r){ long long mid=l+r>>1; if (judge(mid)) l=mid; else r=mid; } cout<<2*k+2*l<<endl; } int main() { cintie; int T; cin>>T; while (T--) solve(); return 0; }
数学题目解不等式,最终没有找到解析解,用二分搞的答案
这个数组经过手推后可以发现,是 2,6,10,12,14,18,20...
,可以发现这个数组包含了所有偶数除开 \(2\) 的整数幂与 \(0\) 但包括 \(2\)
考虑第 \(k\) 项可以被表示为 \(2*(k+n)\) ,其中 \(n\) 为第 \(k\) 项前被删去的 \(2\) 的整数幂元素的个数,那么这个数所在的区间为
那么通过二分查找 \(n\) 的值即可得到解
I:
题目大意:
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define lc(x) tr[x].ch[0] #define rc(x) tr[x].ch[1] using namespace std; const int N=3e5+5; struct node{ int ch[2]; int s; }; node tr[N*12]; int n,m,a[N]; int root[N],idx; void build(int &x,int l,int r){ x=++idx; if (l==r) return; int m=l+r>>1; build(lc(x),l,m); build(rc(x),m+1,r); } void insert(int x,int &y,int l,int r,int v){ y=++idx; tr[y]=tr[x]; tr[y].s++; if (l==r) return; int m=l+r>>1; if (v<=m) insert(lc(x),lc(y),l,m,v); else insert(rc(x),rc(y),m+1,r,v); } int rankquery(int x,int y,int l,int r,int k){ if (r<=k) return tr[y].s-tr[x].s; if (l>k) return 0; int m=l+r>>1; return rankquery(lc(x),lc(y),l,m,k)+rankquery(rc(x),rc(y),m+1,r,k); } void solve(void){ idx=0; cin>>n>>m; build(root[0],1,n); for (int i=1;i<=n;i++) cin>>a[i]; for (int i=1;i<=n;i++) insert(root[i-1],root[i],1,n,a[i]); while (m--){ int l,r,c; cin>>l>>r>>c; cout<<rankquery(root[l-1],root[r],1,n,a[c])+l-1<<endl; } } int main() { cintie; int T; cin>>T; while (T--) solve(); return 0; }
一开始打暴力,TLE了,然后改写主席树,空间卡了两发
实际上没有考察这么重的数据结构,通过树状数组可以简单地解答
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; struct que{ int l,r,c,id; }; int n,m; int a[300010]; int pos[300010]; int s[300010]; que q[300010]; int lowbit(int x){ return x&-x; } void change(int x,int k){ while (x<=n){ s[x]+=k; x+=lowbit(x); } } int query(int x){ int t=0; while (x){ t+=s[x]; x-=lowbit(x); } return t; } void solve(void){ memset(s,0,sizeof s); cin>>n>>m; for (int i=1;i<=n;i++){ cin>>a[i]; pos[a[i]]=i;//记录每个元素的位置 } for (int i=1;i<=m;i++){ int c; cin>>q[i].l>>q[i].r>>c; q[i].id=i; q[i].c=a[c]; } sort(q+1,q+m+1,[](que x,que y){return x.c<y.c;});//按照a[c] 进行排序 vector<int> ans(m+10); int cnt=1; for (int i=1;i<=m;i++){ while (cnt<q[i].c){ change(pos[cnt],1);//在cnt位置上加1 cnt++; } int sum=query(q[i].r)-query(q[i].l-1); ans[q[i].id]=q[i].l+sum;//维护答案,保持顺序 } for (int i=1;i<=m;i++) cout<<ans[i]<<endl; } int main(){ cintie; int T; cin>>T; while (T--) solve(); return 0; }
离线维护树状数组,每次询问给出的 l,r,c
其实是在问在 \([l,r]\) 这个区间上小于等于 \(a_c\) 的元素的个数
struct que{ int l,r,c,id; };//id用来记录这是第几个询问
于是就可以离线维护一个全 \(0\) 的树状数组,按照 \(a_c\) 的顺序给在 \(a_c\) 位置上的数依次加 \(1\) ,最后对答案排序后输出
例:a=1,4,2,3,5
,q={3,5,4},{1,3,2},{1,5,4}
对 q
按照 \(a_c\) 进行排序后是 q={3,5,4},{1,5,4},{1,3,2}
while (cnt<q[i].c){ change(pos[cnt],1); cnt++; }
于是树状数组的状态为
1,0,0,0,0 -> 1,0,1,0,0 -> 1,0,1,1,0 -> 1,1,1,1,0 -> 1,1,1,1,1
查询 {3,5,4},{1,5,4}
就是求树状数组在状态 \(3\) 的区间和,查询 {1,3,2}
就是求树状数组在状态 \(4\) 的区间和
J:
题目大意:
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define Trd int T;cin>>T;while (T--)solve(); using namespace std; void solve(void) { long long n,x,y; cin>>n>>x>>y; long long ans=0; for (int t=1;t<=min(n,y);t++){ long long sum=0; long long a=x+t,s1=min(n,y)-t; sum+=a*s1; long long s2=n-min(n,y); if (a-s2>=0){ sum+=(a+a-s2*(s2+1)/2; }else sum+=a*(a+1)/2; ans=max(ans,sum); } cout<<ans<<endl; } int main() { Trd; return 0; }
考察贪心,先磨刀一定不会劣,过程可以分为三个阶段
- 只磨刀
- 边磨边打
- 只打
考虑边磨边打阶段,这一段时间内的攻击力不会下降,考虑只打阶段,攻击力成等差数列下降
所以可以枚举在哪个时间开始打
for (int t=1;t<=min(n,y);t++){//枚举时间节点 long long sum=0; long long a=x+t,s1=min(n,y)-t;//a记录磨完刀后的攻击力,s1计算边磨边打的时间 sum+=a*s1;//计算边磨边打的伤害 long long s2=n-min(n,y);//计算只打的时间 if (a-s2>=0)//如果攻击力下降到0就不能下降了 sum+=(a+a-s2*(s2+1)/2;//计算没有下降到0的总伤害 else sum+=a*(a+1)/2;//计算下降到0的总伤害 ans=max(ans,sum);//记录枚举过程中的最大值 }
B:
题目大意:
#include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define Trd int T;cin>>T;while (T--)solve(); using namespace std; void solve(void){ long long n,c1,c2; cin>>n>>c1>>c2; pair<int,int> p[n+10]; for (long long i=1;i<=n;i++) cin>>p[i].first>>p[i].second; long long dp[1010][2]; memset(dp,0x3f,sizeof dp); dp[0][0]=dp[0][1]=0; long long ans=1e18; for (long long i=1;i<=n;i++){ for (long long j=0;j<i;j++){ if (p[i].first>=p[j].first&&p[i].second>=p[j].second){ dp[i][0]=min(dp[i][0],dp[j][0]+(i-j-1)*c1); } if (p[i].first>=p[j].second&&p[i].second>=p[j].first){ dp[i][1]=min(dp[i][1],c2+dp[j][0]+(i-j-1)*c1); } if (p[i].first>=p[j].second&&p[i].second>=p[j].first){ dp[i][0]=min(dp[i][0],dp[j][1]+(i-j-1)*c1); } if (p[i].first>=p[j].first&&p[i].second>=p[j].second){ dp[i][1]=min(dp[i][1],c2+dp[j][1]+(i-j-1)*c1); } } ans=min(ans,min(dp[i][0],dp[i][1])+(n-i)*c1); } cout<<ans<<endl; } int main() { Trd; return 0; }
dp签到题,写不来
需要二维数组 \(dp_{x,y}\) ,\(x\) 表示转移到了第几个数对,\(y\) 表示当前数对翻转的状态
类似于最长上升子序列的问题,考虑 \((a,b)_i\) 组时,需要从前面的状态得出,用 \(j\) 指针扫描前面的 \((a,b)_j\)
存在四种情况,判断更新删去 \(i,j\) 之间数对的代价 \((i-j-1)*c_1\)
-
\((a,b)_j\) 不翻转,且 \((a,b)_i\) 合法,由 \(dp_{j,0}\) 转移得到
-
\((a,b)_j\) 不翻转,且 \((a,b)_i\) 翻转后合法,由 \(dp_{j,0}+c_2\) 转移得到
-
\((a,b)_j\) 翻转,且 \((a,b)_i\) 合法,由 \(dp_{j,1}\) 转移得到
-
\((a,b)_j\) 翻转,且 \((a,b)_i\) 翻转后合法,由 \(dp_{j,1}+c_2\) 转移得到
ans=min(ans,min(dp[i][0],dp[i][1])+(n-i)*c1);
记录答案,更新最小代价
表达式为 当前转移到的数对的代价+将后面全部删去的代价总和
注意,需要初始化 \(dp\) 数组极大值,且 ans
也需要极大值(\(1e9\) 也会小),开 long long
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具