2025-10-15 ZR-J 模拟赛 赛后总结【ZR】
孩子们,我开了文件读写然后全爆 0 了!!!!!
T1 吃
吃了吧。
题意
给 \(a\) 和 \(b\) 两个序列,选定一个子串,对于子串的任意一个位置,都可以选择 \(a_i\) 或 \(b_i\) 其一,选定的连续子串合法当且仅当存在一种选法使选择的所有数都相同。
\(1\le n\le 10^5,1\le a_i,b_i\le 5\)。
赛时
赛时 20 分钟敲了个转移很显然的 \(O(n)\) dp。结果挂成 10pts。
赛后暴力枚举值域过了。
啥啊原来我转移式写错了,一个位置的 1 写成 0 了。【不对拍的后果】
题解
考虑 \(dp_{i,0/1}\) 表示第 \(i\) 个位置选择 \(a_i\) 或 \(b_i\),以 \(i\) 为结尾的最长合法子串。
显然,当 \(a_{i-1}=a_i\) 时,\(dp_{i,0}\) 可以从 \(dp_{i-1,0}\) 转移。
同理,当 \(b_{i-1}=a_i\) 时,\(dp_{i,0}\) 可以从 \(dp_{i-1,1}\) 转移。
以此类推。总共四个转移式。
最后答案取全局 \(\max\) 再找到符合要求的最小值就行了。这个做法可以推广到 \(a_i,b_i\gt 5\) 的情况。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
int n;
int a[100010],b[100010];
int dp[100010][2];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
    for(int i=1;i<=n;i++) dp[i][0]=dp[i][1]=1;
    for(int i=2;i<=n;i++){
        if(a[i]==a[i-1]) dp[i][0]=max(dp[i][0],dp[i-1][0]+1);
        if(b[i]==b[i-1]) dp[i][1]=max(dp[i][1],dp[i-1][1]+1);
        if(a[i]==b[i-1]) dp[i][0]=max(dp[i][0],dp[i-1][1]+1);
        if(b[i]==a[i-1]) dp[i][1]=max(dp[i][1],dp[i-1][0]+1);
    }
    int mx=-1;
    for(int i=1;i<=n;i++) mx=max(mx,max(dp[i][0],dp[i][1]));
    int ans=inf;
    for(int i=1;i<=n;i++) for(int j=0;j<=1;j++) if(dp[i][j]==mx) ans=min(ans,(j?b[i]:a[i]));
    cout<<mx<<" "<<ans;
    return 0;
}
T2 糖
糖不糖。
题意
给长为 \(n\) 的序列 \(a\) 和正整数 \(m\)。构造一个长为 \(n\) 的序列 \(b\),满足 \(\sum b\le m\)。最小化 \(\sum_{b_i<a_i} (a_i-b_i)^2\),并求出这个最小值。
赛时
赛时摸了个二分+贪心的解法。居然真了。
题解
严格证明太复杂了。考虑简单点的,我们把向 \(b\) 加数转化为向 \(a\) 减数。
首先,初始答案显然为 \(\sum a_i^2\)。
对于 \(a_i-1\to a_i\),答案的变化量为 \(-2a_i+1\)。那么显然每次选择最大的数进行操作一定不劣。思考到这里容易想到 \(O(m\log n)\) 的优先队列解法。
考虑优化这个减数的过程。注意到我们会对同一个数反复修改,非常耗费时间。所以我们可以把相等的数合并到一起,并一次性减少到此时序列中的严格次小值(From baieeeeeeeee)。
这是一种办法,但是还是太复杂了。
所以我又想了一个更简单的办法,先二分出来极限情况下能把所有数减少到多少。很显然有单调性,可以二分。
处理出来这个信息之后,再把剩余的数降序排一下,剩下没减的数肯定选择最大值更优。
然后就可以 \(O(n\log V + n\log n)\) 解决了。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
int n;
long long m;
long long a[100010];
bool chk(long long md){
    long long nw=0;
    for(int i=1;i<=n;i++) if(a[i]>md) nw+=a[i]-md;
    return nw<=m;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>m>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    long long l=0,r=LLONG_MAX;
    while(l<r){
        long long mid=(l+r)>>1;
        if(chk(mid)) r=mid;
        else l=mid+1;
    }
    long long nw=0;
    for(int i=1;i<=n;i++) if(a[i]>l) nw+=a[i]-l,a[i]=l;
    nw=m-nw;
    sort(a+1,a+1+n,greater<int>());
    for(int i=1;i<=n&&nw;i++,nw--) a[i]--;
    long long ans=0;
    for(int i=1;i<=n;i++) ans+=a[i]*a[i];
    cout<<ans; 
    return 0;
}
T3 数
还在数?
题意
给长为 \(n\) 的序列 \(a\)。定义 \(f(s)=\max_{x\in s}x-\min_{x\in s}x\)。
求 \(\sum_{i=1}^{n} \sum_{j=i}^{n} f(a[i:j])\)。
赛时
敲了单调栈,但是没处理相等的情况还写挂了。所以全挂了。
第二天补了题,细节真的很多,但不失为一道好题。
题解
考虑拆贡献。
对于每个数 \(a_i\),当其所在序列中不存在比其大的数时,这个数能作为最大值计入答案。
对应的,当其所在序列中不存在比其小的数时,这个数能作为最小值计入答案。
然后把两个情况合起来就是答案了。
用四个单调栈维护就够了。听别人说用线段树,但我感觉完全没必要。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
int n;
long long a[300010];
stack<int> stk;
int fr1[300010],fr2[300010];
int bk1[300010],bk2[300010];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
        if(stk.empty()) fr1[i]=1;
        else fr1[i]=stk.top()+1;
        stk.push(i);
    }
    while(!stk.empty()) stk.pop();
    for(int i=1;i<=n;i++){
        while(!stk.empty()&&a[stk.top()]<=a[i]) stk.pop();
        if(stk.empty()) fr2[i]=1;
        else fr2[i]=stk.top()+1;
        stk.push(i);
    }
    while(!stk.empty()) stk.pop();
    for(int i=n;i>=1;i--){
        while(!stk.empty()&&a[stk.top()]>a[i]) stk.pop();
        if(stk.empty()) bk1[i]=n;
        else bk1[i]=stk.top()-1;
        stk.push(i);
    }
    while(!stk.empty()) stk.pop();
    for(int i=n;i>=1;i--){
        while(!stk.empty()&&a[stk.top()]<a[i]) stk.pop();
        if(stk.empty()) bk2[i]=n;
        else bk2[i]=stk.top()-1;
        stk.push(i);
    }
    while(!stk.empty()) stk.pop();
    long long ans=0;
    for(int i=1;i<=n;i++){
        ans+=1ll*(bk2[i]-i+1)*(i-fr2[i]+1)*a[i];
        ans-=1ll*(bk1[i]-i+1)*(i-fr1[i]+1)*a[i];
    }
    cout<<ans;
    return 0;
}
总结
所以为什么 OI 赛制还不给大样例。
考虑到简单题的对拍是必须的。这场 T1 挂的非常离谱。
                    
                
                
            
        
浙公网安备 33010602011771号