OI 笑传 #28

今早上才知道 sakanaction 又要上红白了,12 年了你干的好啊/dk

今天是洛谷 NOIP2025 模拟,研究了下前两题,后两题暴力跑路了。

赛时 \(100+24+25+12\),于是:

Luogu P14507 缺零分治

非常有 CCF 风味的分讨细节贪心题,放 T1 感觉太对了。

首先找到从 \(0\) 开始的连续自然数,后面的显然放在哪个集合都不会影响直接不管。

由于要让多重集最小于是我们先尽可能大 mex 的构造集合,就是取前面最小值然后顺次记录下来,放进数组里。显然大的 mex 可以变成小的 mex,于是保证我们后面还能调整。

之后从大 mex 到小 mex 把贡献做下前缀和,对于询问的 \(m\),首先二分找第一个严格大的位置,之后退回来一个进行调整。

调整很简单,差差除除算一下即可。

把两块集合的数量合起来就是答案,其它数都可以放进最大的集合里面。

有一个 corner,如果我们连一个最大的集合都没选出来,这时候我们只能把 \(0\) 都堆进这个集合,其它的数还要单开一个集合放。好在这个 corner 我在测大样例之前就写上了。

然后都选上也凑不到就无解,连 \(0\) 都没有的如果要凑的不是 \(0\) 也无解。

赛时 1h10min 过大样例,没挂分,不像某个 T1 来着。

有些地方要用 int128。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e5+5;
i64 a[N],b[N];
i64 minr[N];
i64 v[N],c[N];
i128 pf[N];
i64 pfcnt[N];
int n,q;
void init(){
  for(int i=1;i<=n;i++){
    a[i]=b[i]=minr[i]=v[i]=c[i]=pf[i]=pfcnt[i]=0;
  }
  return ;
}
void solve(){
  cin>>n>>q;
  int f=0;
  b[0]=0x3f3f3f3f;
  for(int i=1;i<=n;i++){
    a[i]=rd;b[i]=rd;
    if(a[i]==i-1)f=i;
    if(b[minr[i-1]]>=b[i])minr[i]=i;
    else minr[i]=minr[i-1];
  } 
  b[0]=0;
  if(f==0){
    for(int i=1;i<=q;i++){
      i64 m;m=rd;
      if(m==0)cout<<1<<'\n';
      else cout<<-1<<'\n';
    }
    return ;
  }
  int cnt=0;
  int dt=0;
  for(int i=f;i>=1;i--){
    if(b[minr[i]]-dt==0)continue;
    cnt++;
    v[cnt]=i;c[cnt]=b[minr[i]]-dt;
    pf[cnt]=v[cnt]*c[cnt];
    dt=b[minr[i]];
    i=minr[i];
    // cout<<"precised "<<v[cnt]<<' '<<c[cnt]<<'\n';
  }
  for(int i=1;i<=cnt;i++){
    pf[i]=pf[i-1]+pf[i];
    pfcnt[i]=pfcnt[i-1]+c[i];
  }
  for(int i=1;i<=q;i++){
    i64 m;cin>>m;
    if(m==0){cout<<-1<<'\n';continue;}
    int wp=upper_bound(pf+1,pf+1+cnt,m)-pf-1;
    // cout<<"gwp "<<wp<<' '<<pf[wp]<<'\n';
    if(pf[wp]==m){cout<<pfcnt[wp]<<'\n';continue;}
    i128 rm=m-pf[wp];
    i64 ans=pfcnt[wp];
    if(wp==cnt){cout<<-1<<'\n';continue;}
    wp++;
    i64 det=rm/v[wp]+(rm%v[wp]==0?0:1);
    ans+=det; 
    if(wp==1&&det==1&&rm<v[wp])ans++;
    cout<<ans<<'\n';
  }

  return ;
}
int main(){
  
  int T;cin>>T;
  while(T--){
    init();solve();
  }
  
  return 0;
}

Luogu P14508 猜数游戏

被博弈状物吓哭了,写完 T3T4 暴力就想着是个 T2 还是硬着头皮做做吧。。

于是赛时想了个猎奇区间 DP,虽然只有 24pts。

博弈套路都是从终态推初态吧,于是终态一定是我们确认 \([1,n]\) 里面只有 \(p\) 是“包含目标位置”的状态,其它位置都是“不包含目标位置”的状态,于是往前推也就是要把一些区间染成“包含目标位置”的状态。

然后又发现这个棋子可以乱跑很烦,于是把这棋子向左或者向右正好移动 \(k\) 个位置的最小代价,这个 Dij 就行。

我设 \(dp_{i,j}\) 表示将区间 \([i,j]\) 染成“包含目标位置”的状态的用最优方案的最坏情况值,然后我大胆假设棋子染完色之后一定是在端点上的,因为我们乱跑无非就是要凑一个特定的移动距离,这个我们已经处理了。

于是再加一维表示状态 \([i,j]\) 弄完后棋子在 \(i\) 还是 \(j\),用个 \(0/1\),于是 \(dp_{i,j,0/1}\)

于是转移时枚举分界点 \(k\),棋子位置,\(\operatorname{costl},\operatorname{costr}\) 分别是向左走,右走的最小代价:

\[dp_{i,j,0} = \min{\max(dp_{i,k-1,1},dp_{k,j,0})+\operatorname{costr}(k-i+1)} \]

\[dp_{i,j,1} = \min{\max(dp_{i,k-1,1},dp_{k,j,0})+\operatorname{costl}(j-k+1)} \]

答案 \(dp_{1,n,0}\)

这东西不是很好想吧,怎么只有 24pts/fn

下面是正解:

哎你先别急,我看看你理解没有为什么转移式子是个 \(\min \max\) 状物。

原因就是你可以认为原题我们在和一个大手子博弈,这个大手子可以告诉你那个区间是有目标点的,告诉你了你就只能去对应的那里面找对不对,于是大手子肯定会让你去大的那一个,也就是内层的 \(\max\)

但是你可以选择你的棋子要跳多远,那最优策略肯定要最好运的选了走这一步接下来最小的那一个位置,这就是外层的 \(\min\)

下面是正解:

这个二维的 DP 没前途啊,我们再试试 simplify 一下状态:

就是确认“包含目标位置”的状态区间的长度,我们设“包含目标位置”的区间长度为 \(i\) 时候的最小代价为 \(dp_i\)

然后你会发现这个棋子在两边是没错的,但是在哪一边好像不重要,在这个状态下我们只需要知道我们往区间里走了多少长度,而且左右走是对称的。

那枚举吧,假设我们往区间里走了 \(j\),那么从 \(dp_{i},dp_{i-j}\) 转移即可。式子也是个 \(\min \max\) 状物。

然后你想 \(O(n(n+m))\) 过这题吗?当然可以,因为我们枚举 \(j\) 最多只用到 \(i\),于是还有 \(\frac{1}{2}\) 常数呢,\(O(5e8)\) 能过。

呃呃是有点不讲理,其实有 \(O(m(n+m))\) 做法。

就是我们发现枚举的 \(j\) 如果大于 \(\max a_i\) 那么一定是由两步以上跳过来的,我们不用那么急,每次只转移一步即可。

但是出题人都说了第一个精细实现是给过的(

代码还没补。

posted @ 2025-11-15 17:31  hm2ns  阅读(3)  评论(0)    收藏  举报