专栏文章
2025 年 10 月 20 日 J 组 CJ 模拟赛总结 & 题解
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minjzjsr
- 此快照首次捕获于
- 2025/12/02 03:39 3 个月前
- 此快照最后确认于
- 2025/12/02 03:39 3 个月前
原:24 年集训第十一场。
| A | B | C | D | 总分 | |
|---|---|---|---|---|---|
| 分数 | |||||
| 评价 | 特别简单,不评价 | 思路很容易想,但是我为什么没有想到啊 | 结论题 | 倍增好题 |
很难,但也没有那么难。
补题链接。
A:卡片
题意简述
有 个 , 个 , 个 ,从中选出若干张卡片,使得综合为 。求方案数。
正解思路
我们可以枚举使用 张 , 张 。如果 ,那么这就是一个合法的方案,答案 。
赛时
简单题,直接切了。
B:分组
题意简述
有一个长度为 的数组 ,每次找到最大值,把 里面的数取出来。令这是第 个被取出来的段,如果 是奇数,把一段全部编号为 ,否则编号为 。最后求所有的编号。
正解思路
看到区间删除,很容易想到用链表维护这整个东西。每次最大值,可以用一个
pair,它的 first 存原来的值,second 存下标。我们用一个优先队列存这个玩意。如果当前这个地方的答案非零,直接 continue。然后,我们暴力往两边跳 步,直到是最左边或最右边为止(或者跳完 步了)。然后,使用链表 把这个区间内所有点赋值为当前的编号。由于每个点只会被访问一次,所以时间复杂度 ,瓶颈在于优先队列。赛时
只写了 的部分分,想正解没有想到用链表,甚至想到树状数组去了。
AC 代码:
CPP#include <iostream>
#include <cstdio>
#include <queue>
#include <utility>
#include <algorithm>
#define endl '\n'
using namespace std;
struct node{
int prv,nxt;
}lb[6000005];//链表
int ans[6000005];
int n,m;
pair<int,int> aa[6000005];//卡常
queue<pair<int,int> > q;//正常的有限队列,但是因为aa排过序了,所以直接用普通队列就行
signed main()
{
freopen("team.in","r",stdin);
freopen("team.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i = 1;i<=n;i++){
lb[i].prv = i-1;
lb[i].nxt = i+1;
int a;
cin>>a;
aa[i] = {a,i};
}
sort(aa+1,aa+1+n,greater<pair<int,int> >() );
for(int i = 1;i<=n;i++){
q.push(aa[i]);
}
lb[n].nxt = 0;
lb[0].nxt = 1;
int now = 1;
#define top front
while(!q.empty()){
pair<int,int> f = q.top();
q.pop();
if(ans[f.second]) continue;//如果这个地方被找过了
int l = f.second,r = f.second;
for(int i = 1;i<=m&&lb[l].prv!=0;i++){//暴力向左找
l = lb[l].prv;
}
for(int i = 1;i<=m&&lb[r].nxt!=0;i++){//暴力向右找
r = lb[r].nxt;
}
// cout<<l<<' '<<r<<' '<<f.second<<endl;
for(int i = l;i<=r;i = lb[i].nxt){//染色
ans[i] = now;
if(lb[i].nxt==0) break;
}
// for(int i = l;i<=r;i++){
// ans[i] = now;
// // lb[i].nxt = lb[i].prv = 0;
// }
now = 3-now;
lb[lb[l].prv].nxt = lb[r].nxt;
lb[lb[r].nxt].prv = lb[l].prv;//删除
while(!q.empty()&&ans[q.top().second]) q.pop();//把已经染色的点删掉,卡常
// cout<<"lb[]:"<<endl;
// for(int j = 1;j<=n;j++){
// cout<<lb[j].prv<<' '<<lb[j].nxt<<endl;
// }
// cout<<endl;
// nxt[pre[l]]=nxt[r];
// pre[nxt[r]]=pre[l];
// while(!q.empty()&&vis[q.top().second])q.pop();
}
for(int i = 1;i<=n;i++){
cout<<ans[i];
}
return 0;
}
C:活动
题意简述
有 个线段,每个线段的左端点是 ,右端点是 (其实是给你 和 ,)。你最多可以移动一个线段到任意位置。求怎么移动才能使得仅被一个线段覆盖的地方尽量多。输出这个值。
正解思路
我们先定义一个函数:
不难看出,这就是求第 个线段不被 和 这两个线段覆盖的长度。
先求出不进行操作的答案,设这个答案为 ,显然:
当然要特殊处理一下 这里。可以直接把 。
然后,我们枚举移动的是 这条线段。显然,我们可以把这个线段移动到很远的地方,使得这 个地方都不会有其他线段挡住。那么,原来这个线段对 做出的贡献都需要减掉,再加上移除这个线段之后剩下的线段新对答案增加了哪些贡献。用数学式子表达,答案就是
时间复杂度线性。
赛时
没推出这个式子,导致只能拿 的 分。以后需要多尝试,不能只拿暴力分。
AC 代码:
CPP#include <iostream>
#include <cstdio>
#include <utility>
#include <algorithm>
#define int long long
#define endl '\n'
using namespace std;
int n,m;
pair<int,int> t[200005];
int f(int i,int j,int k){
if(j<1||j>n) return 0;
return max(0ll,m-max(0ll,t[i].second-t[j].first+1)-max(0ll,t[j].second-t[k].first+1));
}
signed main()
{
freopen("activity.in","r",stdin);
freopen("activity.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i = 1;i<=n;i++){
cin>>t[i].first;
t[i].second = t[i].first+m-1;
}
sort(t+1,t+1+n);
int now = 0;
t[n+1].first = t[n+1].second = t[n].second+1;
for(int i = 1;i<=n;i++){
// now+=max(0ll,m-max(0ll,t[i-1].second-t[i].first+1)-max(0ll,t[i].second-t[i+1].first+1));
now+=f(i-1,i,i+1);
// cout<<i<<' '<<now<<endl;
}//求now
// cout<<now<<endl;
int ans = now;
for(int i = 2;i<=n;i++){
ans = max(ans,now+m-f(i-2,i-1,i)-f(i-1,i,i+1)-f(i,i+1,i+2)+f(i-2,i-1,i+1)+f(i-1,i+1,i+2));
// ans = max(ans,m+now-f(i-1,i,i+1)+f(i-2,i-1,i+1));
// ans = max(ans,m+now-max(0ll,m-max(0ll,t[i-1].second-t[i].first+1)-max(0ll,t[i].second-t[i+1].first+1))+max(0ll,m-max(0ll,t[i-2].second-t[i-1].first+1)-max(0ll,t[i-1].second-t[i+1].first+1)));
}//求最终答案
cout<<ans;
return 0;
}
D:卡牌游戏
题意简述
有一个长度为 的数组 。每次从给定的 到 跳跃。如果当前的值是 ,且后面有一个地方 ,使得 ,那么可以直接传送到那里的后一个地方,且不花费任何代价,否则花费 的代价,往右移动一格。如果有多处满足,只能传送到最近的一个。求最小代价。 组数据,对于每组数据, 组查询 和 。
正解思路
我们先算出对于 它能够传送到哪里,设这个地方为 。定义 表示从 开始 仅通过传送 跳 能跳到的地方。如果 有意义,,否则 。
通过倍增的思想,我们很容易得到
然后对于每次询问,如果能跳,我们必定跳,具体跳多少可以通过倍增解决,注意:跳完之后还需要 到下一格。否则,答案 ,往后跳一格。
时间复杂度 ,瓶颈在于倍增。
赛时
打了个暴力走人,没想到 分就是在正解上面不用倍增用暴力跳就行。
AC 代码
CPP#include <iostream>
#include <cstdio>
#include <map>
// #define int long long
#define endl '\n'
using namespace std;
namespace IO{
char buff[1<<21],*p1=buff,*p2=buff;
char getch(){
return p1==p2&&(p2=((p1=buff)+fread(buff,1,1<<21,stdin)),p1==p2)?EOF:*p1++;
}
template<typename T>
void read(T &x){
char ch=getch();int fl=1;x=0;
while(ch>'9'||ch<'0'){if(ch=='-')fl=-1;ch=getch();}
while(ch<='9'&&ch>='0'){x=x*10+ch-48;ch=getch();}
x*=fl;
}
template<typename T,typename ...Args>
void read(T &x,Args& ...args){
read(x);read(args...);
}
char obuf[1<<21],*p3=obuf;
void putch(char ch){
if(p3-obuf<(1<<21))*p3++=ch;
else fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=ch;
}
void pc(const char *s){
while(*s)putch(*s),s++;
}
char ch[100];
template<typename T>
void write(T x){
if(!x)return putch('0');
if(x<0)putch('-'),x*=-1;
int top=0;
while(x)ch[++top]=x%10+48,x/=10;
while(top)putch(ch[top]),top--;
}
template<typename T,typename ...Args>
void write(T x,Args ...args){
write(x),putch(' '),write(args...);
}
void flush(){fwrite(obuf,p3-obuf,1,stdout);}
}
using namespace IO;//上面联考同款快读快写
int lst[15];
int nxt[500005];
int n;
int a[500005];
int f[35][500005];//把小的一位放前面,卡常
int lg[500005];
signed main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _;
// cin>>_;
read(_);
for(int i = 2;i<=(int)(5e5);i++){
lg[i] = lg[i>>1]+1;
}
while(_--){
// cin>>n;
read(n);
for(int i = 1;i<=13;i++) lst[i] = 0;
for(int j = 0;j<=lg[n];j++){
for(int i = 1;i<=n;i++){
f[j][i] = n+1;//初始化
}
}
for(int i = 1;i<=n;i++){
// cin>>a[i];
read(a[i]);
nxt[i] = 0;
}
for(int i = n;i;i--){
nxt[i] = lst[a[i]];
// mp[a[i]] = i;
lst[a[i]] = i;
if(nxt[i]!=0){
f[0][i] = nxt[i];
}
// cout<<"f["<<i<<"][0]="<<f[i][0]<<endl;
}
for(int j = 1;j<=lg[n];j++){
for(int i = 1;i+(1<<j)-1<=n;i++){//倍增转移
if(f[j-1][i]<n)
f[j][i] = f[j-1][f[j-1][i]+1];
// cout<<"f["<<i<<"]["<<j<<"]="<<f[i][j]<<endl;
}
}
// return 0;
int q;
// cin>>q;
read(q);
while(q--){
int l,r;
// cin>>l>>r;
read(l,r);
int ans = 0;
while(l<=r){
if(f[0][l]>r){
ans++;
l++;
}else{
for(int i = lg[n];i>=0;i--){//找能够跳多少
if(f[i][l]<=r){
// return 0;
l = f[i][l]+1;
break;
}
}
}
// if(nxt[l]!=0&&nxt[l]<=r){
// l = nxt[l]+1;
// }else l++,ans++;
}
// cout<<ans<<endl;
write(ans);
putch('\n');
}
}
flush();
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...