Good Problems

CF1750H BinaryStringForces

1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx 1kri /bx

 

复读一下 1 老师题解。

摆了。

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200005;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,a[N],len[N],f[N],cnt,c[N],L[N]; ll ans; char str[N];
struct Node1{
    int mx;
}t1[N<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
inline void build(int k,int l,int r){
    t1[k].mx=0;
    if(l==r){
        if(!a[l] && a[l+1]) t1[k].mx=l+len[l];
        return;
    }
    int mid=l+r>>1;
    build(ls(k),l,mid);
    build(rs(k),mid+1,r);
    t1[k].mx=max(t1[ls(k)].mx,t1[rs(k)].mx);
}
inline void find(int k,int l,int r,int ql,int qr,int x){
    if(t1[k].mx<x) return;
    if(l==r){
        c[++cnt]=l;
        return;
    }
    int mid=l+r>>1;
    if(ql<=mid) find(ls(k),l,mid,ql,qr,x);
    if(qr>mid) find(rs(k),mid+1,r,ql,qr,x);
}
int rt[N],tot;
struct Node2{
    int all,cnt,ls,rs;
}t2[N<<6];
inline void pushup(int k){
    if(t2[k].all) return;
    t2[k].cnt=t2[t2[k].ls].cnt+t2[t2[k].rs].cnt;
}
inline int update(int k,int l,int r,int ql,int qr){
    if(t2[k].all) return k;
    int dir=++tot; t2[tot]=t2[k];
    if(l>=ql && r<=qr){
        t2[dir].all=1;
        t2[dir].cnt=r-l+1;
        return dir;
    }
    int mid=l+r>>1;
    if(ql<=mid) t2[dir].ls=update(t2[dir].ls,l,mid,ql,qr);
    if(qr>mid) t2[dir].rs=update(t2[dir].rs,mid+1,r,ql,qr);
    pushup(dir); return dir;
}
inline int query(int k,int l,int r,int ql,int qr){
    if(!k) return 0;
    if(t2[k].all) return min(qr,r)-max(ql,l)+1;
    if(l>=ql && r<=qr) return t2[k].cnt;
    int mid=l+r>>1,ret=0;
    if(ql<=mid) ret+=query(t2[k].ls,l,mid,ql,qr);
    if(qr>mid) ret+=query(t2[k].rs,mid+1,r,ql,qr);
    return ret;
}
inline void STO_1kri_Orz(){
    n=read(); scanf("%s",str+1);
    for(int i=1;i<=n;i++) a[i]=str[i]-'0'; a[n+1]=1;
    ans=1ll*n*(n+1)/2;
    for(int i=1;i<=n;i++) len[i]=(!a[i])?len[i-1]+1:0;
    build(1,1,n);
    for(int i=1;i<=n;i++){
        if(!a[i] && i>len[i]){ // 钦定 i 作为右端点留下
            cnt=0; L[i]=n+1;
            // i-len[i]-j < len[i]
            // j > i-2*len[i]
            // len[j] > i-len[i]-j
            // j+len[j] > i-len[i]
            find(1,1,n,max(1,i-2*len[i]+1),i-len[i],i-len[i]+1);
            if(cnt>0){
                L[i]=L[c[1]];
                if(cnt>1 && L[c[2]]<=c[1]) rt[i]=rt[c[2]],L[i]=min(L[i],L[c[2]]); // c2 包含 c1
                else{ // c1 c2 无交
                    // i-len[i]-j < j-k+1
                    // k < 2j+1+len[i]-i
                    rt[i]=update(rt[c[1]],1,n,c[1]-len[c[1]]+1,2*c[1]+len[i]-i);
                }
            }
            // i-len[i]-k+1 < len[i]
            // k > i-2len[i]+1 
            L[i]=min(L[i],max(1,i-2*len[i]+2));
            if(len[i]>1) rt[i]=update(rt[i],1,n,max(1,i-2*len[i]+2),i-len[i]);
        }
        // 在前面枚举最后一个钦定的地方 j+len[j] > i
        cnt=0;
        if(i>1) find(1,1,n,1,i-1,i+1);
        if(!a[i]) c[++cnt]=i;
        for(int j=1,k=1;j<=cnt;j++){
            // k > i-c[j]
            ans-=len[c[j]]-(i-c[j]);
            // c[j] : [lst,c[j]-(i-c[j])]
            ans-=query(rt[c[j]],1,n,k,2*c[j]-i);
            k=2*c[j]-i+1;
        }
    }
    printf("%lld\n",ans);
    for(int i=1;i<=n;i++) rt[i]=0;
    for(int i=1;i<=tot;i++) t2[i]={0,0,0,0};
    tot=0;
}
int main(){
    int tc=read();
    while(tc--) STO_1kri_Orz();
    return 0;
}
View Code

 

 

ARC083F Collecting Balls

 


首先,我们把支配关系转化到图上,对于在 $(x,y)$ 的球,我们给第 $x$ 行与第 $y$ 列连无向边。

这样我们把问题转化为,我们需要给每条边选一个端点,让这个端点支配这条边。同时,同时如果边 $(u,v)$ 被 $u$ 支配,那么对于所有 $d<v$,$(u,d)$ 必须在 $(u,v)$ 之前被 $d$ 支配。

那么首先,如果这个图不是基环树森林,答案一定为 $0$。

那么现在考虑那个支配顺序关系。考虑对于一棵基环树,如果我们先把环支配好(显然有两种方式,即和环上的支配顺序有关),再按照支配关系连边,那么这又会变成一个内向树森林。

如果我们支配好了每个基环树的环,那么贡献就是整个内向树森林的拓扑序数量,即 $\frac{2n!}{\prod{siz_i} }$。证明就是因为对于 $i$ 而言,有 $siz_i-1$ 个点不能在它前面,那么合法方案就要乘一个 $\frac{1}{siz_i}$ 的系数。

当然直接枚举定向关系再把每种情况的答案加起来复杂度就上天了,这里我们考虑加法原理的本质,即每棵基环树有两种定向方式,设其算出来的系数为 $A,B$,那么我们算完了其它部分的总系数 $T$ 后,合并起来的系数就应该是 $(A+B)T$。因此我们只需要对每棵基环树计算这个系数即可。

精细实现可做到 $O(n)$。

 

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
const int mod=1000000007;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,fac[N],inv[N],ans;
int dfn[N],low[N],idx,bri[N<<1],col[N],tr,ecnt; vector<int>g[N];
int cnt,vis[N],b[N],circnt,iscir[N],ctr[N],cir[N],deg[N],siz[N];
struct Edge{
    int to,nxt;
}e[N<<1],c[N];
int heade[N],tote,headc[N],totc;
inline void adde(int u,int v){
    e[++tote]={v,heade[u]};
    heade[u]=tote;
}
inline void addc(int u,int v){
    c[++totc]={v,headc[u]};
    headc[u]=totc;
}
inline void tarjan(int u,int fa){
    dfn[u]=low[u]=(++idx);
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u]) bri[i]=bri[i^1]=1;
        }
        else if(v!=fa) low[u]=min(low[u],dfn[v]);
    }
}
inline void dfs1(int u,int fa){
    col[u]=tr; g[tr].emplace_back(u);
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa || bri[i]) continue;
        if(col[v]){ ecnt++; continue; }
        dfs1(v,u);
    }
}
inline void dfs2(int u){
    vis[u]=1; b[++cnt]=u;
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(vis[v]) continue;
        dfs2(v);
    }
}
inline void dfs3(int u,int fa){
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(iscir[v] || v==fa) continue;
        ctr[v]=u; dfs3(v,u);
    }
}
inline void dfs4(int u,int fa){
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v>=ctr[u]) continue;
        addc(u,v); deg[v]++;
    }
    for(int i=heade[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa || iscir[v]) continue;
        dfs4(v,u);
    }
}
inline int dfs5(int u,int fa){
    siz[u]=1; int res=1;
    for(int i=headc[u];i;i=c[i].nxt){
        int v=c[i].to;
        if(v==fa) continue;
        res=1ll*res*dfs5(v,u)%mod;
        siz[u]+=siz[v];
    }
    res=1ll*res*inv[siz[u]]%mod;
    return res;
}
inline int calc(){
    for(int i=1;i<=circnt;i++) dfs4(cir[i],-1);
    int ret=1;
    for(int i=1;i<=cnt;i++)
        if(!deg[b[i]]) ret=1ll*ret*dfs5(b[i],-1)%mod;
    return ret;
}
inline int solve(int rt){
    cnt=circnt=0; dfs2(rt);
    for(int i=1;i<=cnt;i++)
        if(iscir[b[i]]) dfs3(b[i],-1),cir[++circnt]=b[i];
    for(int i=1;i<=cnt;i++) headc[b[i]]=deg[b[i]]=0; totc=0;
    for(int i=1;i<circnt;i++)
        ctr[cir[i]]=cir[i+1];
    ctr[cir[circnt]]=cir[1];
    int ret=calc();
    for(int i=1;i<=cnt;i++) headc[b[i]]=deg[b[i]]=0; totc=0;
    for(int i=1;i<circnt;i++)
        ctr[cir[i+1]]=cir[i];
    ctr[cir[1]]=cir[circnt];
    (ret+=calc())%=mod;
    return ret;
}
int main(){
    n=read(); tote=1;
    for(int i=1;i<=n*2;i++){
        int u=read(),v=read();
        adde(u,v+n); adde(v+n,u);
    }
    fac[0]=fac[1]=inv[1]=1;
    for(int i=2;i<=n*2;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=n*2;i++) if(!dfn[i]) tarjan(i,-1);
    for(int i=1;i<=n*2;i++)
        if(!col[i]){
            tr++; ecnt=0;
            dfs1(i,-1);
            if(ecnt>2) return puts("0"),0;
        }
    for(int i=1;i<=tr;i++){
        if(g[i].size()==1) continue;
        for(int j=0;j<g[i].size();j++)
            iscir[g[i][j]]=1;
    }
    ans=1;
    for(int i=1;i<=n*2;i++)
        if(!vis[i]) ans=1ll*ans*solve(i)%mod;
    ans=1ll*ans*fac[n*2]%mod;
    printf("%d\n",ans);
    return 0;
}
View Code

 

CF930E  Coins Exhibition

看上去不好下手,我们应该从暴力出发。

首先来一个不用脑子的暴力:设 $f_{i,0/1,j}$ 表示处理了前 $i$ 个位置,$a_i=0/1$,且 $j$ 为最近一个满足 $a_j \neq a_i$ 的位置的合法方案数。直接做复杂度 $O(k^2)$,肯定是不行的。

当然我们需要离散化一下,改成一段一段区间转移。我们记离散化后第 $i$ 段区间的最后一个位置为 $r_i$。

修改一下 dp 状态:$f_{i,0/1,j}$ 表示处理到第 $i$ 个区间,$a_{r_i}=0/1$, 上一个满足 $a_x \neq a_{r_i}$ 的位置 $x$ 处在第 $j$ 个区间的合法方案数。以 $f_{i,0,j}$ 为例,依次考虑以下转移:

1. 整个区间全部填 $0$

$f_{i,0,j} \to f_{i+1,0,j}$

2. 整个区间全部填 $1$

$f_{i,0,j} \to f_{i+1,1,i}$

3. 区间内有 $0$ 和 $1$,且 $a_{r_i}=0$

$f_{i,0,j} \times (2^{len_i-1}-1) \to f_{i+1,0,i+1}$

4. 区间内有 $0$ 和 $1$,且 $a_{r_i}=1$

$f_{i,0,j} \times (2^{len_i-1}-1) \to f_{i+1,1,i+1}$

其中 $len_i$ 表示第 $i$ 段区间的长度。

对于 $f_{i,1,j}$ 的转移是类似的。

再考虑去除掉不合法的方案,每一层 $i$ 转移完后,我们对于形如 $[0/1,x,i+1]$ 的限制,将对应的 $f_{i+1,1/0,j}(j<x)$ 置为 $0$ 即可。此时你应该注意到,我们在离散化时需要将 $x-1$ 也进行离散化,否则 $x$ 之前所在的一段会没有被覆盖到。

这样我们就得到了一个 $O((n+m)^2)$ 的做法。它还是过不了。

但其实接下来的优化是简单的。我们考虑跟随 $i$ 的移动,动态维护整个 dp 过程。对应上面转移的标号:

- 1 和 2 不需要做任何事。

- 3 和 4 事实上是一个 全局和 的形式。

- 去除掉不合法的方案相当于前缀推平为 $0$。

这其实都不需要任何数据结构维护了,我们可以直接对于前缀维护推平指针(显然一个位置被推平后就永远都是 $0$ 了),并维护好 $0/1$ 的全局和就可以了。

时间复杂度为 $O((n+m) \log (n+m) + (n+m) \log k)$,前面是离散化的复杂度,后面是算贡献那里求一个快速幂的复杂度。

 

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=600005;
const int mod=1000000007;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
/*
dp[i][0/1][j] : 处理到第 i 个区间 sta[r[i]]=0/1 上一个与 sta[r[i]] 不一样的位置在第 j 个区间 
1.dp[i][0][j] -> dp[i+1][0][j]
2.dp[i][0][j] -> dp[i+1][1][i]
3.dp[i][0][j]*(2^(len-1)-1) -> dp[i+1][0/1][i+1]

1.dp[i][1][j] -> dp[i+1][1][j]
2.dp[i][1][j] -> dp[i+1][0][i]
3.dp[i][1][j]*(2^(len-1)-1) -> dp[i+1][0/i][i+1]

考虑动态维护
1. 不用动
2. 一个全局和的形式
3. 一个全局和的形式
4. 前缀推平为 0
不需要 ds 就能维护
*/
int K,m,n,lcnt,b[N],dp[2][N],sum[2],pos[2]={1,1},ans;
struct A{
    int l,r;
}a[N];
struct Q{
    int l,v;
};  vector<Q>q[N];
inline int qpow(int y){
    int x=2,r=1;
    while(y){
        if(y&1) r=1ll*r*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return r;
}
int main(){
    K=read(),n=read(),m=read();
    for(int i=1;i<=n;i++){
        a[i].l=read(),a[i].r=read();
        b[++lcnt]=a[i].l-1; b[++lcnt]=a[i].l; b[++lcnt]=a[i].r;
    }
    for(int i=n+1;i<=n+m;i++){
        a[i].l=read(),a[i].r=read();
        b[++lcnt]=a[i].l-1; b[++lcnt]=a[i].l; b[++lcnt]=a[i].r;
    }
    b[++lcnt]=0; b[++lcnt]=1; b[++lcnt]=K;
    sort(b+1,b+lcnt+1); lcnt=unique(b+1,b+lcnt+1)-b-1;
    for(int i=1;i<=n+m;i++){
        int j=lower_bound(b+1,b+lcnt+1,a[i].r)-b,k=lower_bound(b+1,b+lcnt+1,a[i].l)-b;
        q[j].emplace_back(Q{k,(i>n)});
    }
    /*
    f[1][0][1]=1;
    for(int i=1;i<lcnt;i++){
        int siz=b[i+1]-b[i],val=(qpow(siz-1)+mod-1)%mod;
        for(int j=1;j<=i;j++){
            (f[i+1][0][j] += f[i][0][j]) %= mod;
            (f[i+1][1][i] += f[i][0][j]) %= mod;
            (f[i+1][0][i+1] += 1ll*val*f[i][0][j]%mod) %= mod;
            (f[i+1][1][i+1] += 1ll*val*f[i][0][j]%mod) %= mod;
            (f[i+1][1][j] += f[i][1][j]) %= mod;
            (f[i+1][0][i] += f[i][1][j]) %= mod;
            (f[i+1][1][i+1] += 1ll*val*f[i][1][j]%mod) %= mod;
            (f[i+1][0][i+1] += 1ll*val*f[i][1][j]%mod) %= mod;
        }
        for(int j=0;j<q[i+1].size();j++)
            for(int k=0;k<q[i+1][j].l;k++)
                f[i+1][q[i+1][j].v^1][k]=0;
    }
    for(int j=1;j<=lcnt;j++) (ans += (f[lcnt][0][j]+f[lcnt][1][j])%mod ) %= mod;
    printf("%d\n",ans);
    */
    dp[0][1]=sum[0]=1;
    for(int i=1;i<lcnt;i++){
        int siz=b[i+1]-b[i],val=(qpow(siz-1)+mod-1)%mod,tot=(sum[0]+sum[1])%mod;
        (dp[0][i] += sum[1]) %= mod;
        (dp[0][i+1] += 1ll*tot*val%mod) %= mod;
        (dp[1][i] += sum[0]) %= mod;
        (dp[1][i+1] += 1ll*tot*val%mod) %= mod;
        int nxt = ( (sum[0] + sum[1]) % mod + 1ll*tot*val%mod ) % mod;
        sum[0]=sum[1]=nxt;
        for(int j=0;j<q[i+1].size();j++){
            int op=q[i+1][j].v^1;
            while(pos[op]<q[i+1][j].l)
                (sum[op]+=mod-dp[op][pos[op]])%=mod,dp[op][pos[op]]=0,pos[op]++;
        }
    }
    for(int j=1;j<=lcnt;j++) (ans += (dp[0][j]+dp[1][j])%mod ) %= mod;
    printf("%d\n",ans);
    return 0;
}
View Code

 

  

 ARC101E Ribbons on Tree

先考虑一个 $O(n^3)$ 的简单做法:

设 $f_{u,i}$ 表示 $u$ 的子树内还有 $i$ 个点要向上匹配的方案数。我们发现如果 $(u,v)$ 这条边要被覆盖到,那么 $v$ 的子树内必然有点要向上匹配。

因此合并转移的时候枚举 $u$ 子树内的点数 $i$,$v$ 子树内的点数 $j$(注意 $j\ge1$),以及匹配的对数 $k$,有转移:

$ f_{u,i} \times f_{v,j} \times {i \choose k} \times {j \choose k} \times k! \to newf_{u,i+j-2k} $

答案即为 $f_{1,0}$。但是这样做系数很复杂,感觉完全没有优化的空间。

正难则反,考虑容斥求不合法方案。

对于不合法方案,我们可以看作断开一个边集 $S$,然后给每个联通块内两两配对,得到方案数 $T(S)$,对答案的贡献即为 $(-1)^k \times T(S)$。

容易发现的是,大小为 $2t$ 的联通块两两配对,方案数 $g(t)=\prod_{i=1}^{t} (2i-1)$

重新设状态 $f_{u,i}$ 表示在 $u$ 子树内,$u$ 所在联通块大小为 $i$ 的方案数。

考虑边的钦定情况,有转移方程:

$f_{u,i} \times f_{v,j} \times (-g(j)) \to newf_{u,i}$ 钦定该边被断

$f_{u,i} \times f_{v,j} \to newf_{u,i+j} $ 该边没被断

答案为 $ \sum_{i=1}^n f_{1,i} \times g(i)$。时间复杂度 $O(n^2)$。

 

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
const int mod=1000000007;
inline int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
int n,siz[N],g[N],f[N][N],ff[N],ans;
struct Edge{
    int to,nxt;
}e[N<<1];
int head[N],tot;
inline void add(int u,int v){
    e[++tot]={v,head[u]};
    head[u]=tot;
}
inline void dfs(int u,int fa){
    siz[u]=1; f[u][1]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=1;j<=siz[u];j++) ff[j]=f[u][j],f[u][j]=0;
        for(int j=1;j<=siz[u];j++)
            for(int k=1;k<=siz[v];k++){
                (f[u][j]+=mod-1ll*ff[j]*f[v][k]%mod*g[k]%mod)%=mod;
                (f[u][j+k]+=1ll*ff[j]*f[v][k]%mod)%=mod;
            }
        siz[u]+=siz[v];
    }
}
int main(){
    n=read();
    g[0]=1; for(int i=2;i<=n;i+=2) g[i]=1ll*g[i-2]*(i-1)%mod;
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v); add(v,u);
    }
    dfs(1,-1);
    for(int i=1;i<=n;i++) (ans+=1ll*f[1][i]*g[i]%mod)%=mod;
    printf("%d\n",ans);
    return 0;
}
View Code

 

CF207C3 Game with Two Trees

 

确定一个看上去较为简单的计数方式:对于 $t_1$ 上的每个根到节点上的路径,统计 $t_2$ 上节点到祖先的路径与其相等的方案数。

不妨先离线,得到 $t_1,t_2$ 的形态。考虑对 $t_1$ 建 trie,然后以 $t_2$ 上的每个节点为起点向上跳,看最多能在 $t_1$ 的 trie 树上匹配到哪个位置,假设 $t_2$ 上的节点 $u$ 最终匹配到了 trie 上的节点 $v$,那么 trie 上根到 $v$ 的路径上的所有节点与 $u$ 都可以产生贡献。

计算贡献是简单的,可以直接分别维护 $t_1$ 上之前的节点对 $t_2$ 上新节点的贡献与 $t_2$ 上之前的节点对 $t_1$ 上新节点的贡献,用两个树状树组就可以完成(分别只需要子树加单点查值与单点加子树求和)。

我们考虑怎么维护找匹配点的过程。

我们使用倍增+重剖维护这个过程。具体地,我们对 trie 树重剖一下,然后从 $t_2$ 上的节点 $u$ 向上跳使用倍增维护。每次在 trie 树一条重链上与 $t_2$ 当前节点倍增匹配,匹配到头再跳轻边,重复这个过程直到无法匹配即可。这个过程的复杂度是 $O(n\log^2 n)$ 的。


另一种做法(来自 @_•́へ•́╬_/bx)是将 trie 上节点的值对应位置存下来,匹配的时候二分一下就好。复杂度相同但是常数略大,不过应该会好写一些。

因此我们就在 $O(n\log^2 n)$ 的时间内解决了本题,瓶颈即找匹配点的过程。

 

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
const int mod=998244353;
typedef long long ll;
namespace io{
    const int SIZE=(1<<21)+1;
    char ibuf[SIZE],*iS,*iT,obuf[SIZE],*oS=obuf,*oT=oS+SIZE-1,c,qu[55]; int qr;
    #define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),(iS==iT?EOF:*iS++)):*iS++)
    inline void flush(){
        fwrite(obuf,1,oS-obuf,stdout);
        oS=obuf;
    }
    inline void putc(char x){
        *oS++=x;
        if(oS==oT) flush();
    }
    template <class I>
    inline void read(I &x){
        for(c=gc();c<'0'||c>'9';c=gc());
        for(x=0;c<='9'&&c>='0';c=gc()) x=x*10+(c&15);
    }
    template <class I>
    inline void print(I x){
        if(!x) putc('0');
        while(x) qu[++qr]=x%10+'0',x/=10;
        while(qr) putc(qu[qr--]);
    }
    inline void getc(char &c){
        c=gc();
        while(c<'a'||c>'z') c=gc();
    }
}
using io::read; using io::putc; using io::print; using io::getc;
int m,op[N],to[N],u,n1,n2,idx,a[N],b[N]; char ch; ll ans;
int val[N],siz[N],id[N],top[N],dfn[N],son[N],len[N],dep[N];
int lg[N],fa[17][N],h[N],pw[N],tr[N][27];
inline void dfs1(int u){
    siz[u]=1;
    for(int i=0;i<26;i++){
        int v=tr[u][i];
        if(!v) continue;
        val[v]=(131ll*val[u]+i+47)%mod;
        dfs1(v);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}
inline void dfs2(int u){
    dfn[u]=++idx; id[idx]=u;
    if(!son[u]) return;
    dfs2(son[u]); len[u]=len[son[u]]+1;
    for(int i=0;i<26;i++){
        int v=tr[u][i];
        if(!v || v==son[u]) continue;
        dfs2(v);
    }
}
inline int find(int u,int v,int s){
    for(int i=lg[min(len[v],dep[u])];~i;i--)
    if((h[s]-h[fa[i][u]]+mod)%mod==1ll*val[id[dfn[v]+(1<<i)]]*pw[dep[fa[i][u]]]%mod)
    u=fa[i][u],v=id[dfn[v]+(1<<i)];
    if(u==1 || !tr[v][to[u]]) return v;
    return find(fa[0][u],tr[v][to[u]],s);
}
struct BIT{
    int c[N];
    #define lowbit(x) (x&(-x))
    inline void update(int x,int y){ while(x<=n1) c[x]+=y,x+=lowbit(x); }
    inline int query(int x){ int r=0; while(x) r+=c[x],x-=lowbit(x); return r; }
}T1,T2;
int main(){
    read(m); n1=n2=1; dep[1]=a[1]=1;
    pw[0]=1; for(int i=1;i<=m;i++) pw[i]=131ll*pw[i-1]%mod;
    for(int i=1;i<=m;i++){
        read(op[i]),read(u),getc(ch);
        if(op[i]==1){
            int ru=a[u]; n1++;
            if(!tr[ru][ch-'a'])
                tr[ru][ch-'a']=n1;
            a[n1]=tr[ru][ch-'a'];
        }
        else{
            n2++;
            fa[0][n2]=u;
            to[n2]=ch-'a';
            dep[n2]=dep[u]+1;
            h[n2]=(h[u]+(to[n2]+47ll)*pw[dep[u]])%mod;
        }
    }
    dfs1(1); dfs2(1);
    lg[0]=-1; for(int i=2;i<=m;i++) lg[i]=lg[i>>1]+1;
    for(int j=1;j<=lg[n2];j++)
        for(int i=1;i<=n2;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]];
    b[1]=1; for(int i=2;i<=n2;i++) b[i]=find(i,1,i);
    T1.update(dfn[1],1); T1.update(dfn[1]+siz[1],-1);
    T2.update(dfn[b[1]],1); ans=1;
    for(int i=1,x=1,y=1;i<=m;i++){
        if(op[i]==1){
            int u=a[++x];
            ans+=T2.query(dfn[u]+siz[u]-1)-T2.query(dfn[u]-1);
            T1.update(dfn[u],1); T1.update(dfn[u]+siz[u],-1);
        }
        else{
            int u=b[++y];
            ans+=T1.query(dfn[u]);
            T2.update(dfn[u],1);
        }
        print(ans); putc('\n');
    }
    io::flush();
    return 0;
}
View Code

 

 

CF1601D Difficult Mountain

 

不妨把登山者分为 $s_i < a_i$ 和 $s_i \ge a_i$ 两类。

如果我们只单独考虑第一类,容易发现按照 $a_i$ 升序贪心考虑(能登山就登)一定是最优的;如果我们只单独考虑第二类,按照 $s_i$ 升序贪心考虑一定是最优的。这两个的证明是比较显然的。

那么显然考虑两种情况同时出现,首先可以证明的是同一类之间的登山者贪心的相对顺序是不变的,证明的话就大概考虑如果其与原顺序不同,我交换回来一定是不会劣的。

这样我们只需要考虑当前两类的最优决策 $u$ 和 $v$ 如何选择了:

- 如果 $a_u \le s_v$,那么我们选择 $u$ 不会对第二类决策产生任何影响,那不选白不选,我们当然会先选择 $u$。

- 否则你发现如果我们选了 $u$ 那就不能选 $v$ 了,且得到新的 $d'=a_u > s_v$。这不好,因为我们换成选择 $v$ 可以得到新的 $d''=\max\{d,a_v\}\le s_v < d'$,所以选择 $v$ 显然是更优的。

于是直接这样贪心即可。

仔细分析的话会发现这个思路与题解区按照 $(\max\{s_i,a_i\},s_i)$ 进行双关键字再贪心的思路本质相同,但是我个人认为这个思路更加自然明了(

 

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mp make_pair
using namespace std;
const int N=500005;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}    int tot,n,m,d,ans; pii a[N],b[N];
int main(){
    tot=read(),d=read();
    for(int i=1;i<=tot;i++){
        int x=read(),y=read();
        if(y>x) a[++n]=mp(y,x);
        else b[++m]=mp(x,y);
    }    sort(a+1,a+n+1); sort(b+1,b+m+1);
    for(int i=1,j=1;;){
        while(i<=n&&a[i].second<d) i++;
        while(j<=m&&b[j].first<d) j++;
        if(i<=n&&j<=m){
            if(a[i].first<=b[j].first) d=a[i].first,ans++,i++;
            else d=max(d,b[j].second),ans++,j++;
        }
        else if(i<=n)
            d=a[i].first,ans++,i++;
        else if(j<=m)
            d=max(d,b[j].second),ans++,j++;
        else
            break;
    }
    printf("%d\n",ans);
    return 0;
}
View Code

 

 

 

 

AtCoder KEYENCE Programming Contest 2019 E

题目相当于在求这个完全图的 MST。把所有边全部连起来显然是不行的,我们考虑删去一些无用的边。

考虑什么样的边可以丢掉。注意到一个环上,删去最大边不会对答案产生影响。

把边权分配到点权上:对于 $i<j$,有 $w_{i,j}=(a_i-id)+(a_j+jd)$。我们先定义 $c_i=a_i-id,d_i=a_i+id$。

有偏序关系,考虑分治。

考虑把点集 $[l,r]$ 分成 $[l,mid],[mid+1,r]$ 两部分,再把点集内的边分成在左半边内、在右半边内、横跨两半边三类,处理横跨两半边的,另外两种递归下去。

 找到左半边 $c_i$ 的最小位置 $x$ 和右半边 $d_i$ 的最小位置 $y$,然后 $x$ 向 $[mid+1,r]$ 连边,$y$ 向 $[l,mid]$ 连边。

容易发现没有连的边都是没用的:对于没连的一条边 $(a,b)$,$(a,y),(b,x),(x,y)$ 均存在,显然 $(a,b)$ 可以直接删去。

边数是 $n \log n$ 的,再跑 kruskal,复杂度 $O(n\log^2n)$。

 

ABC293Ex Optimal Path Decomposition

 

起手先二分一个 $K$,把问题转换为判定。

考虑一个点 $u$ 的决策,发现它只会和其一或两个儿子的颜色相同。

那么你发现双儿子对 $u$ 子树内的路径更优,单儿子可能对 $fa_u$ 的决策更优。特别的,叶子视为单儿子决策(因为它可以继续向上传递)

这时我们二分 $K$ 的用处就体现出来了:如果可以选单儿子且对 $fa_u$ 的决策更优的话我们就选单儿子,否则能选双儿子就双儿子,否则判定失败。

设 $f_i$ 表示最优决策下 $i$ 的子树内,从 $i$ 开始的链的最多颜色个数,
$g_i$ 表示最优决策下 $i$ 的子树内,一条链的最多颜色个数。

那么合法就等价于 $\forall i \in \{1,2,\cdots,n\},g_i\le K$。

然后进行转移。考虑 $u$ 的儿子中两种决策的 $f$ 形成的降序集合 $\{x_1,x_2,\cdots,x_X\}$,$\{y_1,y_2,\cdots,x_Y\}$,其中 $x$ 为单儿子决策集合,$y$ 为双儿子决策集合。(注意这里的决策是对儿子而言的)

定义 $s2\{S\}$ 表示集合 $S$ 中前两大的数之和。

那么如果 $u$ 选择双儿子,就有:

$f_u'=\max\{x_1,x_2,x_3+1,\cdots,x_X+1,y_1+1,y_2+1,\cdots,y_Y+1\}$

$g_u'=s2\{x_1,x_2,x_3+1,\cdots,x_X+1,y_1+1,y_2+1,\cdots,y_Y+1\}$

如果选择单儿子,则有:

$f_u''=\max\{x_1,x_2+1,x_3+1,\cdots,x_X+1,y_1+1,y_2+1,\cdots,y_Y+1\}$

$g_u''=s2\{x_1,x_2+1,x_3+1,\cdots,x_X+1,y_1+1,y_2+1,\cdots,y_Y+1\}$

那么:

若 $g_u'>K$ 直接判定失败。

若 $g_u'\le K,g_u''>K$ 选择双儿子,因为不得不选。

若 $f_u''=f_u'+1$ 选择双儿子,这是一定不劣的($f_u'$ 传给父亲可能可以减 $1$ 费贡献,但是依然不会比选择 $f_u''$ 更优)。

否则选择单儿子。

注意到上述的转移中 $x$ 只需要用到前四大值,$y$ 只需要用到前两大值,于是一轮 dp 判定的复杂度就是 $O(n)$ 的了。

加上外面的二分,时间复杂度 $O(n \log n)$。

实际上依据形如重剖的分析可以得出 $K$ 是 $O(\log n)$ 级的,因此甚至可以做到 $O(n \log \log n)$。

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,K,deg[N],pn,qn,cn,dn,p[9],q[9],c[9],d[9];
struct Edge{
    int to,nxt;
}e[N<<1];
int head[N],tot;
inline void add(int u,int v){
    e[++tot]={v,head[u]};
    head[u]=tot;
}
struct Dp{ int op,val; }f[N];
inline bool dfs(int u,int fa){
    if(deg[u]==1 && fa!=-1){
        f[u]={1,1};
        return 1;
    }
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa) continue;
        if(!dfs(v,u)) return 0;
    }
    pn=qn=cn=dn=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa) continue;
        if(f[v].op){
            int j=1;
            while(j<=pn&&p[j]>=f[v].val) j++;
            if(j>4) continue;
            if(pn<4) pn++;
            for(int k=pn;k>j;k--) p[k]=p[k-1];
            p[j]=f[v].val;
        }
        else{
            int j=1;
            while(j<=qn&&q[j]>=f[v].val) j++;
            if(j>2) continue;
            if(qn<2) qn++;
            for(int k=qn;k>j;k--) q[k]=q[k-1];
            q[j]=f[v].val;
        }
    }
    p[++pn]=0; q[++qn]=0;
    if(pn){
        for(int i=1;i<=pn;i++)
            c[++cn]=p[i]+(i>1),d[++dn]=p[i]+(i>2);
    }
    if(qn){
        for(int i=1;i<=qn;i++)
            c[++cn]=d[++dn]=q[i]+1;
    }
    sort(c+1,c+cn+1,greater<int>());
    sort(d+1,d+dn+1,greater<int>());
    if(d[1]+d[2]-1>K) return 0;
    if(c[1]+c[2]-1>K||c[1]>d[1]){
        f[u]={0,d[1]};
        return 1;
    }
    f[u]={1,c[1]};
    return 1;
}
int main(){
    n=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v); add(v,u);
        deg[u]++; deg[v]++;
    }
    K=1; while(!dfs(1,-1)) K++;
    printf("%d\n",K);
    return 0;
}
View Code

 


CF1553H XOR and Distance

题目越发的水

考虑一个暴力:

建出 trie 树,并在 trie 上遍历。对于有两个子树的节点,我们对于每个 $x$ 分别查询出来左子树异或 $x$ 的最小最大值和右子树异或 $x$ 的最小最大值,并更新答案。这样的复杂度可以达到 $O(4^k\times k)$,无法接受。

考虑继续优化。观察对于一个节点深度为 $d$ 的节点,那么子树内自高向低的前 $d$ 位的值全部相同,因此我们发现 $x$ 与 $x \space \text{and} \space (2^{k-d}-1)$ 完全等效!于是我们对于深为 $d$ 的点 $x$ 只需要枚举到 $2^{k-d}-1$,那么这部分的复杂度就变成了 $O(\sum\limits_{d=0}^k 2^d \times 2^{k-d} \times k)=O(2^k \times k^2)$。

查询时直接枚举第一个不同的位的深度 $i$,对应的值即为 $f_{i,x \space \text{and} \space (2^{k-d}-1) }$。

实际上根据儿子的 $f$ 值向父亲转移是可以做到 $O(2^k \times k)$,不过这不重要。

 

#include<bits/stdc++.h>
using namespace std;
const int inf=1000000005;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}   int n,k,tot,ch[12000005][2],siz[12000005],val[12000005],f[21][600005],ans;
inline void Insert(int x){
    int u=0;
    for(int i=k-1;~i;i--){
        int y=(x>>i)&1;
        if(!ch[u][y]) ch[u][y]=++tot;
        u=ch[u][y]; siz[u]++; val[u]=x;
    }
}
inline int qmn(int u,int x){
    if(siz[u]==1) return val[u]^x;
    if(!ch[u][0]||!ch[u][1]) return qmn(ch[u][0]+ch[u][1],x);
    return (val[ch[u][0]]^x)<(val[ch[u][1]]^x)?qmn(ch[u][0],x):qmn(ch[u][1],x); 
}
inline int qmx(int u,int x){
    if(siz[u]==1) return val[u]^x;
    if(!ch[u][0]||!ch[u][1]) return qmx(ch[u][0]+ch[u][1],x);
    return (val[ch[u][0]]^x)>(val[ch[u][1]]^x)?qmx(ch[u][0],x):qmx(ch[u][1],x); 
}
inline void dfs(int u,int d){
    if(siz[ch[u][0]]&&siz[ch[u][1]]){
        for(int i=0;i<(1<<d+1);i++){
            f[d][i]=min(f[d][i],abs(qmx(ch[u][0],i)-qmn(ch[u][1],i)));
            f[d][i]=min(f[d][i],abs(qmn(ch[u][0],i)-qmx(ch[u][1],i)));
        }
    }
    if(siz[ch[u][0]]>1) dfs(ch[u][0],d-1);
    if(siz[ch[u][1]]>1) dfs(ch[u][1],d-1);
}
int main(){
    n=read(),k=read();
    for(int i=1;i<=n;i++) Insert(read());
    for(int i=0;i<k;i++)
        for(int j=0;j<(1<<i+1);j++)
            f[i][j]=inf;
    dfs(0,k-1);
    for(int i=0;i<(1<<k);i++){
        ans=inf;
        for(int j=0;j<k;j++)
            ans=min(ans,f[j][i&((1<<j+1)-1)]);
        printf("%d%c",ans,i+1==(1<<k)?'\n':' ');
    }
    return 0;
}
View Code

 

CF1609G A Stroll Around the Matrix

先不考虑修改,考虑查询的实质。

题目里给了一个差分数组单调上升的条件,我们自然地去将问题放到差分数组上考虑。记 $c,d$ 为 $a,b$ 的差分数组,然后你会发现我在 $(i,j)$ 位置往下走/往右走一步相当于将 $c_{i+1}$ / $d_{j+1}$ 加入后面所有步的贡献。

进一步的,由差分数组单调上升,我们容易得到每次选取 $c_{i+1}$ 和 $d_{j+1}$ 中更小的那个必然不劣。

若我们将 $c$ 和 $d$ 归并得到数组 $x$,那么答案可以表示为:

$(n+m-1) \times (a_1+b_1) + \sum\limits_{i=1}^{n+m-2} (n+m-1-i) \times x_i$

而修改操作对差分数组就是后缀加。

但是每次查询都进行归并的话显然是吃不消的。我们注意到 $n \le 100$ 看上去很有用,我们可以这么做:

对于 $c$ 数组的修改直接暴力,而 $d$ 数组在线段树上维护。

对于查询的贡献我们分成三部分:$c$ 数组自身的贡献;$d$ 数组自身的贡献;将 $c$ 和 $d$ 归并起来时,额外造成的贡献。

$c$ 自身的贡献即为 $\sum\limits_{i=1}^{n-1} c_i \times (n-i)$,$d$ 同理。这个是好维护的。

每次归并的时候只需要考虑将 $c_i$ 插入 $d$ 数组时的贡献。$d$ 数组中所有比 $c_i$ 小的数会多一步贡献,而其他数会给 $c_i$ 造成一步贡献。容易用线段树二分实现。

总时间复杂度 $O(nq \log m)$,我 CF 上跑了 5500ms,鉴定为人傻常数大。

 

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=100005;
inline ll read(){
    ll x=0; char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
int n,m,q,o,k; ll a[N],b[N],d,ans_A,ans_B,ans;
struct Node{ ll sum,mn,tag; int len; }t[N<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
inline void pushup(int k){
    t[k].sum=t[ls(k)].sum+t[rs(k)].sum;
    t[k].mn=min(t[ls(k)].mn,t[rs(k)].mn);
}
inline void pushdown(int k){
    if(!t[k].tag) return;
    t[ls(k)].sum+=t[ls(k)].len*t[k].tag;
    t[ls(k)].mn+=t[k].tag;
    t[ls(k)].tag+=t[k].tag;
    t[rs(k)].sum+=t[rs(k)].len*t[k].tag;
    t[rs(k)].mn+=t[k].tag;
    t[rs(k)].tag+=t[k].tag;
    t[k].tag=0;
}
inline void build(int k,int l,int r){
    t[k].len=r-l+1;
    if(l==r){ t[k].sum=t[k].mn=(l==1?-LONG_LONG_MAX:b[l]); return; }
    int mid=l+r>>1;
    build(ls(k),l,mid); build(rs(k),mid+1,r);
    pushup(k);
}
inline void update(int k,int l,int r,int ql,int qr,int x){
    if(l>=ql&&r<=qr){
        t[k].sum+=t[k].len*x;
        t[k].mn+=x;
        t[k].tag+=x;
        return;
    }
    int mid=l+r>>1; pushdown(k);
    if(ql<=mid) update(ls(k),l,mid,ql,qr,x);
    if(qr>mid) update(rs(k),mid+1,r,ql,qr,x);
    pushup(k);
}
inline int find(int k,int l,int r,ll x){
    if(l==r) return l;
    int mid=l+r>>1; pushdown(k);
    if(t[rs(k)].mn>x) return find(ls(k),l,mid,x);
    return find(rs(k),mid+1,r,x);
}
inline ll query(int k,int l,int r,int ql,int qr){
    if(l>=ql && r<=qr) return t[k].sum;
    int mid=l+r>>1; pushdown(k);
    if(qr<=mid) return query(ls(k),l,mid,ql,qr);
    if(ql>mid) return query(rs(k),mid+1,r,ql,qr);
    return query(ls(k),l,mid,ql,qr)+query(rs(k),mid+1,r,ql,qr);
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>b[i];
    for(int i=n;i;i--) a[i]-=a[i-1];
    for(int i=m;i;i--) b[i]-=b[i-1];
    // sum i=1 to n+m-2 (n+m-1-i)*d[i]
    build(1,1,m);
    for(int i=2;i<=n;i++) ans_A+=a[i]*(n-i+1);
    for(int i=2;i<=m;i++) ans_B+=b[i]*(m-i+1);
    while(q--){
        cin>>o>>k>>d;
        if(o==1){
            if(k==n) a[1]+=d,k--;
            for(int i=n-k+1;i<=n;i++)
                a[i]+=d;
            ans_A+=d*(1+k)*k/2;
        }
        else{
            if(k==m) b[1]+=d,k--;
            update(1,1,m,m-k+1,m,d);
            ans_B+=d*(1+k)*k/2;
        }
        ans=(a[1]+b[1])*(n+m-1)+ans_A+ans_B;
        for(int i=2;i<=n;i++){
            int pos=find(1,1,m,a[i]);
            if(pos!=1) ans+=query(1,1,m,2,pos);
            ans+=a[i]*(m-pos);
        }
        cout<<ans<<'\n';
    }
    return 0;
}
View Code

 

QOJ6368 Zenyk, Marichka and Interesting Game

首先,我们可以将所有 $a_i$ 对 $A+B$ 取模。为啥可以这样呢?就是说我们可以发现这个状态下的必胜方必然可以将初始状态转化到该状态。

然后就是没有营养的分类讨论。

  • $A=B$

直接判断 $\ge A$ 的数字个数的奇偶性即可。

  • $A>B$

如果先手想赢,那么他必然要保证任意时刻不存在一个数他取不到但是后手能取到。其充分性也是显然的。

如果存在 $B \le a_i < A$,那先手肯定寄了。

同理,如果存在超过一个 $ a_i \ge 2B$,那先手也寄了。

其他情况就判 $\ge A$ 的数字个数的奇偶性即可。

  • $A<B$

可以看作先手走一步,然后后手走 $A>B$ 的情况。

如果存在 $A \le a_i < B$,那先手肯定赢了。

存在如果 $a_i \ge 2A$ 你发现先手也赢了,我第一步选这个就行了。

其他情况就判 $\ge A$ 的数字个数的奇偶性即可。

 

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,A,B,a[N];
namespace subtask1{ // A=B
    inline void solve(){
        int cnt=0;
        for(int i=1;i<=n;i++) if(a[i]>=A) cnt++;
        puts((cnt&1)?"Zenyk":"Marichka");
    }
};
namespace subtask2{ // A>B
    inline void solve(){
        for(int i=1;i<=n;i++)
            if(a[i]>=B&&a[i]<A)
                return puts("Marichka"),void();
        int cnt=0;
        for(int i=1;i<=n;i++)
            if(a[i]>=2*B)
                cnt++;
        if(cnt>1) return puts("Marichka"),void();
        cnt=0;
        for(int i=1;i<=n;i++)
            if(a[i]>=A)
                cnt++;
        puts((cnt&1)?"Zenyk":"Marichka");
    }
};
namespace subtask3{ // A<B
    inline void solve(){
        for(int i=1;i<=n;i++)
            if(a[i]>=2*A)
                return puts("Zenyk"),void();
        for(int i=1;i<=n;i++)
            if(a[i]>=A&&a[i]<B)
                puts("Zenyk"),void();
        int cnt=0;
        for(int i=1;i<=n;i++)
            if(a[i]>=A)
                cnt++;
        puts((cnt&1)?"Zenyk":"Marichka");
    }
};
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>A>>B;
    for(int i=1;i<=n;i++) cin>>a[i],a[i]%=A+B;
    if(A==B) subtask1::solve();
    if(A>B) subtask2::solve();
    if(A<B) subtask3::solve();
    return 0;
}
View Code

 

QOJ3040 Container

题目大意:有一个长度为 $n$ 的序列 $a$,满足 $a_i=1/2$。你希望通过若干次变换将其变换为另一个序列 $b$(保证两序列 $1$ 和 $2$ 的个数相同)。一次变换中,你可以选择 $a$ 序列的一个长度不超过 $3$ 的连续子序列并将其翻转,代价为被选中的连续子序列的 $a_i$ 之和加上一个固定的常数 $C$。要求构造出一种总代价最小的变换方案。

$1\leq n \leq 500$,$0 \leq C \leq 1000$。

bonus:$1 \leq n \leq 5000$。

时空限制 2.5s,1024MB。

做法一:

我们尝试将问题其转化到一个平面上。

将序列中的 $1$ 看作在平面内向上走一个单位,$2$ 看作在平面内向右走一个单位,于是初始状态和最终状态可以看作两条起点和终点相同的路径。

而我们的变换操作本质上也只有 $3$ 组:$12$ 与 $21$,代价为 $C+3$;$112$ 与 $211$,代价为 $C+4$;$122$ 与 $221$,代价为 $C+5$。其对应到平面上分别可以表示为 $1 \times 1$,$2 \times 1$,$1 \times 2$ 的矩形的左、上两条边与右、下两条边。

考察被这两条路径围成的封闭区域(可能不止一块),我们可以发现,原问题等价于用 $1 \times 1$,$2 \times 1$,$1 \times 2$ 的矩形去覆盖这些区域,所需要的最小代价。证明直接考虑构造答案,从区域边缘一步步覆盖到中心。

我们先假设区域全部被 $1 \times 1$ 的矩形覆盖,再用 $2 \times 1$ 和 $1 \times 2$ 的矩形去替换。这是一个二分图最权匹配(不要求完美)的模型,即最小费用流。

构造答案在上面提到了,用拓扑排序实现即可。

但是直接连边暴力跑的话,时间复杂度是 $O(n^4 \log (n^2) )$ 的,不太能过(图上有 $n^2$ 级别的点)

但是这个图有非常好的性质:流量均为 $1$,且费用只有两种!分析我们连出来的图,发现在前面的绝大多数轮中,最后只需要流掉一条费用为较小那种的边。我们先无脑匹配掉这些边($2 \times 1$ 的矩形),我们发现剩下没匹配到的点只剩 $O(n)$ 个了,换言之我再跑费用流就只会跑 $O(n)$ 轮,那么时间复杂度降至 $O(n^3 \log (n^2) )$,足以通过原题限制。

做法二:

我们还有更加优秀的做法。

尝试将所有 $2$ 归位。

设终状态中的 $2$ 被初状态中匹配的位置为 $p_i$。

一个显然的性质:将终状态中的 $2$ 按照位置奇偶分成单调上升的两组,则一定存在最优解,对每一组而言,分别有 $p_i$ 单调上升。(调换组内 $p_i$ 的顺序不会对答案产生影响)

考虑钦定每个 $2$ 在最终状态匹配到的位置,我们可以得到一个答案的下界:$(\sum\limits_i { \lceil \frac{d_i}{2} \rceil \times C + \lfloor \frac{d_i}{2} \rfloor \times 4 + (d_i \bmod 2) \times 3 })+(\sum\limits_i \sum\limits_{j>i} [p_i>p_j])$。前面一项是假设所有长度为 $3$ 的变换全部为 $112(211)$ 得到的,最后一项就考虑什么情况下对 $122$ 变换是不可避免的,讨论一下发现 $p$ 的每对逆序对一定会带来一次贡献(根据上面的性质,奇偶相同的 $i,j$ 视作不产生逆序对)。

我们依然可以证明答案一定可以取到下界,且方法也是直接构造答案:对于两个位置 $i,j$,它们最终到达的位置为 $i',j'$。$i \to i'$ 和 $j \to j'$ 相交,不包含,且转移方向相同时(其他情况可以发现转移的顺序不影响贡献),先转移走靠近转移方向的那个位置总是可以避免对 $122$ 的变换。

于是可以得到一个 dp:设 $f_{i,j}$ 表示处理到原序列中第 $i$ 个 $2$,匹配掉了偶数位置的前 $j$ 个 $2$(剩下 $i-j$ 个即匹配奇数位置)的最小代价。枚举下一步匹配奇还是偶,转移是 $O(1)$ 的。

构造答案依旧是拓扑排序,总时间复杂度 $O(n^2)$。

#include<bits/stdc++.h>
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int inf=1000000005;
const int fx[5]={1,-1,0,0};
const int fy[5]={0,0,1,-1};
int n,c,nn,cnt,a[505],b[505],id[505][505],h[505][505],stp[250005],deg[250005],mat[250005];
pii ans[250005]; vector<int>o[250005]; queue<int>q; vector<pii>loc; string S,T;
namespace MF{
    int n,S,T,dis[250005],vis[250005],pre[250005],fl,cst,head[250005],tot=1;
    struct Edge{ int to,nxt,w,f; }e[2000005];
    inline void clr(){ for(int i=1;i<=n;i++) head[i]=0; }
    inline void ade(int u,int v,int w,int f){
        e[++tot]={v,head[u],w,f};
        head[u]=tot;
    }
    inline void add(int u,int v,int f,int c){
        ade(u,v,c,f);
        ade(v,u,-c,0);
    }
    inline bool check(int u,int v){
        for(int i=head[u];i;i=e[i].nxt)
            if(e[i].to==S&&e[i].f)
                return 0;
        for(int i=head[v];i;i=e[i].nxt)
            if(e[i].to==T&&!e[i].f)
                return 0;
        return 1;
    }
    inline void preflow(int u,int v,int c){
        fl++; cst+=c;
        for(int i=head[v];i;i=e[i].nxt)
            if(e[i].to==T)
                e[i].f--,e[i^1].f++;
        for(int i=head[u];i;i=e[i].nxt)
            if(e[i].to==v)
                e[i].f--,e[i^1].f++;
        for(int i=head[u];i;i=e[i].nxt)
            if(e[i].to==S)
                e[i^1].f--,e[i].f++;
    }
    inline bool spfa(){
        queue<int>q; q.push(S);
        for(int i=1;i<=n;i++) dis[i]=(i==S?0:inf),vis[i]=(i==S);
        while(!q.empty()){
            int u=q.front(); q.pop(); vis[u]=0;
            for(int i=head[u];i;i=e[i].nxt){
                int v=e[i].to;
                if(e[i].f&&dis[v]>dis[u]+e[i].w){
                    dis[v]=dis[u]+e[i].w;
                    pre[v]=i;
                    if(!vis[v]) q.push(v);
                }
            }
        }   return dis[T]<0;
    }
    inline pii EK(){
        while(spfa()){
            fl++; cst+=dis[T];
            for(int i=pre[T];;i=pre[e[i^1].to]){
                e[i].f--,e[i^1].f++;
                if(e[i^1].to==S) break;
            }
        }
        return mp(fl,cst);
    }
    inline void cut(){
        for(int u=1;u<=nn;u++){
            if(!(stp[u]&1)){
                for(int i=head[u];i;i=e[i].nxt){
                    int v=e[i].to;
                    if(v<=nn&&!e[i].f){
                        int z=min(stp[u],stp[v]);
                          ans[++cnt]=mp(z,z+2);
                        mat[u]=mat[v]=cnt;
                    }
                }
            }
        }
    }
};
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>c>>S>>T;
    for(int i=1;i<=n;i++) a[i]=S[i-1]-'0',b[i]=T[i-1]-'0';
    for(int i=1,x=1,y=1;i<=n;i++) if(a[i]==1){
        for(int j=y;j<=n;j++) h[x][j]++;
        x++;
    }   else y++;
    for(int i=1,x=1,y=1;i<=n;i++) if(b[i]==1){
        for(int j=y;j<=n;j++) h[x][j]--;
        x++;
    }   else y++;
    loc.emplace_back(mp(0,0));
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=n+1;j++)
            if(h[i][j]){
                id[i][j]=++nn;
                loc.emplace_back(mp(i,j));
            }
    MF::n=nn+2; MF::S=nn+1; MF::T=nn+2;
    for(int i=1;i<=nn;i++) stp[i]=loc[i].first+loc[i].second-1;
    for(auto [i,j]:loc){
        if(i+j&1){
            MF::add(nn+1,id[i][j],1,0);
            for(int k=0;k<4;k++){
                int ni=i+fx[k],nj=j+fy[k];
                if(h[ni][nj]) MF::add(id[i][j],id[ni][nj],1,-(c+1+(k<2)));
            }
        }
        else
            MF::add(id[i][j],nn+2,1,0);
    }
    for(auto [i,j]:loc){
        if(!((i+j)&1)) continue;
        for(int k=0;k<2;k++){
            int ni=i+fx[k],nj=j+fy[k];
            if(h[ni][nj]&&MF::check(id[i][j],id[ni][nj]))
                MF::preflow(id[i][j],id[ni][nj],-(c+2)); 
        }
    }
    pii flow=MF::EK(); MF::cut();
    // cerr << "Cost: " << nn*(c+3)+flow.second << '\n'; 
    for(int i=1;i<=nn;i++) if(!mat[i]) ans[++cnt]=mp(stp[i],stp[i]+1),mat[i]=cnt;
    for(int i=1;i<=nn;i++){
        int x=loc[i].first,y=loc[i].second;
        if(h[x][y]==1){
            if(h[x-1][y]&&mat[i]!=mat[id[x-1][y]])
                o[mat[i]].emplace_back(mat[id[x-1][y]]),deg[mat[id[x-1][y]]]++;
            if(h[x][y+1]&&mat[i]!=mat[id[x][y+1]])
                o[mat[i]].emplace_back(mat[id[x][y+1]]),deg[mat[id[x][y+1]]]++;
        }
        if(h[x][y]==-1){
            if(h[x+1][y]&&mat[i]!=mat[id[x+1][y]])
                o[mat[i]].emplace_back(mat[id[x+1][y]]),deg[mat[id[x+1][y]]]++;
            if(h[x][y-1]&&mat[i]!=mat[id[x][y-1]])
                o[mat[i]].emplace_back(mat[id[x][y-1]]),deg[mat[id[x][y-1]]]++;
        }
    }
    cout << cnt << '\n';
    for(int i=1;i<=cnt;i++) if(!deg[i]) q.push(i);
    while(!q.empty()){
        int u=q.front(); q.pop();
        cout << ans[u].first << ' ' << ans[u].second << '\n';
        for(int v:o[u]) if(!--deg[v]) q.push(v);
    }
    return 0;
}
O(n^3log)
#include<bits/stdc++.h>
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int inf=1000000005;
int n,c,m,cnt,sum[2][505],f[505][505],g[505][505],mat[505],deg[505];
vector<int>a,b[2],o[250005]; vector<pii>ans; queue<int>q; string S,T;
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>c>>S>>T;
    for(int i=1,j=0;i<=n;i++){
        if(S[i-1]=='2') a.emplace_back(i);
        if(T[i-1]=='2') b[i&1].emplace_back(i),sum[i&1][i]++;
        sum[0][i]+=sum[0][i-1]; sum[1][i]+=sum[1][i-1];
    }   m=a.size();
    for(int i=0;i<=m;i++) for(int j=0;j<=m;j++) f[i][j]=inf;
    f[0][0]=0;
    for(int i=0;i<m;i++)
        for(int j=0;j<=i;j++){
            if(f[i][j]==inf) continue;
            if(j<b[0].size()){
                int tar=b[0][j],dis=abs(tar-a[i]);
                int val=f[i][j]+((dis+1)/2)*c+(dis/2)*4+(dis%2)*3+max(0,sum[1][tar]-(i-j));
                if(val<f[i+1][j+1]) f[i+1][j+1]=val,g[i+1][j+1]=0;
            }
            if(i-j<b[1].size()){
                int tar=b[1][i-j],dis=abs(tar-a[i]);
                int val=f[i][j]+((dis+1)/2)*c+(dis/2)*4+(dis%2)*3+max(0,sum[0][tar]-j);
                if(val<f[i+1][j]) f[i+1][j]=val,g[i+1][j]=1;
            }
        }
    // cerr << "Cost: " << f[m][b[0].size()] << '\n';
    for(int i=m,j=b[0].size();i;){
        if(!g[i][j]) i--,j--,mat[i]=b[0][j];
        else i--,mat[i]=b[1][i-j];
    }
    for(int i=0;i<m;i++)
        for(int j=i+1;j<m;j++)
            if(mat[i]<mat[j]){
                if(mat[i]>a[i])
                    o[j].emplace_back(i),deg[i]++;
                else
                    o[i].emplace_back(j),deg[j]++;
            }
    for(int i=0;i<m;i++) if(!deg[i]) q.push(i);
    while(!q.empty()){
        int u=q.front(); q.pop();
        int x=a[u],y=mat[u];
        while(x+1<y) ans.emplace_back(mp(x,x+2)),x+=2;
        while(x-1>y) ans.emplace_back(mp(x-2,x)),x-=2;
        if(x<y) ans.emplace_back(mp(x,x+1)),x++;
        if(x>y) ans.emplace_back(mp(x-1,x)),x--;
        for(int v:o[u]) if(!--deg[v]) q.push(v);
    }
    cout << ans.size() << '\n';
    for(auto [i,j]:ans) cout << i << ' ' << j << '\n';
    return 0;
}
O(n^2)

 

Luogu P5642 人造情感(emotion)

首先将所有路径挂在 LCA 上。

先考虑求 $W(U)$,设 $f_u$ 表示 $u$ 子树内的最大独立集,$sum_u=\sum\limits_{v \in subtree(u)} {f_v}$,自下而上 dp:

  • $u$ 不被独立集中路径经过

$f_u=sum_u$

  • $u$ 被独立集中路径经过

$f_u=\max\limits_{(x_i,y_i,w_i)}\{{sum_u+w_i-\sum\limits_{z \in path(x_i,y_i),z \neq u} (f_z-sum_z)}\}$

树状数组辅助转移,单点加链求和转换为子树加单点求值,即可做到单 log。

再设 $g_u$ 表示 $u$ 子树外的最大独立集。

再刚刚求 $f$ 时,我们在遇到边 $(x_i,y_i,w_i)$ 时记下 $t_i={sum_u+w_i-\sum\limits_{z \in path(x_i,y_i),z \neq u} (f_z-sum_z)}$ 的值,即选择这条边情况下子树内的最优答案,以便我们接下来的转移。自上而下 dp:

  • $u$ 不被独立集中路径经过

$g_v=g_u+sum_u-f_v$

  • $u$ 被独立集中路径经过,且路径 LCA 为 $h$

$g_v=\max\limits_{(x_i,y_i,w_i)}\{{g_h+t_i-f_v}\}$

对于第二种情况,$h=u$ 可以直接按照 $t_i$ 排序后暴力扫,否则一个端点在祖先的其他子树中,另一端点在 $u$ 的非 $v$ 子树中,可以在挂在线段树上查询,可以做到单 log。

得出了 $f_u$ 和 $g_u$ 就可以统计答案了:对于任意 $(i,j)$,所求的 $F(i,j)$ 都是由若干 $f_u$ 和至多一个 $g_u$ 贡献而来,一个 $f_u$ 有贡献当且仅当 $u$ 不在路径上且 $fa_u$ 在路径上,一个 $g_u$ 有贡献当且仅当 $fa_u$ 不在路径上且 $u$ 在路径上,统计答案是容易的。

视 $n,m$ 同阶,总时间复杂度 $O(n \log n)$。

 

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=300005;
const int mod=998244353;
int n,idx,m,ans,fa[N],son[N],top[N],dep[N],siz[N],dfn[N]; vector<int>e[N]; ll h[N],f[N],sum[N],g[N];
struct Q{ int u,v; ll w; bool operator < (const Q &a) const { return w>a.w; } }; vector<Q>o[N];
namespace BIT{
    ll c[N];
    #define lowbit(x) (x&(-x))
    inline void add(int x,ll y){ while(x<=n) c[x]+=y,x+=lowbit(x); }
    inline ll query(int x){ ll r=0; while(x) r+=c[x],x-=lowbit(x); return r; }
    inline void update(int l,int r,ll x){ add(l,x); add(r+1,-x); }
}
namespace SGT{
    ll mx[N<<2];
    #define ls(x) (x<<1)
    #define rs(x) (x<<1|1)
    inline void pushup(int k){ mx[k]=max(mx[ls(k)],mx[rs(k)]); }
    inline void cmx(int k,int l,int r,int x,ll y){
        if(l==r){ mx[k]=max(mx[k],y); return; }
        int mid=l+r>>1;
        if(x<=mid) cmx(ls(k),l,mid,x,y);
        else cmx(rs(k),mid+1,r,x,y);
        pushup(k);
    }
    inline ll qmx(int k,int l,int r,int ql,int qr){
        if(l>=ql&&r<=qr) return mx[k];
        int mid=l+r>>1;
        if(qr<=mid) return qmx(ls(k),l,mid,ql,qr);
        if(ql>mid) return qmx(rs(k),mid+1,r,ql,qr);
        return max(qmx(ls(k),l,mid,ql,qr),qmx(rs(k),mid+1,r,ql,qr));
    }
    inline void update(int x,ll y){ cmx(1,1,n,x,y); }
    inline ll query(int L,int R,int l,int r){
        ll ret=0;
        if(L<l) ret=max(ret,qmx(1,1,n,L,l-1));
        if(R>r) ret=max(ret,qmx(1,1,n,r+1,R));
        return ret;
    }
}
inline int lca(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        u=fa[top[u]];
    }   return dep[u]<dep[v]?u:v;
}
inline void dfs1(int u){
    siz[u]=1;
    for(int v:e[u]){
        if(v==fa[u]) continue;
        fa[v]=u; dep[v]=dep[u]+1;
        dfs1(v); siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
        h[u]+=1ll*siz[v]*siz[v];
    }
    h[u]+=1ll*(n-siz[u])*(n-siz[u]);
}
inline void dfs2(int u,int tp){
    top[u]=tp; dfn[u]=++idx;
    if(!son[u]) return;
    dfs2(son[u],tp);
    for(int v:e[u]) if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
inline void dfs3(int u){
    for(int v:e[u]){
        if(v==fa[u]) continue;
        dfs3(v); sum[u]+=f[v];
    }
    f[u]=sum[u];
    for(int i=0;i<o[u].size();i++){
        o[u][i].w+=sum[u]+BIT::query(dfn[o[u][i].u])+BIT::query(dfn[o[u][i].v]);
        f[u]=max(f[u],o[u][i].w);
    }
    BIT::update(dfn[u],dfn[u]+siz[u]-1,sum[u]-f[u]);
}
inline void dfs4(int u){
    sort(o[u].begin(),o[u].end());
    for(int v:e[u]){
        if(v==fa[u]) continue;
        g[v]=g[u]+sum[u]-f[v];
        for(Q i:o[u])
            if(lca(i.u,v)!=v&&lca(i.v,v)!=v){
                g[v]=max(g[v],g[u]+i.w-f[v]);
                break;
            }
        g[v]=max(g[v],SGT::query(dfn[u],dfn[u]+siz[u]-1,dfn[v],dfn[v]+siz[v]-1)-f[v]);
    }
    for(Q i:o[u])
        SGT::update(dfn[i.u],g[u]+i.w),SGT::update(dfn[i.v],g[u]+i.w);
    for(int v:e[u]) if(v!=fa[u]) dfs4(v);
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1,u,v;i<n;i++){
        cin>>u>>v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs1(1); dfs2(1,1);
    for(int i=1,u,v,w,t;i<=m;i++){
        cin>>u>>v>>w; t=lca(u,v);
        o[t].emplace_back(Q{u,v,w});
    }
    dfs3(1); dfs4(1);
    ans=1ll*n*n%mod*(f[1]%mod)%mod;
    for(int u=1;u<=n;u++){
        if(u!=1) ans=(ans-(1ll*n*n-2ll*siz[u]*(n-siz[u])-h[fa[u]])%mod*(f[u]%mod)%mod+mod)%mod;
        ans=(ans-(1ll*n*n-2ll*siz[u]*(n-siz[u])-h[u])%mod*(g[u]%mod)%mod+mod)%mod;
    }
    cout<<ans<<'\n';
    return 0;
}
View Code

 

CF1098E Fedya the Potter

先 ST表 维护 gcd,枚举左端点,二分/倍增右端点求出 $b$ 数组(用 $(val,cnt)$ 的二元组表示出来,即连续 $cnt$ 个数字 $val$ 的一个段)。

再二分答案 $K$,问题转化为求 $b$ 数组中区间和 $\le K$ 的区间个数。

同一个段内的贡献可以单独算,这个是比较平凡的。

考虑不同段 $r>l$ 的贡献。记 $cnt \times val$ 的前缀和数组为 $sum$,$cnt$ 的前缀和数组为 $len$。

- $sum_r-sum_{l-1}\le K$

端点在两段任取都可以,贡献自然是 $cnt_l \times cnt_r$。双指针配合 $len$ 数组可以轻松计算这种情况的贡献。

- $sum_r-sum_{l-1} > K$,$sum_{r-1}-sum_l+val_l+val_r\le K$

端点在两段有散的贡献。你发现这种情况下 $(l-1,r)$ 或 $(l,r+1)$ 一定没有贡献,因此可知这种满足这种情况的 $(l,r)$ 只有 $O(L)$ 种($L$ 表示 $b$ 数组的长度)。于是考虑对每对 $(l,r)$ 单独计算。为表述方便,接下来记 $n=cnt_l$,$m=cnt_r$,$a=val_l$,$b=val_r$,$T=K-(sum_{r-1}-sum_l)$。

容斥计算一下 $f(l,r)$:

$f(l,r)=\sum\limits_{i=1}^n \sum\limits_{j=1}^m [ai+bj\le T]$

$\space \space \space \space \space \space \space \space \space \space \space \space =\sum\limits_{i=1}^{ \min(n, \lfloor \frac{T}{a}) \rfloor } \min(m,\lfloor \frac{T-ai}{b} \rfloor )$

$\space \space \space \space \space \space \space \space \space \space \space \space =\sum\limits_{i=1}^{\lfloor \frac{T}{a} \rfloor } \lfloor \frac{T-ai}{b} \rfloor - \sum\limits_{i=1}^{\lfloor \frac{T-an}{a} \rfloor } \lfloor \frac{T-an-ai}{b} \rfloor - \sum\limits_{i=1}^{\lfloor \frac{T-bm}{a} \rfloor } \lfloor \frac{T-bm-ai}{b} \rfloor + \sum\limits_{i=1}^{\lfloor \frac{T-an-bm}{a} \rfloor } \lfloor \frac{T-an-bm-ai}{b} \rfloor $

再考虑计算 $\sum\limits_{i=1}^{ \lfloor \frac{c}{a} \rfloor} \lfloor \frac{c-ai}{b} \rfloor (c\ge 0) $:

$\sum\limits_{i=1}^{ \lfloor \frac{c}{a} \rfloor} \lfloor \frac{c-ai}{b} \rfloor = \sum\limits_{i=0}^{ \lfloor \frac{c}{a} \rfloor-1} \lfloor \frac{c \bmod a + ai}{b} \rfloor $

$\space \space \space \space \space \space \space \space \space \space \space \space \space \space \space \space = \lfloor \frac{c \bmod a}{b} \rfloor \times \lfloor \frac{c}{a} \rfloor + \sum\limits_{i=0}^{ \lfloor \frac{c}{a} \rfloor-1} \lfloor \frac{ai+((c \bmod a) \bmod b)}{b} \rfloor $

类欧计算即可。

总时间复杂度 $O(n \log^3)$。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=50005;
const int LG=19;
const int M=2000005;
int n,m,qn,a[N],lg[N],g[LG][N]; map<int,ll>mp; ll sum[N],len[N],target,ans;
struct Potter{ int val; ll cnt; }b[M];
inline int gcd(int x,int y){
    int xz=__builtin_ctz(x),yz=__builtin_ctz(y),z=min(xz,yz);
    y>>=yz;
    while(x){
        x>>=xz;
        int diff=x-y;
        xz=__builtin_ctz(diff);
        y=min(x,y),x=abs(diff);
    }
    return y<<z;
}
inline int qd(int l,int r){
    int k=lg[r-l+1];
    return gcd(g[k][l],g[k][r-(1<<k)+1]);
}
inline ll calc(int l,int r){ return 1ll*(l+r)*(r-l+1)/2; }
inline ll fsum(ll a,ll b,ll c,ll n){ // sum i=0 to n (ai+b)/c
    if(n==-1) return 0;
    ll ans=0;
    if(a>=c){
        ans+=(a/c)*(n*(n+1)/2);
        a%=c;
    }
    if(b>=c){
        ans+=(b/c)*(n+1);
        b%=c;
    }
    ll m=(a*n+b)/c;
    ans=ans+m*n-fsum(c,c-b-1,a,m-1);
    return ans;
}
inline ll calc(ll a,ll b,ll c){ // sum i=1 to c/a (c-ai)/b
    if(c<0) return 0;
    return (c%a)/b*(c/a)+fsum(a,c%a%b,b,c/a-1);
}
inline ll query(Potter a,Potter b,ll lim){
    ll ret=calc(a.val,b.val,lim);
    ret-=calc(a.val,b.val,lim-a.val*a.cnt);
    ret-=calc(a.val,b.val,lim-b.val*b.cnt);
    ret+=calc(a.val,b.val,lim-a.val*a.cnt-b.val*b.cnt);
    return ret;
}
inline bool check(ll z){
    ll h=0;
    for(int i=1;i<=m;i++){
        ll k=z/b[i].val;
        if(k>=b[i].cnt) h+=b[i].cnt*(b[i].cnt+1)/2;
        else h+=k*(b[i].cnt-k+1)+k*(k-1)/2;
    }
    for(int i=1,j=0;i<=m;i++){
        if(j<i) j=i;
        while(j<m&&sum[j+1]-sum[i-1]<=z) j++;
        if(j>i) h+=b[i].cnt*(len[j]-len[i]);
        int k=j; ll rest=z-(sum[j]-sum[i]);
        while(k<m&&b[i].val+b[k+1].val<=rest){
            k++; h+=query(b[i],b[k],rest);
            rest-=b[k].val*b[k].cnt;
        }
    }
    return h>=target;
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=n;i++) g[0][i]=a[i];
    for(int j=1;j<=lg[n];j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            g[j][i]=gcd(g[j-1][i],g[j-1][i+(1<<j-1)]);
    for(int l=1;l<=n;l++){
        int now=l;
        while(now<=n){
            int L=now+1,R=n; int pos=now;
            while(L<=R){
                int mid=L+R>>1;
                if(qd(l,mid)==qd(l,now)) pos=mid,L=mid+1;
                else R=mid-1;
            }
            mp[qd(l,pos)]+=pos-now+1; now=pos+1;
        }
    }
    target=((1ll*n*(n+1)/2)*(1ll*n*(n+1)/2+1)/2+1)/2;
    for(auto it=mp.begin();it!=mp.end();it++)
        b[++m]={it->first,it->second};
    for(int i=1;i<=m;i++) sum[i]=sum[i-1]+b[i].val*b[i].cnt,len[i]=len[i-1]+b[i].cnt;
    ll L=1,R=400000000000000000ll;
    while(L<=R){
        ll mid=L+R>>1;
        if(check(mid)) ans=mid,R=mid-1;
        else L=mid+1;
    }
    cout<<ans<<'\n';
    return 0;
}
View Code

 

posted @ 2023-02-03 16:08  铃兰星夜  阅读(235)  评论(0编辑  收藏  举报