The 4th Universal Cup. Stage 13: Grand Prix of Ōokayama

Preface

这低迷的,经典霓虹场,题目难度巨大,签到题是个分治 NTT

坐牢一整场怒写 4 题,通信交互题 K 队友搞了个 \(77\) 还是 \(78\) 次的东西(刚好超限制一两次),只能说是大败而归了


A. Depth of Interval

首先不难发现实际被用到的 \(f(a,b)\)\((a,b)\) 数量是 \(O(n)\)

具体地,令 \(pre_i,nxt_i\) 分别表示位置 \(i\) 前面/后面第一个小于它的元素(容易用单调栈求出),则所有的 \((pre_i,i),(i,nxt_i)\) 就是所有可能的 pair

要具体算出每个 pair 的值只需要用 ST 表预处理下区间最小值后,简单记忆化即可;或者有个更巧妙的实现就是把所有 pair 按照左右端点距离从小到大排序依次枚举,即可保证按照嵌套顺序从里至外依次枚举到

考虑对于一个 pair \((a,b)\) 求出有那些区间的最小/次小值对应着它,首先有个很明显的 bound 就是左端点不能 \(\le pre_a\),右端点不能 \(\ge nxt_b\)

手玩一下后我们发现这里需要分讨,不妨令 \(P_a<P_b\),则此时右端点可以取 \([b,nxt_b)\) 中任意位置,但左端点的下界无法确定

这时候可以利用二分,找到最靠左的位置 \(pos\),满足 \([pos,nxt_b)\) 这段区间的最小/次小值对依然为 \((a,b)\),此时左端点的取值范围就是 \([pos,a]\)

由于 ST 表询问总是 \(O(1)\) 的,最终复杂度 \(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,INF=1e9;
int n,a[N],pre[N],nxt[N]; long long ans[N];
map <pair <int,int>,int> f;
namespace ST_Table
{
    int mn[20][N],_lg[N];
    inline int minpos(CI x,CI y)
    {
        return a[x]<a[y]?x:y;
    }
    inline void init(CI n)
    {
        _lg[0]=-1;
        for (RI i=1;i<=n;++i) _lg[i]=_lg[i>>1]+1;
        for (RI i=1;i<=n;++i) mn[0][i]=i;
        for (RI j=1;j<20;++j)
        for (RI i=1;i+(1<<j)-1<=n;++i)
        mn[j][i]=minpos(mn[j-1][i],mn[j-1][i+(1<<j-1)]);
    }
    inline int query_mnpos(CI l,CI r)
    {
        if (l>r) return -1;
        int k=_lg[r-l+1];
        return minpos(mn[k][l],mn[k][r-(1<<k)+1]);
    }
    inline pair <int,int> query(CI l,CI r)
    {
        int mnpos=query_mnpos(l,r);
        int left=query_mnpos(l,mnpos-1),right=query_mnpos(mnpos+1,r);
        if (left==-1) return {mnpos,right}; else
        if (right==-1) return {left,mnpos}; else
        if (a[left]<a[right]) return {left,mnpos}; else
        return {mnpos,right};
    }
};
using namespace ST_Table;
inline int F(CI x,CI y)
{
    if (x>=y) return 0;
    if (f.count({x,y})) return f[{x,y}];
    if (x+1<y-1)
    {
        auto [l,r]=query(x+1,y-1);
        f[{x,y}]=F(l,r)+1;
    } else f[{x,y}]=1;
    return f[{x,y}];
}
int main()
{
    scanf("%d",&n);
    for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
    vector <pair <int,int>> vec;
    static int stk[N]; int top;
    stk[top=0]=0;
    for (RI i=1;i<=n;++i)
    {
        while (top&&a[stk[top]]>a[i]) --top;
        pre[i]=stk[top]; stk[++top]=i;
        if (pre[i]!=0) vec.push_back({pre[i],i});
    }
    stk[top=0]=n+1;
    for (RI i=n;i>=1;--i)
    {
        while (top&&a[stk[top]]>a[i]) --top;
        nxt[i]=stk[top]; stk[++top]=i;
        if (nxt[i]!=n+1) vec.push_back({i,nxt[i]});
    }
    auto cmp=[&](const pair <int,int>& A,const pair <int,int>& B)
    {
        return A.second-A.first<B.second-B.first;
    };
    sort(vec.begin(),vec.end(),cmp);
    init(n);
    for (auto [x,y]:vec)
    {
        int L=pre[x]+1,R=nxt[y]-1;
        if (a[x]<a[y])
        {
            int l=L,r=x,mid,res=-1;
            while (l<=r)
            if (query(mid=l+r>>1,R)==make_pair(x,y)) res=mid,r=mid-1; else l=mid+1;
            L=res;
        } else
        {
            int l=y,r=R,mid,res=-1;
            while (l<=r)
            if (query(L,mid=l+r>>1)==make_pair(x,y)) res=mid,l=mid+1; else r=mid-1;
            R=res;
        }
        ans[F(x,y)]+=1LL*(x-L+1)*(R-y+1);
        // printf("(%d, %d): f = %d, interval = [%d, %d], val = %d\n",x,y,F(x,y),L,R,(x-L+1)*(R-y+1));
    }
    for (RI i=1;i<=n;++i) printf("%lld\n",ans[i]);
    return 0;
}

C. Sum of Three Inversions

用 Meet in middle 解决 counting 问题吗,哈基闪你这家伙……

考虑把每个位置的 \(6\) 种排列分为两组,上面为 A 组,下面为 B 组:

\[A=\{(1,2,3),(2,3,1),(3,1,2)\}\\ B=\{(1,3,2),(2,1,3),(3,2,1)\} \]

此时有一个很好的性质,任选 \(a\in A,b\in B\),则排列 \(a,b\) 产生的逆序对数量必定为 \(1\),不论谁在前谁在后

(本质是因为此时 \(a,b\) 必然有一个位置上的数相同,且另外两个位置是交换后的结果)

因此我们可以把原问题拆成两个独立的子问题,总逆序对数量等于 A,B 两组内部的逆序对数量,再加上 \(A,B\) 数量的乘积

考虑 DP 处理 A 组内部的情形,B 组同理,令 \(f[len][a][b][inv]\) 表示长度为 \(len\),放了 \(a\)\((1,2,3)\)\(b\)\((2,3,1)\),逆序对总数为 \(inv\) 的方案数,转移非常显然

最后合并的时候注意要乘一个组合数,总复杂度 \(O(n^5)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=52,S=23500;
int n,x,y,k,mod,A[S][N*N*3/2],B[S][N*N*3/2],id[N][N][N],C[N][N];
const int coef[3][3]={{0,1,2},{2,0,1},{1,2,0}};
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
inline void DP(int f[S][N*N*3/2],CI n,CI m)
{
    f[id[0][0][0]][0]=1;
    for (RI len=0;len<n;++len)
    {
        for (RI a=0;a<=len;++a)
        for (RI b=0;a+b<=len;++b)
        {
            int c=len-a-b;
            int ID=id[len][a][b];
            for (RI inv=0;inv<=m;++inv)
            {
                if (f[ID][inv]==0) continue;
                int pre[3]={a,b,c};
                int tmp=inv; // append 0
                for (RI i=0;i<3;++i) tmp+=pre[i]*coef[i][0];
                if (tmp<=m) inc(f[id[len+1][a+1][b]][tmp],f[ID][inv]);
                tmp=inv; //append 1
                for (RI i=0;i<3;++i) tmp+=pre[i]*coef[i][1];
                if (tmp<=m) inc(f[id[len+1][a][b+1]][tmp],f[ID][inv]);
                tmp=inv; //append 2
                for (RI i=0;i<3;++i) tmp+=pre[i]*coef[i][2];
                if (tmp<=m) inc(f[id[len+1][a][b]][tmp],f[ID][inv]);
            }
        }
    }
}
int main()
{
    // int cnt=0;
    // for (RI len=0;len<=50;++len)
    // for (RI a=0;a<=len;++a)
    // for (RI b=0;a+b<=len;++b)
    // ++cnt;
    // return printf("%d\n",cnt),0;
    scanf("%d%d%d%d%d",&n,&x,&y,&k,&mod);
    int cnt=0;
    for (RI len=0;len<=n;++len)
    for (RI a=0;a<=len;++a)
    for (RI b=0;a+b<=len;++b)
    id[len][a][b]=++cnt;
    C[0][0]=1;
    for (RI i=1;i<=n;++i)
    {
        C[i][0]=1;
        for (RI j=1;j<=i;++j)
        {
            C[i][j]=C[i-1][j-1];
            inc(C[i][j],C[i-1][j]);
        }
    }
    DP(A,n,k); DP(B,n,k); int ans=0;
    for (RI lena=0;lena<=n;++lena)
    {
        int lenb=n-lena;
        for (RI inva=0;inva<=k;++inva)
        {
            int invb=k-inva-lena*lenb;
            if (invb<0||invb>k) continue;
            for (RI a=0;a<=x;++a)
            for (RI b=0;b<=y;++b)
            inc(ans,1LL*A[id[lena][a][b]][inva]*B[id[lenb][x-a][y-b]][invb]%mod*C[n][lena]%mod);
        }
    }
    return printf("%d\n",ans),0;
}

F. Decimal Pyramid

从右到左把原序列按照下标标为第 \(0\sim n-1\) 位,最终序列从右到左按照下标标为 \(0\sim 2^{n-1}-1\)

手玩一波可以发现以下性质:第 \(i\) 位数最后出现的下标 \(mask\) 满足 \(\operatorname{popcount}(mask)=i\)

因此考虑用以下 DP 统计贡献,令 \(f_{i,j}\) 表示长为 \(i\) 的二进制串,恰好有 \(j\) 位为 \(1\) 的下标贡献和

当新加入某一位 \(i+1\) 时,它对应的贡献是 \(10^{2^{i+1}}\),因此转移为:

\[f_{i,j}\to f_{i+1,j}\\ f_{i,j}\times 10^{2^{i+1}}\to f_{i+1,j+1} \]

最后所有的 \(f_{n-1,i}\) 即为所求,不难发现这个 DP 是经典的分治 NTT 形式

具体地,令 \(F(x)=\prod_{i=0}^{n-2} (10^{2^i}x+1)\),最后第 \(i\) 位的贡献就是 \(F(x)[x^i]\)

总复杂度 \(O(n\log^2 n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353,mod2=mod-1;
int n; char s[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline int quick_pow2(int x,int p=mod2-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod2) if (p&1) mul=1LL*mul*x%mod2; return mul;
}
inline int sum(CI x,CI y)
{
    return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
    return x-y<0?x-y+mod:x-y;
}
namespace Poly
{
    int rev[N<<3],lim,p;
    inline void init(CI n)
    {
        for (lim=1,p=0;lim<=n;lim<<=1,++p);
        for (RI i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1);
    }
    inline void NTT(int* f,CI opt)
    {
        for (RI i=0;i<lim;++i) if (i<rev[i]) swap(f[i],f[rev[i]]);
        for (RI i=1;i<lim;i<<=1)
        {
            int m=i<<1,D=quick_pow(3,opt==1?(mod-1)/m:mod-1-(mod-1)/m);
            for (RI j=0;j<lim;j+=m)
            {
                int W=1;
                for (RI k=0;k<i;++k,W=1LL*W*D%mod)
                {
                    int x=f[j+k],y=1LL*f[i+j+k]*W%mod;
                    f[j+k]=sum(x,y); f[i+j+k]=sub(x,y);
                }
            }
        }
        if (opt==-1)
        {
            int Div=quick_pow(lim);
            for (RI i=0;i<lim;++i) f[i]=1LL*f[i]*Div%mod;
        }
    }
};
inline vector <int> merge(const vector <int>& A,const vector <int>& B)
{
    static int ta[N<<3],tb[N<<3];
    int n=A.size(),m=B.size(); Poly::init(n+m-1);
    for (RI i=0;i<n;++i) ta[i]=A[i];
    for (RI i=n;i<Poly::lim;++i) ta[i]=0;
    for (RI i=0;i<m;++i) tb[i]=B[i];
    for (RI i=m;i<Poly::lim;++i) tb[i]=0;
    Poly::NTT(ta,1); Poly::NTT(tb,1);
    for (RI i=0;i<Poly::lim;++i) ta[i]=1LL*ta[i]*tb[i]%mod;
    Poly::NTT(ta,-1); vector <int> C;
    for (RI i=0;i<n+m-1;++i) C.push_back(ta[i]);
    return C;
}
inline vector <int> solve(CI l,CI r)
{
    if (l==r) return vector <int>{1,quick_pow(10,(quick_pow2(2,l-1)+mod2)%mod2)};
    int mid=l+r>>1; return merge(solve(l,mid),solve(mid+1,r));
}
int main()
{
    scanf("%d%s",&n,s+1);
    if (n==1) return putchar(s[1]),0;
    vector <int> res=solve(1,n-1);
    // for (RI i=0;i<=n;++i) printf("x[%d] : %d\n",i,res[i]);
    int ans=0;
    for (RI i=1;i<=n;++i)
    ans=sum(ans,1LL*(s[i]-'0')*res[n-i]%mod);
    return printf("%d\n",ans),0;
}

G. Don't Make Zero

这题队友讨论+写的,我连题意都不知道

#include <bits/stdc++.h>

constexpr bool debug = false;

std::mt19937 rng(114514);

int read_sign() {
    static std::string s;
    if constexpr (debug) {
        char c = (rng() & 1) ? '+' : '-';
        std::cout << c << std::endl;
        return (c == '+') ? 1 : -1;
    } else {
        std::cin >> s;
        return (s[0] == '+') ? 1 : -1;
    }
}

int n, x;
std::bitset<10001> bs1, bs2;
void wra(int a) {
    if constexpr (debug) {
        assert(1 <= std::abs(a) && std::abs(a) <= n);
        if(a > 0) bs1 |= bs1 << a;
        else      bs2 |= bs2 << (-a);
        auto bs = bs1 & bs2;
        bs[0] = 0;
        assert(!bs.any());
    }
    
    std::cout << a << std::endl;
    return ;
}

void work() {
    if constexpr (debug) {
        n = std::uniform_int_distribution{1, 10}(rng);
        x = (int)std::sqrt(n) * 2 - 1;
        std::cerr << "n = " << n << std::endl;
        std::cerr << "x = " << x << std::endl;
    } else {
        std::cin >> n >> x;
    }

    if constexpr (debug) {
        bs1.reset(), bs2.reset();
        bs1[0] = bs2[0] = 1;
    }
    
    int pos_cnt = 0, neg_cnt = 0, sign;
    const int h = (x + 1) / 2;
    std::queue<int> pos_q;
    std::queue<int> neg_q;

    for(int i = h + 1; i <= n; i += h) pos_q.push(i);
    for(int i = n / h * h + 2; i >= h; i -= h) if(i <= n) pos_q.push(i);
    pos_q.push(1);
    pos_q.push(2);
    
    for(int i = h; i <= n; i += h) neg_q.push(i);
    for(int i = n / h * h - 1; i >= 1; i -= h) if(i <= n) neg_q.push(i);
    
    // std::cerr << "pos_q.size() = " << pos_q.size() << char(10);
    // std::cerr << "neg_q.size() = " << neg_q.size() << char(10);

    while(x--) {
        sign = read_sign();
        if(sign > 0) wra( pos_q.front()), pos_q.pop();
        else         wra(-neg_q.front()), neg_q.pop();
    }


}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T;
    while(T--) work();
    return 0;
}

K. Two-Way Communication

很有趣的交互通信题,使我的大脑旋转,留着等队友补了(会赢吗)


Postscript

感觉这场可做的题就只有上面 5 个,只能说霓虹场罪大恶极啊

posted @ 2026-01-27 17:38  空気力学の詩  阅读(6)  评论(0)    收藏  举报