• Gym102956 部分题解


    Contest Link

    带更。

    感谢 @hht2005 的帮助。

    B. Beautiful Sequence Unraveling

    定义一个长度为 \(n\) 好序列 \(a\) 是不存在一个位置 \(1\le i<n\) 使得 \(\max\{a_1,\ldots,a_i\}=\min\{a_{i+1},\ldots,a_n\}\) 的序列。
    对长度为 \(n\),值域为 \([1,k]\) 的好序列计数,对 \(p\) 取模并输出。
    \(1\le n\le 400\)\(1\le k\le 10^8\)\(998244353\le p\le 10^9+9\)\(p\in\text{prime}\)

    Solution

    \(k\) 是坑你的,如果考虑和 \(k\) 有关的直接凉凉。
    考虑回避 \(k\),不难发现合法的序列出现的不同的数字只有 \(n\) 种,直接离散化。
    考虑 \(f_{i,j}\) 表示一个长度为 \(i\) 的序列,值域为 \([1,j]\) 的好序列个数。
    好序列并不好求,但考虑一个不好序列 \(a\),设其最大的位置 \(p\) 使得 \(\max\{a_1,\ldots,a_p\}=\min\{a_{p+1},\ldots,a_n\}\),容易发现一个大性质:\(a_{p+1},\ldots,a_n\) 是一个好序列,如果其不是一个好序列的话,\(p\) 便不是最大的。
    枚举最大的位置 \(p\),枚举其值 \(m\) 可以得到 DP 式子:

    \[f_{i,j}=j^i-\sum^{i-1}_{p=1}\sum^j_{m=1}(m^p-(m-1)^p)(f_{i-p,j-m+1}-f_{i-p,j-m}) \]

    使用前缀和优化可以做到 \(O(n^3)\)
    考虑再设 \(g_i\) 表示长度为 \(n\) 的序列,值域为 \([1,i]\) 的好序列个数:

    \[g_i=f_{n,i}-\sum^{i-1}_{j=1}\binom i j g_j \]

    所以答案为:\(\displaystyle \sum^n_{i=1}\binom k i g_i\)
    组合数暴力计算,时间复杂度 \(O(n^3)\)

    Code
    const int N=400;
    int n,K,mod,f[N+10][N+10],ans,g[N+10],sum[N+10][N+10][N+10];
    void Add(int &x,int y) {
        x+=y;
        if(x>=mod) x-=mod;
    }
    void Del(int &x,int y) {
        x-=y;
        if(x<0) x+=mod;
    }
    int fpow(int x,int y) {
        int ret=1;
        for(;y;y>>=1) {
            if(y&1) ret=1ll*ret*x%mod;
            x=1ll*x*x%mod;
        }
        return ret;
    }
    int C(int n,int m) {
        int ret=1,fm=1;
        for(int i=n;i>=n-m+1;i--) ret=1ll*ret*i%mod;
        for(int i=1;i<=m;i++) fm=1ll*fm*i%mod;
        return 1ll*ret*fpow(fm,mod-2)%mod;
    }
    //n!/(n-m)!/m!
    int po[N+10][N+10],inv[N+10][N+10];
    void init() {
        for(int i=1;i<=n;i++) {
            po[i][0]=inv[i][0]=1;
            inv[i][1]=fpow(po[i][1]=i,mod-2);
            for(int j=2;j<=n;j++) po[i][j]=1ll*po[i][j-1]*i%mod,inv[i][j]=1ll*inv[i][j-1]*inv[i][1]%mod;
        }
    }
    int main() {
        n=read(),K=read(),mod=read();
        init();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) {
                f[i][j]=po[j][i];
                for(int t=1;t<=j;t++) {
                    int A=po[t][i],B=po[t-1][i];
                    int sum1=(sum[i-1][j-t][t]-sum[i-1][j-t+1][t]+mod)%mod;
                    int sum2=(sum[i-1][j-t+1][t-1]-sum[i-1][j-t][t-1]+mod)%mod;
                    Add(f[i][j],(1ll*A*sum1%mod+1ll*B*sum2%mod)%mod);
                }
                for(int t=1;t<=n;t++) sum[i][j][t]=(sum[i-1][j][t]+1ll*f[i][j]*inv[t][i]%mod)%mod;
            }
        for(int i=1;i<=n;i++) {
            g[i]=f[n][i];
            for(int j=1;j<i;j++) Del(g[i],1ll*C(i,j)*g[j]%mod);
        }
        for(int i=1;i<=n;i++) Add(ans,1ll*C(K,i)*g[i]%mod);
        write((ans+mod)%mod),enter;
        return WDNMD;
    }
    

    C. Brave Seekers of Unicorns

    定义一个好序列满足如下条件:

    • 序列非空。
    • 没有三个连续的元素异或和 \(=0\)
    • 序列递增。
    • 序列的值域为 \([1,n]\)

    给定 \(n\),对好序列计数,答案对 \(998244353\) 取模。

    Solution

    \(f_i\) 表示以 \(i\) 结尾的所有方案数,有:

    \[f_i=\sum^{i-1}_{j=1}f_j-[j\oplus i<j]f_{j\oplus i} \]

    \(f_j\) 可以一波前缀和带走,考虑后面,可以变形为 \(\sum_{k<k\oplus i<i} f_k\)
    因为要考虑 \(i\oplus j\oplus k=0\) 的情况,所以一个位上最多只有两个 \(1\),枚举二进制上 \(i\) 的除最高位的 \(1\)\(d\),钦定 \(j\) 的第 \(d\) 位是 \(1\)\(k\) 的第 \(d\) 位为 \(0\),那么第 \(d\) 位往后都可以随便乱取。
    那么 \(k\) 的范围就是 \([2^d,2^{d+1}-1]\),也可以前缀和了。
    时间复杂度 \(O(n\log n)\)

    Code
    const int N=1e6,mod=998244353;
    ll f[N+10],n,sum[N+10];
    int main() {
        scanf("%d",&n);
        f[1]=1;sum[1]=1;
        for(int i=2;i<=n;i++) {
            f[i]=sum[i-1]+1;
            int j;
            for(j=20;j>=0;j--)
                if(i&(1<<j)) break;
            j--;
            for(;j>=0;j--) if(i&(1<<j)) f[i]=(f[i]-sum[(1<<(j+1))-1]+sum[(1<<j)-1]+mod)%mod;
            sum[i]=(f[i]+sum[i-1])%mod;
        }
        printf("%lld\n",sum[n]%mod);
    }
    

    D. Bank Security Unification

    从一个长度为 \(n\) 序列 \(a\) 中取出一个子序列,使得相邻两个元素的按位与值的和最大。

    \(2\le n\le 10^6\)\(0\le a_i\le 10^{12}\)

    Solution

    我们希望按位与和最大,就是在希望相邻两个元素的二进制位数尽可能相同。
    考虑 \(f_i\) 表示末位为 \(i\) 的子序列答案最大是多少,朴素转移是 \(O(n^2)\) 的。
    考虑剪枝,如果考虑当前的一位,我们想从哪里转移而来,显然是与之相同的位,如果选这些位靠后一定较靠前的优秀。
    所以记录 \(las_{i,0/1}\) 表示第 \(i\) 位上一个是 \(0/1\) 在哪里,每次只从对应的 \(las\) 转移而来。
    时间复杂度 \(O(n\log a_i)\)

    Code
    const int N=1e6;
    int n;
    ll f[N+10],g[N+10],ans;
    int las[44][2];
    int main() {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&f[i]);
        g[2]=(f[1]&f[2]);
        for(int i=1;i<=2;i++)
            for(int j=39;j>=0;j--)
                if(f[i]&(1ll<<j)) las[j][1]=i;
                else las[j][0]=i;
        for(int i=3;i<=n;i++) {
            for(int j=39;j>=0;j--) {
                int zz=(bool)(f[i]&(1ll<<j));
                g[i]=max(g[i],g[las[j][zz]]+(f[las[j][zz]]&f[i]));
            }
            for(int j=39;j>=0;j--)
                if(f[i]&(1ll<<j)) las[j][1]=i;
                else las[j][0]=i;
        }
        for(int i=1;i<=n;i++) ans=max(ans,g[i]);
        printf("%lld\n",ans);
    }
    

    E. Brief Statements Union

    给定 \(n\) 个数字,\(k\) 条限制,限制形如 \(a_l\And a_{l+1}\And\ldots\And a_{r}=x\)

    对于某一条限制,如果可以删去这条限制,使得存在满足条件的序列,称这是好的。

    输出哪些序列是好的,哪些序列是坏的。

    \(1\le n,k\le 10^6\)\(0\le x_i\le 10^{18}\)

    Solution

    按位考虑,那么剩下的限制分为两类:\(1\) 限制和 \(0\) 限制。
    覆盖掉 \(1\) 限制,考虑 \(0\) 限制的冲突个数:

    • \(0\):全都可以。
    • \(1\):那个冲突的是内鬼,刀了。
    • \(>1\):咋整都不行。

    \(0\) 限制的情况就考虑完了。
    考虑如何删除 \(1\) 限制,对于每一个只被 \(1\) 限制覆盖一次的点,我们可以删除这个 \(1\) 限制,来满足若干 \(0\) 限制。
    对于某一个 \(0\) 限制,我们将能够删除的 \(1\) 限制,使得该 \(0\) 限制合法化的 \(1\) 限制取出,对所有 \(0\) 限制的如此集合取交。
    运用差分可以做到 \(O(n\log x_i)\)

    Code
    const int N=1e6;
    int n,K;
    int ql[N+10],qr[N+10];
    ll qx[N+10],f[N+10],g[N+10];
    int nxt[N+10],pre[N+10],ans[N+10];
    int vio[N+10],tot,id,pos[N+10],seg[N+10];
    int main() {
        scanf("%d %d",&n,&K);
        for(int i=1;i<=K;i++) scanf("%d %d %lld",&ql[i],&qr[i],&qx[i]);
        nxt[n+1]=n+1,pre[0]=0;
        for(int k=0;k<60;k++) {
            for(int i=1;i<=n;i++) f[i]=g[i]=0;
            for(int i=1;i<=K;i++)
                if((1ll<<k)&qx[i])
                    f[ql[i]]++,f[qr[i]+1]--,
                    g[ql[i]]+=i,g[qr[i]+1]-=i;
            for(int i=1;i<=n;i++) f[i]+=f[i-1],g[i]+=g[i-1];
            for(int i=n;i>=1;i--) nxt[i]=f[i]?nxt[i+1]:i;
            id=tot=0;
            for(int i=1;i<=K;i++)
                if(!((1ll<<k)&qx[i]))
                    if(nxt[ql[i]]>qr[i])
                        vio[++tot]=i;
            if(tot==0) {
                for(int i=1;i<=K;i++) ans[i]++;
                continue;
            }
            if(tot==1) ans[vio[1]]++;
            for(int i=1;i<=n;i++)
                if(f[i]==1&&!pos[g[i]])
                    seg[pos[g[i]]=++id]=g[i];
            for(int i=1;i<=n;i++) pre[i]=(f[i]==1)?i:pre[i-1];
            for(int i=n;i>=1;i--) nxt[i]=(f[i]==1)?i:nxt[i+1];
            int l=0,r=id;
            for(int i=1;i<=tot;i++) {
                int u=vio[i];
                if(nxt[ql[u]]<=qr[u]) l=max(l,pos[g[nxt[ql[u]]]]);
                else l=id+1;
                if(pre[qr[u]]>=ql[u]) r=min(r,pos[g[pre[qr[u]]]]);
                else r=0;
            }
            for(int i=l;i<=r;i++) ans[seg[i]]++;
            for(int i=1;i<=K;i++) pos[i]=0;
        }
        for(int i=1;i<=K;i++)
            if(ans[i]==60) putchar('1');
            else putchar('0');
        puts("");
    }
    

    F. Border Similarity Undertaking

    给定一个字母矩形,求出其有多少的子矩形满足四周都是相同字符。

    \(1\le n,m\le 2000\)

    Solution

    考虑矩阵分治,即对一个矩阵的长宽进行分治。
    对于每一个矩阵进行分治,考虑计算跨过中心线的矩阵个数。
    这个过程可以预处理出一个点向上,向下,向左,向右的延伸距离,可以暴力处理。
    中心线左右两边分别处理,处理出左右两边能组成的矩形数。
    代码细节较多较复杂,时间复杂度 \(O(nm\log n)\)

    Code
    const int N=2000;
    int n,m;
    char ch[N+10][N+10];
    int up[N+10][N+10],dn[N+10][N+10],lef[N+10][N+10],rig[N+10][N+10];
    int al[N+10][N+10],ar[N+10][N+10];
    ll ans;
    void solve(int l1,int r1,int l2,int r2) {
        if(l1==r1||l2==r2) return;
        if(r1-l1+1<=r2-l2+1) {
            int mid=(r2+l2)>>1,L,R;
            for(int i=l1;i<=r1;i++)
                for(int j=l1;j<=r1;j++)
                    al[i][j]=ar[i][j]=0;
            for(int i=l1;i<=r1;i++) {
                for(int j=mid;j>=l2&&j>=lef[i][mid];j--)
                    al[i][min(dn[i][j],r1)]++,
                    al[i][max(up[i][j],l1)]++;
                for(int j=r1-1;j>=i;j--) al[i][j]+=al[i][j+1];
                for(int j=l1+1;j<=i;j++) al[i][j]+=al[i][j-1];
                for(int j=mid+1;j<=r2&&j<=rig[i][mid+1];j++) 
                    ar[i][min(dn[i][j],r1)]++,
                    ar[i][max(up[i][j],l1)]++;
                for(int j=r1-1;j>=i;j--) ar[i][j]+=ar[i][j+1];
                for(int j=l1+1;j<=i;j++) ar[i][j]+=ar[i][j-1];
            }
            for(int i=l1;i<=r1;i++)
                for(int j=i+1;j<=r1;j++) {
                    if(ch[i][mid]!=ch[j][mid]) continue;
                    if(ch[i][mid]!=ch[i][mid+1]) continue;
                    if(ch[j][mid]!=ch[j][mid+1]) continue;
                    L=lef[i][mid]>=lef[j][mid]?al[i][j]:al[j][i];
                    R=rig[i][mid+1]<=rig[j][mid+1]?ar[i][j]:ar[j][i];
                    ans+=1ll*L*R;
                }
            solve(l1,r1,l2,mid);
            solve(l1,r1,mid+1,r2);
        }
        else {
            int mid=(l1+r1)>>1,L,R;
            for(int i=l2;i<=r2;i++)
                for(int j=l2;j<=r2;j++)
                    al[i][j]=ar[i][j]=0;
            for(int i=l2;i<=r2;i++) {
                for(int j=mid;j>=l1&&j>=up[mid][i];j--)
                    al[i][min(rig[j][i],r2)]++,
                    al[i][max(lef[j][i],l2)]++;
                for(int j=r2-1;j>=i;j--) al[i][j]+=al[i][j+1];
                for(int j=l2+1;j<=i;j++) al[i][j]+=al[i][j-1];
                for(int j=mid+1;j<=r1&&j<=dn[mid+1][i];j++)
                    ar[i][min(rig[j][i],r2)]++,
                    ar[i][max(lef[j][i],l2)]++;
                for(int j=r2-1;j>=i;j--) ar[i][j]+=ar[i][j+1];
                for(int j=l2+1;j<=i;j++) ar[i][j]+=ar[i][j-1];
            }
            for(int i=l2;i<=r2;i++)
                for(int j=i+1;j<=r2;j++) {
                    if(ch[mid][i]!=ch[mid][j]) continue;
                    if(ch[mid][i]!=ch[mid+1][i]) continue;
                    if(ch[mid][j]!=ch[mid+1][j]) continue;
                    L=up[mid][i]>=up[mid][j]?al[i][j]:al[j][i];
                    R=dn[mid][i]<=dn[mid][j]?ar[i][j]:ar[j][i];
                    ans+=1ll*L*R;
                }
            solve(l1,mid,l2,r2);
            solve(mid+1,r1,l2,r2);
        }
    }
    int main() {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                cin>>ch[i][j];
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++) {
                up[i][j]=i>1&&ch[i][j]==ch[i-1][j]?up[i-1][j]:i;
                lef[i][j]=j>1&&ch[i][j]==ch[i][j-1]?lef[i][j-1]:j;
            }
        for(int i=n;i>=1;i--)
            for(int j=m;j>=1;j--) {
                dn[i][j]=i<n&&ch[i][j]==ch[i+1][j]?dn[i+1][j]:i;
                rig[i][j]=j<m&&ch[i][j]==ch[i][j+1]?rig[i][j+1]:j;
            }
        solve(1,n,1,m);
        printf("%lld\n",ans);
    }
    

    G. Biological Software Utilities

    求出存在完美匹配的 \(n\) 个点的树的数量。\(1\le n\le 10^6\)

    Solution

    把两个点绑一块,共有 \(n!!=1\times 3\times \ldots \times (n-1)\) 种方法。
    绑一块的点能组成的树,有 \((\frac{n}{2})^{n/2-2}\) 种。
    点与点之间相互连接,有 \(4^{n/2-1}\) 种。
    乘起来,没了,时间复杂度 \(O(n)\)

    Code
    const int N=1e6,mod=998244353;
    int n;
    int ans=1;
    int fpow(int x,int y) {
        if(y<0) return 1;
        int ret=1;
        for(;y;y>>=1) {
            if(y&1) ret=1ll*ret*x%mod;
            x=1ll*x*x%mod;
        }
        return ret;
    }
    int main() {
        scanf("%d",&n);
        if(n%2) {puts("0");return 0;}
        for(int i=1;i<=n;i+=2) ans=1ll*ans*i%mod;
        ans=1ll*ans*fpow(n/2,n/2-2)%mod*fpow(4,n/2-1)%mod;
        printf("%d\n",ans);
    }
    

    H. Bytelandia States Union

    \((x_1,y_1)\) 走到 \((x_2,y_2)\),求最短路径。
    其中从 \((x,y)\) 向四个方向走的方案不一,具体看原题。
    \(1\le T\le 5\times 10^4\)\(1\le x_1,y_1,x_2,y_2\le 10^9\)

    Solution

    诈骗题,我可以很负责任的告诉你,一条路径 \((x_1,y_1),(x_2,y_2),(x_3,y_3),\ldots,(x_k,y_k)\) 的费用是 \(x^2_ky^2_k-x^2_1y^2_1-x_k^2-x_k^2+\sum^{k}_{i=1}x^2_i+y^2_i\),证明?可以问hht2005,总之我不会。
    最小化路径,只需最小化后面的值,所以只要尽可能挨近 \(x=y\) 这条直线就可以了。
    大力分讨即可,单次时间复杂度 \(O(1)\)

    Code
    void solve() {
        int x1,y1,x2,y2;
        scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2);
        ans=(p2(1ll*x2*y2%mod)-p2(1ll*x1*y1%mod)+mod)%mod;
        ans=(ans-p2(x2)-p2(y2)+mod)%mod;
        if(x1>x2) swap(x1,x2),swap(y1,y2);
        if(y2>y1)
            if(x1<y1) {
                int nx=min(y1,x2);
                Add(ans,1ll*(nx-x1)*p2(y1)%mod);
                Add(ans,sum(x1,nx-1));
                if(nx==x2) {
                    Add(ans,1ll*(y2-y1+1)*p2(x2)%mod);
                    Add(ans,sum(y1,y2));
                }
                else {
                    int nxt=min(x2,y2);
                    Add(ans,(sum(nx,nxt-1)*3ll%mod+sum(nx+1,nxt))%mod);
                    if(nxt==y2) {
                        Add(ans,1ll*p2(y2)*(x2-nxt+1)%mod);
                        Add(ans,sum(nxt,x2));
                    }
                    else {
                        Add(ans,1ll*p2(x2)*(y2-nxt+1)%mod);
                        Add(ans,sum(nxt,y2));
                    }
                }
            }
            else {
                int ny=min(x1,y2);
                Add(ans,1ll*(ny-y1)*p2(x1)%mod);
                Add(ans,sum(y1,ny-1));
                if(ny==y2) {
                    Add(ans,1ll*(x2-x1+1)*p2(y2)%mod);
                    Add(ans,sum(x1,x2));
                }
                else {
                    int nxt=min(x2,y2);
                    Add(ans,sum(ny,nxt-1)*2ll%mod);
                    Add(ans,sum(ny,nxt-1)+sum(ny+1,nxt));
                    if(nxt==x2) {
                        Add(ans,1ll*p2(x2)*(y2-nxt+1)%mod);
                        Add(ans,sum(nxt,y2));
                    }
                    else {
                        Add(ans,1ll*p2(y2)*(x2-nxt+1)%mod);
                        Add(ans,sum(nxt,x2));
                    }
                }
            }
        else {
            Add(ans,1ll*(y1-y2)*p2(x1)%mod);
            Add(ans,sum(y2+1,y1));
            Add(ans,1ll*(x2-x1+1)*p2(y2)%mod);
            Add(ans,sum(x1,x2));
        }
        printf("%lld\n",(ans%mod+mod)%mod);
    }
    signed main() {
        ll t;scanf("%lld",&t);
        while(t--) solve();    
    }
    

    I. Binary Supersonic Utahraptors

    Alice 和 Bob 又在玩游戏,Alice 有黑与白两种物品,Bob 也有黑与白两种物品。

    \(k\) 个回合,每一回合中,Alice 先送给 Bob \(s_i\) 个物品,Bob 则回礼 \(s_i\) 个物品。

    Alice 想让 Alice 的黑物品与 Bob 的白物品差尽可能小,Bob 则想让上述东西最大。

    求最后的这个值。

    \(1\le n,m,k\le 3\times 10^5\)

    Solution

    人定不胜天,Alice 和 Bob 并不能决定这一切。
    答案恒定不变,是总的黑物品数与 \(n\) 的差。
    时间复杂度 \(O(1)\)

    Code
    #include<bits/stdc++.h>
    using namespace std;
    int n,m,k,r,y;
    int main() {
        scanf("%d %d %d",&n,&m,&k);
        for(int i=1,x;i<=n;i++) {
            scanf("%d",&x);
            if(x==1) r++;
        }
        for(int i=1,x;i<=m;i++) {
            scanf("%d",&x);
            if(x==1) r++;
        }
        printf("%d\n",abs(n-r));
    }
    

    J. Burnished Security Updates

    求一张图的最小的覆盖所有边的独立集,\(2\le n\le 3\times 10^5\)\(1\le m\le 3\times 10^5\)

    Solution

    要覆盖所有边,所以相邻的两个点一定取得状态不同。
    考虑二分图染色,如果原图有奇环,则一定无解,否则取较小的那种颜色的点集。
    时间复杂度 \(O(n+m)\)

    Code
    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int N=3e5;
    int n,m,vis[N+10],cnt[4],ans;
    vector<int> G[N+10];
    bool dfs(int u,int col) {
        vis[u]=col;cnt[col]++;
        bool fl=1;
        for(auto v:G[u])
            if(!vis[v]) fl&=dfs(v,3-col);
            else if(vis[v]==col) return 0;
        return 1;
    }
    int main() {
        scanf("%d %d",&n,&m);
        for(int i=1,x,y;i<=m;i++) {
            scanf("%d %d",&x,&y);
            G[x].pb(y),G[y].pb(x);
        }
        for(int i=1;i<=n;i++)
            if(!vis[i]) {
                cnt[1]=cnt[2]=0;
                if(!dfs(i,1)) {
                    puts("-1");
                    return 0;
                }
                ans+=min(cnt[1],cnt[2]);
            }
        printf("%d\n",ans);
    }
    

    K. Bookcase Solidity United

    \(n\) 块木板,第 \(i\) 块木板有稳定性 \(a_i\),木板从高到低排列。
    在木板上放置铱球,第 \(i\) 块木板被放了大于等于 \(a_i\) 个铱球后会碎裂,有 \(\lfloor\frac k 2\rfloor\) 会下坠到下一块木板上。
    求至少需要多少铱球才可以砸碎前 \(i\) 块木板。
    \(1\le n\le 70\)\(1\le a_i\le 150\)

    Solution

    \(f_{l,r,k}\) 表示砸碎 \([l,r]\) 的木板,留下 \(k\) 个球的最小球数。
    有一个显然的转移:\(f_{l,r,k}+\max(a_{r+1}-k,0)\to f_{l,r+1,\max(k,a_{r+1})/2}\),表示继承下若干个球来砸下面的。
    同时,我们考虑分段砸,\(f_{l,r,k_1+k_2}=f_{l,mid,k_1}+f_{mid+1,r,k_2}\)
    直接暴力转移是 \(O(n^3m^2)\) 的。

    Code
    #include<bits/stdc++.h>
    using namespace std;
    const int N=200,M=200;
    int n,a[N+10],m;
    int f[N+10][N+10][M+10];
    void chkmin(int &x,int y) {
        if(x>y) x=y;
    }
    int main() {
        scanf("%d",&n);
        memset(f,0x3f,sizeof f);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][i][a[i]/2]=a[i],m=max(m,a[i]);
        for(int len=2;len<=n;len++)
            for(int l=1;l+len-1<=n;l++) {
                int r=l+len-1;
                for(int i=0;i<=m;i++) chkmin(f[l][r][max(i,a[r])/2],f[l][r-1][i]+max(0,a[r]-i));
                for(int i=0;i<=m;i++)
                    for(int j=l;j<r;j++)
                        for(int k=0;k<=i;k++)
                            chkmin(f[l][r][i],f[l][j][k]+f[j+1][r][i-k]);
            }
        for(int i=1;i<=n;i++) {
            int ans=INT_MAX;
            for(int j=0;j<=m;j++) ans=min(ans,f[1][i][j]);
            printf("%d ",ans);
        }
        puts("");
        return 0;
    }
    

    M. Brilliant Sequence of Umbrellas

    构造一个长度至少为 \(\lceil \frac2 3 \sqrt{n}\rceil\) 的值域为 \([1,n]\) 递增序列,使得两两之间的 \(\gcd\) 也递增。
    \(1\le n\le 10^{12}\)

    Solution

    观察样例,考虑构造一个每隔两个数均互质的序列,然后将这个序列相邻两项的积作为原序列。
    这样一定满足 \(\gcd\) 递增。

    Code
    typedef long long ll;
    const int N=1e6;
    ll n,a[N+10],cnt,b[N+10];
    int main() {
        scanf("%lld",&n);
        b[++cnt]=1,b[++cnt]=1;
        int lim=ceil(2.0*sqrt(n)/3);
        for(ll i=2;cnt<=lim+1&&b[cnt]*i<=n;i++)
            if(__gcd(b[cnt-1],i)==1)
                b[++cnt]=i;
        printf("%lld\n",cnt-1);
        for(int i=1;i<cnt;i++) printf("%lld ",b[i]*b[i+1]);
        puts("");
    }
    

    N. Best Solution Unknown

    \(n\) 个人打比赛,每个人有个实力值,实力值大的会打败实力值小的,相等的都有机会赢。
    打赢一个人可以增加 \(1\) 的实力值,每个人只能和相邻的人打。
    每次随机取比赛,求会有哪些人可能赢。
    \(1\le n\le 10^6\)\(1\le a_i\le 10^9\)

    Solution

    考虑对于一个大段,一个人能不能赢,抽取出最大值,发现最大值分成两份,一份是左一份是右,要想赢得翻过最大值这座大山。
    那么我们就可以分治了,找出最大值来分治,每次记录左边或右边要至少多少才可以赢。
    分治的时候用 ST 表加速求最值就可以了。

    Code
    const int N=1e6;
    int a[N+10],n,Log[N+10],Max[N+10][30],id[N+10][30];
    bool nb[N+10];
    int query(int l,int r) {
        int k=Log[r-l+1];
        int ret=max(Max[l][k],Max[r-(1<<k)+1][k]);
        if(ret==Max[l][k]) return id[l][k];
        else return id[r-(1<<k)+1][k];
    }
    void dfs(int l,int r,int c) {
        if(l>r) return;
        if(l==r) {
            if(a[l]>=c) nb[l]=1;
            return;
        }
        int id=query(l,r);
        if(a[id]<c) return;
        nb[id]=1;
        dfs(l,id-1,max(c,a[id]-id+1+l));
        dfs(id+1,r,max(c,a[id]+id+1-r));
    }
    int main() {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        Log[1]=0;
        for(int i=2;i<=n;i++) Log[i]=Log[i/2]+1;
        for(int i=1;i<=n;i++) Max[i][0]=a[i],id[i][0]=i;
        for(int j=1;j<=20;j++)
            for(int i=1;i+(1<<(j-1))<=n;i++) {
                Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
                if(Max[i][j]==Max[i][j-1]) id[i][j]=id[i][j-1];
                else id[i][j]=id[i+(1<<(j-1))][j-1];
            }
        dfs(1,n,0);
        int cnt=0;
        for(int i=1;i<=n;i++) if(nb[i]) cnt++;
        printf("%d\n",cnt);
        for(int i=1;i<=n;i++) if(nb[i]) printf("%d ",i);
        puts("");
    }
    
  • 相关阅读:
    flask(十)使用alembic,进行数据库结构管理,升级,加表,加项
    Python sqlalchemy使用
    flask+script命令行交互工具
    flask+APScheduler 任务调度,计划任务,定时任务
    DBA日常工作职责
    Oracle 的 VKTM 进程
    linux
    UF2.0、O4、UFT、TA众明星背后的秘密
    ORA-01502: 索引或这类索引的分区处于不可用状态
    关于Optimizer_index_cost_adj参数的设置
  • 原文地址:https://www.cnblogs.com/cnyzz/p/15424408.html
Copyright © 2020-2023  润新知