2025-10-16 MX-S 模拟赛 赛后总结【MX】
赛时心路历程
- 14:33 因为补了昨天模拟赛的题所以开题晚了一会。
 - 14:37 想到 T1 的 \(O(n\log^2n)\) 做法,过了一会想到了优化到单 \(\log\) 的办法。
 - 15:03 过了 T1 大样例。开 T2。
 - 15:12 啥啊 T2 为啥是串串啊,看了几眼怎么感觉像偏序。
 - 15:40 敲了 T2 暴力。实在想不到正解了。
 - 15:52 把 T2 暴力优化了一下,感觉有点接近正解了,但还是想不到。
 - 16:04 看了一眼 T3,什么后缀排名。不会 SA,跳 T4。
 - 16:36 研究 T4 的特殊性质。
 - 16:52 T4 性质打完了,看看 T5。
 - 16:55 诶我靠在线版矩形面积并。不想写了结束比赛吧。
 
结果是 50+100+0+40+0。
T1 开拓之旅
被铁吃吓哭了。
题意
给定长为 \(n\) 的序列 \(a,b\),初始在 \(1\),你的目标是 \(n\)。一次从 \(i\) 到 \(j\) 的移动的代价是 \(|i-j|+a_j\)。你可以选定一个固定的 \(k\),使单次移动可以从 \(x\) 移动到 \([x-k,x+k]\)。
求在代价不超过 \(m\) 的情况下,你可以选择的最小 \(b_k\) 的值。不保证 \(b\) 单调不降。
赛时
摸了单调队列优化 dp。但是忘了判断窗口长度了导致严肃挂 50pts。
为何数据和大样例都如此水。
题解
首先注意到很显然不会走回头路。
其次注意到 \(r\) 的选择具有单调性。
于是二分 \(r\),每次跑一个单调队列优化的 dp 就行了。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
int n;
long long m;
long long a[1000010],b[1000010];
long long dp[1000010];
int q[1000010],head=1,tail=0;
bool chk(int md){
    head=1,tail=0;
    q[++tail]=1;
    dp[1]=0;
    for(int i=2;i<=n;i++){
        while(head<=tail&&q[head]<i-md) head++;
        dp[i]=dp[q[head]]+b[i];
        while(head<=tail&&dp[q[tail]]>dp[i]) tail--;
        q[++tail]=i;
    }
    return dp[n]<=m-n+1;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    freopen("journey.in","r",stdin);
    freopen("journey.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<n;i++) cin>>a[i];
    for(int i=1;i<n;i++) cin>>b[i];
    int l=1,r=n-1;
    while(l<r){
        int mid=(l+r)>>1;
        if(chk(mid)) r=mid;
        else l=mid+1;
    }
    long long ans=a[l];
    for(int i=l+1;i<n;i++) ans=min(ans,a[i]);
    cout<<ans<<"\n";
    return 0;
}
T2 开拓队伍
为何是串。
题意
给定长为 \(n\) 的字符串序列 \(s\)。称一个字符串序列 \(d\) 合法,当且仅当:
- \(\forall j<i,d_j\le d_i,\operatorname{reverse}(d_j)\le \operatorname{reverse}(d_i)\);
 - 任意相邻两个字符串中有且仅有一个字符串出现字符 
m。 
你需要从 \(s\) 中选出若干个字符串,并任意重排使序列合法,你需要求出最多可以选出的字符串数量。
赛时
看了几眼看出来像偏序,然后打了个 \(O(n^2)\) 的暴力。
然后又想了想套上了一层树状数组,然后居然过了。
所以为什么我的解法比 aoao 的快一倍。aoao 常数居然如此巨大。
题解
注意到这个合法的条件 1 非常显然具有偏序的特点。
所以正序排一次记录一下排名,再反转字符串再排一次,这样就变成二维偏序了。
然后就是套个树状数组的神秘 dp。
其实应该可以优化到一维,但我懒了。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
template<typename T> class BinaryIndexTree{
    private:
        int len;
        vector<T> v;
        
        static int lowbit(int x){return (x&(-x));}
    
    public:
        BinaryIndexTree(int _len=0):len(_len),v(_len+1){}
        
        void reset(int _len=0){
            len=_len;
            v.clear();
            v.resize(_len+1);
        }
        
        void add(int x,T val){
            while(x<=len) v[x-1]=max(v[x-1],val),x+=lowbit(x);
        }
        
        T query(int x){
            T res=T();
            while(x) res=max(res,v[x-1]),x-=lowbit(x);
            return res;
        }
};
BinaryIndexTree<int> bit[2];
int n;
struct node{
    string s;
    int id;
    bool hsm;
    bool operator<(const node&_Q)const{return s<_Q.s;}
}a[100010];
int dp[100010][2];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    freopen("queue.in","r",stdin);
    freopen("queue.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i].s;
    for(int i=1;i<=n;i++) for(char c:a[i].s) a[i].hsm|=(c=='m');
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) a[i].id=i;
    for(int i=1;i<=n;i++) reverse(a[i].s.begin(),a[i].s.end());
    sort(a+1,a+1+n);
    bit[0].reset(n);
    bit[1].reset(n);
    for(int i=1;i<=n;i++){
        dp[i][a[i].hsm]=max(1,bit[!a[i].hsm].query(a[i].id)+1);
        bit[a[i].hsm].add(a[i].id,dp[i][a[i].hsm]);
    }
    int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,dp[i][a[i].hsm]);
    cout<<ans<<"\n";
    return 0;
}
T4 开拓之谜
为什么原题是黑啊有病吧。吓哭了。
题意
给定一张 DAG,你可以在保证图仍是 DAG 的前提下向其中加入至多 \(k\) 条边。最大化加边后图的最小拓扑序的字典序。
赛时
赛时贺了 \(k=0\) 和 \(m=0\) 的特殊性质,想不到正解。
结果赛后一看原题是黑。吓哭了。
题解
不写题解了,思路太复杂了所以我稍微理解了一下就直接贺了篇题解。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;
struct edge{int to,nxt;}e[100010];int head[100010],cnt=1;
inline void addedge(int x,int y){e[cnt]={y,head[x]},head[x]=cnt++;}
int n,m,k;
int indigit[100010];
priority_queue<int> p;
priority_queue<int,vector<int>,greater<int> > q;
struct node{
    int x,y;
};vector<node> ans;
int topolist[100010];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    freopen("puzzle.in","r",stdin);
    freopen("puzzle.out","w",stdout);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        int x,y;cin>>x>>y;
        addedge(x,y);
        indigit[y]++;
    }
    for(int i=1;i<=n;i++) if(!indigit[i]) q.push(i);
    int a,b;
    for(int i=1;i<=n;i++){
        while(!q.empty()&&k){
            int x=q.top(),y=-1;
            if(!p.empty()) y=p.top();
            if(x>y&&q.size()==1) break;
            q.pop();
            p.push(x);
            k--;
        }
        if(!q.empty()) b=q.top(),q.pop();
        else b=p.top(),p.pop()/*,ans.push_back({a,b})*/;
        topolist[i]=b;
        for(int j=head[b];j;j=e[j].nxt){
            int c=e[j].to;
            if(!--indigit[c]) q.push(c);
        }
        a=b;
    }
    for(int i=1;i<=n;i++) cout<<topolist[i]<<" ";
    // cout<<"\n"<<ans.size()<<"\n";
    // for(node x:ans) cout<<x.x<<" "<<x.y<<"\n";
    return 0;
}
总结
这场 T1 爆的最多,实际上是非常纸张的错误。太久没写单调队列忘了控制窗口长度了。
实际上我还没学到 SA 结果这场 T3 考了 SA 的结论(应该是)然后遗憾离场了。赛前把这一块补一补。
关于 T4,我 \(k=1\) 的做法假了,但别的性质都没问题。需要多注重一下特殊性质的结论。
                    
                
                
            
        
浙公网安备 33010602011771号