8.22NOIP Day8模拟赛

T1

显然的,我们可以维护 \(s\) 中每一个位置下一个 \(a\sim z\) 出现的位置,如果没有就从头开始并把答案加 \(1\),然后对于 \(t\) 从前到后跑一遍记录当前位置即可

#include<bits/stdc++.h>
#define N 500005
using namespace std;
int nxt[N][30];
char s[N],t[N];
signed main(){
    scanf("%s%s",s+1,t+1);
    int n=strlen(s+1),m=strlen(t+1);
    for(int i=n-1;i>=0;i--){
        for(int j=0;j<26;j++)nxt[i][j]=nxt[i+1][j];
        nxt[i][s[i+1]-'a']=i+1;
    }
    for(int i=1;i<=n;i++)for(int j=0;j<26;j++)if(!nxt[i][j])nxt[i][j]=nxt[0][j];
    int now=0,ans=1;
    for(int i=1;i<=m;i++){
        if(!nxt[now][t[i]-'a']){
            printf("-1\n");
            return 0;
        }
        if(nxt[now][t[i]-'a']<=now)ans++;
        now=nxt[now][t[i]-'a'];
    }
    printf("%d\n",ans);
    return 0;
}

T2

对于一个合法的 \((l,r)\),它需要满足 \((l,r-1)\)\((l+1,r)\) 合法,否则就无法取走一个然后依旧保持平衡

故对于每一个 \(1\le i\le n\),都存在一个长度为 \(i\) 的区间的重心在箱子上是合法解的必要性条件,由于如果 \((l,r)\) 以及另外一个长度为 \(r-l\) 的区间重心在箱子上,那么 \((l,r)\) 的子区间 \((l+1,r),(l,r-1)\) 中的一个重心一定在箱子上,所以这是充要条件。

\((1,n)\) 的重心为 \(u\),那么可以判断出对于每一个长度 \(i\),选择重心最靠近 \(u\) 的两个之一(左右两个)一定是最优的,所以考虑二分找到这两个区间存下来,然后按照重心位置排序,跑一个双指针求解最小值

#include<bits/stdc++.h>
#define int long long
#define N 200005
using namespace std;
int x[N],y[N],cnt[N];
struct Ty{
    double u;
    int id;
    bool operator <(const Ty &a){return u<a.u;}
}Q[N*2];
signed main(){
    int n;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&x[i]);
    for(int i=1;i<=n;i++)y[i]=y[i-1]+x[i];
    double u=y[n]/1.0/n;
    for(int i=1;i<n;i++){
        int l=1,r=n-i+1;
        while(l<r){
            int mid=(l+r+1)/2;
            double v=(y[mid+i-1]-y[mid-1])/1.0/i;
            if(v<=u)l=mid;
            else r=mid-1;
        }
        Q[i*2-1].u=(y[l+i-1]-y[l-1])/1.0/i;
        Q[i*2-1].id=i;
        Q[i*2].u=(y[l+i]-y[l])/1.0/i;
        Q[i*2].id=i;
    }
    Q[n*2-1].u=u;
    Q[n*2-1].id=n;
    sort(Q+1,Q+n*2);
    int now=0,summ=0;
    double ans=1e9;
    for(int i=1;i<n*2;i++){
        if(Q[i].u>u)break;
        if(i-1){
            cnt[Q[i-1].id]--;
            if(!cnt[Q[i-1].id])summ--;
        }
        while(summ<n){
            now++;
            if(!cnt[Q[now].id])summ++;
            cnt[Q[now].id]++;
        }
        ans=min(ans,Q[now].u-Q[i].u);
    }
    printf("%.11f",ans);
    return 0;
}

T3

观察到我们可以将 \(1\sim n\) 从大到小像冒泡排序一样依次拉到最右边,不同的是可以观察到大致需要 \(\log_{n}\) 次操作就可以移到最右边,总操作数是 \(O(n\log{n})\) 级的

出题入搬题时卡了操作数的常数,我们需要改成常数更小的写法

为了防止恶意构造数据我们需要随机 \(100\) 次操作打乱序列,我也不知道为啥打乱完就均摊 \(O(n)\) 次操作了

还有常数更小的做法,如果说题目给定要求求出把给定的序列整理好的方案,那么我们可以把操作取反一下,然后去求把有序的数列打乱成给定数列的方案,这样操作常数更小

#include<bits/stdc++.h>
#define N 3005
using namespace std;
vector<pair<int,int> >q;
int x[N],y[N],ans=0;
void solve(int l,int r){
    q.push_back(make_pair(l,r));
    int now=l;
    for(int i=l;i<=r;i++)y[i]=x[i];
    for(int i=l+1;i<=r;i+=2)x[i]=y[now++];
    for(int i=l;i<=r;i+=2)x[i]=y[now++];
    return;
}
signed main(){
    srand(time(0));
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int u;
        scanf("%d",&u);
        x[u]=i;
    }
    for(int i=1;i<=100;i++){
        int a=rand()%n+1,b=rand()%n+1;
        solve(min(a,b),max(a,b));
    }
    for(int i=n;i>=1;i--){
        int now=0;
        for(int j=1;j<=i;j++)if(x[j]==i){
            now=j;
            break;
        }
        while(now*2<=i)solve(1,now*=2);
        if(now!=i)solve(now*2-i+1,i);
    }
    reverse(q.begin(),q.end());
    int ans=q.size();
    printf("%d\n",ans);
    for(int i=0;i<q.size();i++)printf("%d %d\n",q[i].first,q[i].second);
    return 0;
}
posted @ 2025-08-22 11:15  Igunareo  阅读(9)  评论(0)    收藏  举报