2022.10.17 CSP2022 模拟赛四

盒子

Source:CF1242C。

想了一半,麻。

考虑盒子 \(i\) 被移出去的数,设这个数是 \(x\),那么很必然的,一定需要有一个 \(x-\Delta_i\) 移进来,其中 \(Delta_i=S-s_i\)

将移出去的数与移进来数之间建一条有向边,这样会连出来一个大小为 \(\sum a_i\) 的一个有向图,有用的显然只有环。

将合法的环抽出来,环上每个点有颜色,问题变为选择若干个环使得所有颜色恰被覆盖一遍,可以状压 DP 解决,剩下的就是根据状压 DP 构造一下方案。

Code
#define int ll
const int K=16,N=750010;
int s[K],rid[N],col[N],to[N],f[1<<15],g[1<<15],pre[1<<15],p1[K],p2[K],deg[N];
map<int,int> id;
vi c[K];
void no() {puts("No"),exit(0);}
void find_loop(int fir,int u,int S) {
    if(u==fir&&!deg[u]) return f[S]=fir,void();
    deg[u]=0;
    int NS=(1<<(col[u]-1));
    if(NS&S) return;
    find_loop(fir,to[u],S|NS);
}
void construct(int S) {
    if(f[S]) {
        int u=f[S];
        while(1) {
            int v=to[u];
            p1[col[v]]=rid[v],p2[col[v]]=col[u];
            u=v;
            if(u==f[S]) return;
        }
        assert(0);
    }
    construct(S^pre[S]),construct(pre[S]);
}
signed main() {
    int K=read(),idc=0,S=0;
    FOR(i,1,K) {
        int l=read();
        FOR(j,1,l) {
            int x=read();
            id[x]=++idc,rid[idc]=x,col[idc]=i;
            s[i]+=x,S+=x;
        }
    }
    if(S%K!=0) no();
    S/=K;
    FOR(i,1,idc) {
        int u=i,v=id[S-s[col[i]]+rid[i]];
        if(!v) continue;
        to[u]=v,++deg[v];
    }
    list<int> qu;
    FOR(i,1,idc) if(!deg[i]) qu.pb(i);
    while(sz(qu)) {
        int u=qu.front();qu.pop_front();
        int v=to[u];
        if(v==0) continue;
        if(--deg[v]==0) qu.pb(v);
    }
    FOR(i,1,idc) if(deg[i]) find_loop(i,i,0);
    FOR(i,0,(1<<K)-1) {
        if(f[i]) {g[i]=1;continue;}
        for(int j=i;j;j=(j-1)&i) {
            if(g[j]&&g[j^i]) g[i]=1,pre[i]=j;
            if(g[i]) break;
        }
    }
    if(g[(1<<K)-1]) {
        construct((1<<K)-1);
        puts("Yes");
        FOR(i,1,K) printf("%lld %lld\n",p1[i],p2[i]);
    }
    else no();
}

爽串

Source:CF1286E

复读 jiangly 题解!!!1

我们考虑在加入字符后求出合法的 border 权值和。

维护 border 的集合,考虑加入字符后对 border 集合的变化:

  • 删除掉下一位不为 \(c\) 的 border。
  • 如果 \(c_i=c_0\),加入一个长度为 \(1\) 的 border。

你发现 border 的个数是 \(O(n)\) 的,所以这部分如果做得足够精准是线性的。

考虑快速找到需要被删除的 border,注意到其都在 fail 树上的向上路径上,我们可以预处理出对于每一个节点第一个与其后继字符不同的祖先,我们就可精准地找到每一个需要删除的 border。

权值的处理使用单调栈:删除 border,需要询问权值,这个可以二分处理;单调栈的删除,这个可以使用 map 暴力找到所有被弹出的权值,均摊也是对的。

总时间复杂度 \(O(n\log n)\)

Code
const int N=6e5+5;
int anc[N],nxt[N],w[N],st[N],top,ans26,ansB;
i128 ans;
map<int,int> cnt;
string s;
char c;
int query(int x) {return w[*lower_bound(st,st+top+1,x)];}
void write(i128 x) {
    if(x>9) write(x/10);
    putchar(x%10+'0');  
} 
int main() {
    int n=read();
    cin>>c;w[0]=read();
    s+=c,ans26=w[0]%26,ansB=ans=w[0];
    printf("%d\n",w[0]);
    st[++top]=0;
    ll sum=0;
    for(int i=1,j=0;i<n;i++) {
        cin>>c;w[i]=read();
        c=(c-'a'+ans26)%26+'a',s+=c,w[i]^=ansB;
        while(j>0&&s[j]!=c) j=nxt[j];
        if(c==s[j]) j++;
        nxt[i+1]=j;
        if(c==s[nxt[i]]) anc[i]=anc[nxt[i]];
        else anc[i]=nxt[i];
        for(int k=i;k;)
            if(s[k]==c) k=anc[k];
            else {
                int v=query(i-k);--cnt[v],sum-=v;
                if(cnt[v]==0) cnt.erase(v);
                k=nxt[k];
            }
        if(s[0]==c) ++cnt[w[i]],sum+=w[i];
        while(top&&w[i]<=w[st[top]]) top--;
        st[++top]=i;
        int nw=0;
        for(auto it=cnt.upper_bound(w[i]);it!=cnt.end();) {
            sum-=1ll*(it->fi-w[i])*it->se,nw+=it->se;
            auto j=next(it);cnt.erase(it),it=j;
        }
        cnt[w[i]]+=nw;
        ll res=w[st[1]]+sum;
        ans+=res,ans26=ans%26,ansB=ans&((1<<30)-1);
        write(ans),putchar('\n');
    }
}

棋盘

Source:CF1239E

贡献式是什么样子的,是 \(\min(\max_{i=1}^n(suf_i+pre_i))\) 其中 \(suf_i\) 是第二行的后缀和,\(pre_i\) 是第一行的前缀和。

我们证明几个性质:

一:第一行单调递增,第二行单调递减。

只证明前一句,后一句类似证明。

考虑调整证明,注意到若 \(i<j\)\(a_{1,i}>a_{1,j}\),交换 \(i,j\) 会使 \([i,j)\) 这一段答案变小,其余不变,故调整后不劣。

二:最小的数在 \(a_{1,1}\),次小的数在 \(a_{2,n}\)

证明:我们钦定最小的数在 \(a_{1,1}\),那么次小的数要么在 \(a_{2,n}\),要么在 \(a_{1,2}\),显然在 \(a_{2,n}\) 更优。

三:此时答案一定是 \(\max(suf_1+pre_n,suf_n+pre_1)\)

证明:考虑两个相邻路径的差,他们的差是 \(|a_{0,i+1}-a_{1,i}|\),因为 \(a_{0,i}\) 单增,\(a_{1,i}\) 单减,所以这个是凸的,最大值就在 \(1,n\) 取到。

故,我们使用背包求出上下差最小时的答案,这就是最优解,依据背包构造答案即可。

Code
const int N=26,M=50005;
int a[N*2],b[2][N],bel[2*N],c0,c1;
bitset<N*M> f[N*2][N];
int main() {
    int n=read();
    FOR(i,1,2*n) a[i]=read()+1;
    sort(a+1,a+2*n+1);
    f[2][0][0]=1;
    int S=0;
    FOR(i,3,2*n) {
        int up=min(i-2,n-1);
        S+=a[i];
        ROF(j,up,1) f[i][j]=f[i-1][j]|(f[i-1][j-1]<<a[i]);
        f[i][0]=f[i-1][0];
    }
    int ans=1e9;
    FOR(i,0,S) if(f[2*n][n-1][i]) chkmin(ans,max(i,S-i));
    bel[1]=0,bel[2]=1;
    int tot=2*n,x=n-1;
    while(ans) {
        if(f[tot-1][x][ans]) tot--;
        else bel[tot]=1,ans-=a[tot],x--,tot--;
    }
    c1=n;
    FOR(i,1,2*n)
        if(!bel[i]) b[0][++c0]=a[i];
        else b[1][c1--]=a[i];
    FOR(i,0,1) {
        FOR(j,1,n) printf("%d ",b[i][j]-1);
        puts("");
    }
}

矩阵

Source:ARC081D。

结论:一个矩形可行当且仅当所有 \(2\times 2\) 的子矩形满足其中有偶数个 #

缩减矩形之后是一个最大全 \(1\) 子矩阵问题,随便搞搞就行了。

注意算上 \(1\times n\) 的情况。

Code
const int N=2005;
int l[N][N],r[N][N],up[N][N],ans;
char a[N][N];
int main() {
    int n=read(),m=read();
    chkmax(ans,n),chkmax(ans,m);
    FOR(i,1,n) {
        scanf("%s",a[i]+1);
        FOR(j,1,m) a[i][j]=(a[i][j]=='#');
    }
    n--,m--;
    FOR(i,1,n) FOR(j,1,m)
        a[i][j]=!(a[i][j]^a[i+1][j]^a[i][j+1]^a[i+1][j+1]),
        l[i][j]=j,r[i][j]=j;
    FOR(i,1,n) {
        FOR(j,2,m) if(a[i][j]&&a[i][j-1]) l[i][j]=l[i][j-1];
        ROF(j,m-1,1) if(a[i][j]&&a[i][j+1]) r[i][j]=r[i][j+1];
        FOR(j,1,m) if(a[i][j]) {
            if(a[i-1][j]) chkmax(l[i][j],l[i-1][j]),chkmin(r[i][j],r[i-1][j]);
            up[i][j]=up[i-1][j]+1;
            chkmax(ans,(r[i][j]-l[i][j]+2)*(up[i][j]+1));
        }
    }
    printf("%d\n",ans);
}
posted @ 2022-10-18 07:53  cnyz  阅读(54)  评论(0编辑  收藏  举报