2025--炼石计划-- 11 月 13 日 --NOIP 模拟赛 #20

2025--炼石计划-- 11 月 13 日 --NOIP 模拟赛 #20

\(T1\) A. 邻间的骰子之舞 \(70pts\)

  • 等价于求在满足 \(\prod\limits_{i=1}^{m}k_{i}>n(k_{i} \ge 2)\) 的情况下 \(xm+(-m+\sum\limits_{i=1}^{m}k_{i})y\) 的最小值。

  • \(m\) 上界为 \(\left\lceil \log_{2}n \right\rceil\) ,在 \(n=10^{18}\) 时取到 \(60\)

  • 部分分

    • 子任务 \(1\) :打表加手摸。
    • 子任务 \(3\) :爆搜。
    点击查看代码
    ll ans=0x7f7f7f7f;
    void dfs(ll dep,ll n,ll mul,ll sum,ll x,ll y)
    {
        if(mul>n)
        {
            ans=min(ans,dep*x+(sum-dep)*y);
            return;
        }
        for(ll i=2;i<=n+1;i++)
        {	
            dfs(dep+1,n,i*mul,sum+i,x,y);
            if(i*mul>n)
            {
                break;
            }
        }
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("dice.in","r",stdin);
        freopen("dice.out","w",stdout);
    #endif
        ll n,m,dis,cnt,x,y,i,j,k;
        cin>>n>>x>>y;
        if(x==1&&y==1)
        {
            ans=2;
            cnt=dis=m=1;
            for(cnt=dis=m=1,ans=2;m<n;m+=dis)
            {
                if(cnt%3==0)
                {
                    dis*=2;
                }
                if(cnt%3==1)
                {
                    dis+=dis/2;
                }
                ans++;
                cnt++;
                if(n<=m+dis-1)
                {
                    break;
                }
            }
        }
        else
        {
            dfs(0,n,1,0,x,y);
        }
        cout<<ans<<endl;
        return 0;
    }
    
  • 正解

    • 考虑枚举 \(m\) ,此时需要求 \(\sum\limits_{i=1}^{m}k_{i}\) 的最小值,由均值不等式先令 \(k_{i}=\max(2,\left\lfloor \sqrt[m]{n} \right\rfloor)\) ,然后再进行适当调整加 \(1\)
    • 在对拍时发现直接使用 pow(n,1.0/m) 可能会出现精度问题,多调整一遍即可。
    • 中途会爆 long long ,使用 unsigned long long 代替即可。
    点击查看代码
    ull f[70][70];
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("dice.in","r",stdin);
        freopen("dice.out","w",stdout);
    #endif
        ull n,x,y,ans=0x7f7f7f7f7f7f7f7f,sum,mul,i,j;
        cin>>n>>x>>y;
        for(i=1;i<=60;i++)
        {
            sum=0;
            mul=1;
            for(j=1;j<=i;j++)
            {
                f[i][j]=max(2.0,pow(n,1.0/i));
                mul*=f[i][j];
                sum+=f[i][j];
            }
            while(mul<=n)//至多对全局加两遍
            {
                for(j=1;j<=i&&mul<=n;j++)
                {
                    mul/=f[i][j];
                    f[i][j]++;
                    mul*=f[i][j];
                    sum++;
                }
            }
            ans=min(ans,x*i+(sum-i)*y);
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(T2\) B. 星海浮沉录 \(20pts\)

  • 部分分

    • \(20pts\) :观察到 \(\operatorname{mex}\) 随区间长度增大不降,只取长度 \(=x\) 的区间进行判断。权值线段树维护即可。
    点击查看代码
    struct SMT
    {
        struct SegmentTree
        {
            int len,sum,cnt;
        }tree[500010];
        int lson(int x)
        {
            return x*2;
        }
        int rson(int x)
        {
            return x*2+1;
        }
        void pushup(int rt)
        {
            tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;
        }
        void build(int rt,int l,int r)
        {
            tree[rt].len=r-l+1;
            tree[rt].sum=tree[rt].cnt=0;
            if(l==r)
            {
                return;
            }
            int mid=(l+r)/2;
            build(lson(rt),l,mid);
            build(rson(rt),mid+1,r);
        }
        void update(int rt,int l,int r,int pos,int val)
        {
            if(l==r)
            {
                tree[rt].cnt+=val;
                tree[rt].sum=(tree[rt].cnt>=1);
                return;
            }
            int mid=(l+r)/2;
            if(pos<=mid)
            {
                update(lson(rt),l,mid,pos,val);
            }
            else
            {
                update(rson(rt),mid+1,r,pos,val);
            }
            pushup(rt);
        }
        int query(int rt,int l,int r)
        {
            if(l==r)
            {
                return l;
            }
            int mid=(l+r)/2;
            if(tree[lson(rt)].sum==tree[lson(rt)].len)
            {
                return query(rson(rt),mid+1,r);
            }
            else
            {
                return query(lson(rt),l,mid);
            }
        }
    }T;	
    int a[500010];
    int query(int n,int x)
    {
        T.build(1,0,n);
        int ans=0x7f7f7f7f;
        for(int i=1;i<=x;i++)
        {
            T.update(1,0,n,a[i],1);
        }
        ans=min(ans,T.query(1,0,n));
        for(int i=x+1;i<=n;i++)
        {
            T.update(1,0,n,a[i-x],-1);
            T.update(1,0,n,a[i],1);
            ans=min(ans,T.query(1,0,n));
        }
        return ans;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("star.in","r",stdin);
        freopen("star.out","w",stdout);
    #endif
        int n,q,pd,x,i;
        scanf("%d%d",&n,&q);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(i=1;i<=q;i++)
        {
            scanf("%d%d",&pd,&x);
            if(pd==1)
            {
                swap(a[x],a[x+1]);
            }
            else
            {
                printf("%d\n",query(n,x));
            }
        }
        return 0;
    }
    
  • 正解

    • 考虑找到一个最小的 \(k \in [0,n]\) 使得存在一个长度 \(=x\) 的区间不包含 \(k\) 。即若设 \(pos_{i,j}\) 表示 \(i\) 在原数列中第 \(j\) 次出现的位置,等价于求最小的 \(k\) 满足存在 \(j\) 使得 \(pos_{k,j}-pos_{k,j-1}>x\)
    • 每个数的全局 \(\max\) 临项差修改时使用 multiset 一并维护,线段树维护前缀 \(\max\) 后在线段树上二分即可。
    点击查看代码
    int a[500010],id[500010];
    vector<int>pos[500010];
    multiset<int>s[500010];
    struct SMT
    {
        struct SegmentTree
        {
            int maxx;
        }tree[2000010];
        int lson(int x)
        {
            return x*2;
        }
        int rson(int x)
        {
            return x*2+1;
        }
        void pushup(int rt)
        {
            tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx);
        }
        void build(int rt,int l,int r)
        {
            if(l==r)
            {
                tree[rt].maxx=*prev(s[l].end());
                return;
            }
            int mid=(l+r)/2;
            build(lson(rt),l,mid);
            build(rson(rt),mid+1,r);
            pushup(rt);
        }
        void update(int rt,int l,int r,int pos,int val)
        {
            if(l==r)
            {
                tree[rt].maxx=val;
                return;
            }
            int mid=(l+r)/2;
            if(pos<=mid)
            {
                update(lson(rt),l,mid,pos,val);
            }
            else
            {
                update(rson(rt),mid+1,r,pos,val);
            }
            pushup(rt);
        }
        int query(int rt,int l,int r,int k)
        {
            if(l==r)
            {
                return l;
            }
            int mid=(l+r)/2;
            if(tree[lson(rt)].maxx>k)
            {
                return query(lson(rt),l,mid,k);
            }
            else
            {
                return query(rson(rt),mid+1,r,k);
            }
        }
    }T;	
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("star.in","r",stdin);
        freopen("star.out","w",stdout);
    #endif
        int n,q,pd,x,i;
        cin>>n>>q;
        for(i=0;i<=n;i++)
        {
            pos[i].push_back(0);
        }
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
            id[i]=pos[a[i]].size();
            s[a[i]].insert(i-pos[a[i]].back());
            pos[a[i]].push_back(i);
        }
        for(i=0;i<=n;i++)
        {
            s[i].insert(n+1-pos[i].back());
            pos[i].push_back(n+1);
        }
        T.build(1,0,n);
        for(i=1;i<=q;i++)
        {
            cin>>pd>>x;
            if(pd==1)
            {
                if(a[x]!=a[x+1])
                {	
                    s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]));
                    s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]));
                    pos[a[x]][id[x]]++;
                    s[a[x]].insert(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]);
                    s[a[x]].insert(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]);
                    T.update(1,0,n,a[x],*prev(s[a[x]].end()));
                    x++;
                    s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]));
                    s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]));
                    pos[a[x]][id[x]]--;
                    s[a[x]].insert(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]);
                    s[a[x]].insert(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]);
                    T.update(1,0,n,a[x],*prev(s[a[x]].end()));
                    swap(a[x],a[x-1]);
                    swap(id[x],id[x-1]);
                }
            }
            else
            {
                cout<<T.query(1,0,n,x)<<endl;
            }
        }
        return 0;
    }
    

\(T3\) C. 勾指起誓 \(12pts\)

  • 部分分

    • \(Subtask 1\) :模拟。
    点击查看代码
    const ll p=998244353;
    ll ans[30],a[100010];
    char c[100010][30];
    vector<ll>tmp;
    set<ll>s;
    set<ll>::iterator it;
    int main()
    {
    #define Issac
    #ifdef Issac
        freopen("yilihun.in","r",stdin);
        freopen("yilihun.out","w",stdout);	
    #endif
        ll n,m,cnt,i;
        scanf("%lld%lld",&n,&m);
        for(i=1;i<=n;i++)
        {
            scanf("%s",c[i]+1);
            a[i]=i;
        }
        do
        {
            s.clear();
            for(i=1;i<=m;i++)
            {
                s.insert(i);
            }
            for(i=1;i<=n;i++)
            {
                cnt=0;
                for(it=s.begin();it!=s.end();it++)
                {
                    cnt+=(c[a[i]][*it]=='1');
                }
                if(cnt==0||cnt==s.size())
                {
                    continue;	
                }
                for(it=s.begin();it!=s.end();it++)
                {
                    if(c[a[i]][*it]=='0')
                    {
                        tmp.push_back(*it);
                    }
                }
                while(tmp.empty()==0)
                {
                    s.erase(tmp.back());
                    tmp.pop_back();
                }
            }
            for(it=s.begin();it!=s.end();it++)
            {
                ans[*it]=(ans[*it]+1)%p;
            }
        }while(next_permutation(a+1,a+1+n));
        for(i=1;i<=m;i++)
        {
            printf("%lld\n",ans[i]);
        }
        return 0;
    }
    
  • 正解

\(T4\) D. 第八交响曲 \(0pts\)

  • 部分分

    • \(10pts\)\(\frac{n(n-1)}{2}\) 次暴力判断。
    点击查看代码
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("symphony.in","r",stdin);
        freopen("symphony.out","w",stdout);
    #endif
        int n,i,j;
        cin>>n;
        cout<<n*(n-1)/2<<endl;
        for(i=n;i>=1;i--)
        {
            for(j=1;j<i;j++)
            {
                cout<<"CMPSWP R"<<j<<" R"<<i<<endl;
            }
        }
        return 0;
    }
    
  • 正解

    • 观察到部分排序间可以并行,考虑双调排序。
      • 双调序列是指满足先单调递增后单调递减(单峰)或先单调递减后单调递增(单谷),或者可以通过循环位移满足上述条件的序列。
      • \(Batcher\) 定理:将一个长度为 \(2n\) 的双调序列 \(\{ a \}\) 分成 \([1,n],[n+1,2n]\) 两部分,分别将 \(a_{i}\)\(a_{i+n}(i \in [1,n])\) 进行比较,较大者放入 \(\max\) 序列,较小者放入 \(\min\) 序列,则得到的 \(\max,\min\) 序列仍为双调序列,且 \(\max\) 序列中的任意一个元素不小于 \(\min\) 序列中的任意一个元素。
        • 分讨加手摸即可证明。
      • 双调排序的基本思路是将原序列排序成双调序列,然后将双调序列排序成单调序列。
        • 对于前者先将原序列分成两半,前后分别递归排序成递增/递减序列,然后就得到了一个双调序列。
        • 将后面的序列翻转后就变成了单峰序列,然后利用 \(Batcher\) 定理进行排序。
    • 先将 \(n\) 扩充成 \(2\) 的整数次幂,空位补充一个极大值,最后再进行忽略。
    • 分治树的每一层所有操作均可以并行进行。
    • 单位时间 数为 \(\sum\limits_{i=1}^{\left\lceil \log_{2}n \right\rceil}i=\frac{\left\lceil \log_{2}n \right\rceil(\left\lceil \log_{2}n \right\rceil+1)}{2}\)
    点击查看代码
    int pos[210],id[210][210],cnt=0;
    vector<pair<int,int> >ans[210];
    void divide(int l,int r,int dep,int hdep)
    {
        if(l==r)
        {
            return;
        }
        if(id[dep][hdep]==0)
        {
            cnt++;
            id[dep][hdep]=cnt;
        }
        int mid=(l+r)/2;
        for(int i=l;i<=mid;i++)
        {
            if(mid+i-l+1<=r)
            {
                ans[id[dep][hdep]].push_back(make_pair(i,mid+i-l+1));//分成两半进行比较
            }
        }
        divide(l,mid,dep+1,hdep);
        divide(mid+1,r,dep+1,hdep);
    }
    void solve(int l,int r,int dep)
    {
        if(l==r)
        {
            return;
        }
        int mid=(l+r)/2;
        solve(l,mid,dep+1);
        solve(mid+1,r,dep+1);
        if(pos[dep]==0)
        {
            cnt++;
            pos[dep]=cnt;
        }
        for(int i=1;i<=mid;i++)
        {
            if(l+i-1<r-i+1)//翻转
            {
                ans[pos[dep]].push_back(make_pair(l+i-1,r-i+1));
            }
        }
        divide(l,mid,dep+1,dep);
        divide(mid+1,r,dep+1,dep);
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("symphony.in","r",stdin);
        freopen("symphony.out","w",stdout);
    #endif
        int n,len=1,i,j;
        cin>>n;
        for(len=1;len<n;len<<=1);
        solve(1,len,1);
        cout<<cnt<<endl;
        for(int i=1;i<=cnt;i++)
        {
            for(int j=0;j<ans[i].size();j++)
            {
                if(ans[i][j].first<=n&&ans[i][j].second<=n)
                {
                    cout<<"CMPSWP R"<<ans[i][j].first<<" R"<<ans[i][j].second<<" ";
                }
            }
            cout<<endl;
        }
        return 0;
    }
    

总结

  • \(T1\)long long 了,挂了 \(30pts\) ;最后 \(20 \min\) 才成功口胡出正解。
  • \(T4\) 多写了个 = ,挂了 \(10pts\)

后记

  • \(T4\) 附加文件仅下发了 checker.cpp ,但没下发 testlib 。而且 checker.cpp 检验合法时直接 rand() 排列进行判断。

    点击查看代码
    // Author : HeRaNO
    // Modified by YunQian
    
    #include "testlib.h"
    #define MAXN 105
    
    const int testCase = 100;
    const int changeTime = 5;
    const int swapTime = 5;
    
    int N[] = {0, 8, 13, 16, 32, 53, 64, 73, 82, 91, 100};
    int T[11][3]={
        {0, 0, 0},
        {28, 9, 6},
        {78, 14, 10},
        {120, 17, 10},
        {496, 33, 15},
        {1378, 54, 21},
        {2016, 65, 21},
        {2628, 74, 28},
        {3321, 83, 28},
        {4095, 92, 29},
        {4950, 101, 30}
    };
    
    int n;
    
    bool isdigit(std::string &s)
    {
        for (char i : s)
            if (!(i >= '0' && i <= '9'))
                return false;
        return true;
    }
    
    std::vector<std::pair<int, int>> valid(int line, std::vector<std::string> &instructions)
    {
        std::vector<bool> vis(n + 1, false);
        std::vector<std::pair<int, int>> res;
        quitif(instructions.size() % 3 != 0, _wa, "wrong parameters format (line #%d)", line);
        for (int i = 0; i < instructions.size() / 3; i++)
        {
            std::string instruction = instructions[3 * i];
            std::string R1 = instructions[3 * i + 1];
            std::string R2 = instructions[3 * i + 2];
            quitif(instruction != "CMPSWP", _wa, "wrong instruction name: %s (line #%d - instruction #%d)", instruction.c_str(), line, i + 1);
            quitif(R1.front() != 'R', _wa, "wrong register Ri name: %s (line #%d - instruction #%d)", R1.c_str(), line, i + 1);
            quitif(R2.front() != 'R', _wa, "wrong register Rj name: %s (line #%d - instruction #%d)", R2.c_str(), line, i + 1);
            int r1, r2;
            R1 = R1.substr(1);
            R2 = R2.substr(1);
            quitif(!isdigit(R1), _wa, "wrong register Ri format: R%s (line #%d - instruction #%d)", R1.c_str(), line, i + 1);
            quitif(!isdigit(R2), _wa, "wrong register Rj format: R%s (line #%d - instruction #%d)", R2.c_str(), line, i + 1);
            try {
                r1 = std::stoi(R1);
                r2 = std::stoi(R2);
            } catch (std::exception& err) {
                quitf(_wa, "wrong register format: %s (line #%d - instruction #%d)", err.what(), line, i + 1);
            }
            quitif(!(1 <= r1 && r1 <= n), _wa, "wrong register Ri format: i < 1 or i > n (line #%d - instruction #%d)", line, i + 1);
            quitif(!(1 <= r2 && r2 <= n), _wa, "wrong register Ri format: i < 1 or i > n (line #%d - instruction #%d)", line, i + 1);
            quitif(r1 == r2, _wa, "R%d is used twice in line #%d", r1, line);
            quitif(vis[r1], _wa, "R%d is used twice in line#%d", r1, line);
            quitif(vis[r2], _wa, "R%d is used twice in line#%d", r2, line);
            vis[r1] = vis[r2] = true;
            res.push_back({r1, r2});
        }
        return res;
    }
    
    void cmpswp(int &a, int &b)
    {
        if (a > b) std::swap(a, b);
        return ;
    }
    
    bool canSwap(std::vector<int> rs, const std::vector<std::pair<int, int>> &allSwapR)
    {
        for (auto [r1, r2] : allSwapR)
            cmpswp(rs[r1 - 1], rs[r2 - 1]);
        return std::is_sorted(rs.begin(), rs.end());
    }
    
    void testSort(const std::vector<std::pair<int, int>> &allSwapR, bool isRandom = true, bool desc = false)
    {
        for (int change = 1; change <= changeTime; change++)
        {
            std::vector<int> rs(n, 0);
            for (int i = 0; i < n; i++)
                rs[i] = rnd.next(n);
            if (!isRandom)
            {
                if (desc)
                    std::sort(rs.begin(), rs.end(), std::greater<int>());
                else
                    std::sort(rs.begin(), rs.end());
            }
            for (int i = 1; i <= testCase; i++)
            {
                if (!canSwap(rs, allSwapR))
                    quitf(_wa, "the commands are wrong - test case %d (random: %s, desc: %s)", i, isRandom ? "true" : "false", desc ? "true" : "false");
                int swapT = rnd.next(1, swapTime);
                while (swapT--)
                {
                    // std::vector<int> swapPos = rnd.distinct(2, 0, n - 1);
                    // int a = swapPos.front();
                    // int b = swapPos.back();
                    int a=rnd.next(n), b=rnd.next(n - 1);
                    if(b>=a)b++;
                    std::swap(rs[a], rs[b]);
                }
            }
        }
        return ;
    }
    
    void registerGenForTest(int argc, char *argv[])
    {
        random_t::version = 1;
        rnd.setSeed(argc, argv);
        return ;
    }
    
    int main(int argc, char *argv[])
    {
        registerTestlibCmd(argc, argv);
        registerGenForTest(argc, argv);
        n = inf.readInt();
        int testCase = 0;
        for (int i = 1; i <= 10; i++)
            if (N[i] == n)
                testCase = i;
        quitif(!testCase, _fail, "didn't found the testcase which n = %d", n);
        std::vector<std::pair<int, int>> allSwapR;
        int t1 = T[testCase][0];
        int t2 = T[testCase][1];
        int t3 = T[testCase][2];
        int t = ouf.readInt();
        ouf.nextLine();
        quitif(t > t1, _wa, "t > t1");
        for (int i = 1; i <= t; i++)
        {
            std::string program;
            std::vector<std::string> instructions;
            std::vector<std::pair<int, int>> swapR;
            ouf.readLineTo(program);
            instructions = split(trim(program), ' ');
            swapR = valid(i, instructions);
            for (auto item: swapR)
                allSwapR.push_back(item);
        }
        testSort(allSwapR);
        testSort(allSwapR, false);
        testSort(allSwapR, false, true);
        if (t1 >= t && t > t2)
            quitp(round(10.0 + 20.0 / (t - t2))/100.0, "correct answer with t1 = %d >= t = %d > t2 = %d", t1, t, t2);
        if (t2 >= t && t > t3)
            quitp(round(30.0 + 70.0 * (t2 - t + 1) / (t2 - t3))/100.0, "correct answer with t2 = %d >= t = %d > t3 = %d", t2, t, t3);
        quitf(_ok, "correct answer with t = %d <= t3 = %d", t, t3);
        return 0;
    }
    
posted @ 2024-11-14 20:57  hzoi_Shadow  阅读(33)  评论(0编辑  收藏  举报
扩大
缩小