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("");
}
posted @ 2021-11-01 16:57  cnyz  阅读(306)  评论(0编辑  收藏  举报