集训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\) 的整数幂元素的个数,那么这个数所在的区间为

\[2^{n+1}\le2*(k+n)\le2^{n+2} \]

那么通过二分查找 \(n\) 的值即可得到解

\[2^{n+1}\le 2*(k+n)\implies2^n-n\le k \]

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

posted @   才瓯  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示