【考试总结】2022-04-01


tree

模拟 是否离开 \(S\leftrightarrow T\) 的链的选择权归属以及这个人的决策 即可得到所有可能局面

两遍 \(\rm DFS\) 求最长链可以得到某个人从某个点离开链后能获得的最大权值,而某个人离开链之后另一个可以自由选择离开链的地方

注意两个人此前都在链上对峙,那么可供选择的点是从链中点开始的向两侧扩展的区间

使用两个变量即可得到两个人最优决策,也可以无脑 \(\rm RMQ\)

Code Display
const int N=5e5+10;
vector G[N];
int n,S,T,fa[N],dep[N];
int fir[N],sec[N];
inline void get_fa(int x,int fat){
    dep[x]=dep[fa[x]=fat]+1;
    for(auto t:G[x]) if(t!=fat) get_fa(t,x);
    return ;
}
inline void get_dia(int x,int fat){
    fir[x]=1; sec[x]=0;
    for(auto t:G[x]) if(t!=fat){
        get_dia(t,x);
        if(fir[t]+1>fir[x]){
            sec[x]=fir[x];
            fir[x]=fir[t]+1;
        }else ckmax(sec[x],fir[t]+1);
    }
    return ;
}
signed main(){
    freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
    n=read(); S=read(); T=read();
    for(int i=1;i chain;
    int tmp=T;
    while(tmp){
        chain.emplace_back(tmp);
        tmp=fa[tmp];
    }
    reverse(chain.begin(),chain.end());
    vector vals,valt;   
    get_dia(T,0);
    int lst=-1,siz=chain.size();
    for(auto x:chain){
        int num=0;
        if((~lst)&&fir[x]==fir[lst]+1) num=sec[x];
        else num=fir[x];
        vals.emplace_back(num+dep[x]-1);
        lst=x;
    }
    memset(fir,0,sizeof(fir));
    memset(sec,0,sizeof(sec));
    get_dia(S,0);
    lst=-1;
    valt.resize(siz);
    for(int i=siz-1;i>=0;--i){
        int num=0,x=chain[i];
        if((~lst)&&fir[x]==fir[lst]+1) num=sec[x];
        else num=fir[x];
        valt[i]=num+dep[T]-dep[x];
        lst=x;
    }
    bool turn=siz&1;
    int lef=(siz-1)>>1,rig=lef+1,ans=turn?n+1:-n-1;
    int lmx=vals[lef],rmx=valt[rig];
    valt.emplace_back(0);
    vals.emplace_back(0);
    while(~lef){
        if(!turn){
            ckmax(ans,vals[lef]-rmx);
            ckmax(lmx,vals[rig++]);
            if(rig

treecnt

\((i,j)\) 同时处在的限制数视作 \((i,j)\) 之间的 \(e_{i,j}\) 条边边的边权

只有一个限制 \(S\) 时(记 \(|S|\)\(1\) 的个数),如果满足生成树上的边权之和为 \(|S|-1\) 那么是合法的生成树,同时可以发现最大的边权之和也就是 \(|S|-1\)

将一个限制推广至多个限制,那么边权之和需要为 \(\sum \left(|S_i|-1\right)\) 且这个权值和和最大生成树的边权和是一致的

那么需要实现新图上的最大生成树计数,可以使用如下两个结论辅助:

  • 任意一个 \(\rm MST\) 上某个特定边权的边的数量相同

  • 任意一个 \(\rm MST\) 断开某个特定边权的边后形成的联通块形态相同

上述结论可以通过观察 \(\rm Kruskal\) 算法的过程来加以证明

那么做法就是枚举边权 \(v\) 并将其他点用树边合并成联通块再将原图中连接两个不同联通块的权值为 \(v\) 的边加入跑 \(\text{Matrix Tree}\) 定理,最后用乘法原理合并

发现复杂度瓶颈也就是高斯消元的复杂度是 $\sum\limits_{i\in {V}} c_i^3 $,其中 \(c_i\) 为断开边权为 \(i\) 的边后形成的联通块数量,这显然满足:

\[\sum_{i} c_i^3\le\left(\sum_{i} c_i\right)^3=(n-1)^3 \]

Code Display
const int N=510;
int a[N][N];
inline int det(int n){
    int ans=1;
    for(int i=1;i<=n&&ans;++i){
        if(!a[i][i]){
            rep(j,i+1,n) if(a[j][i]){
                swap(a[i],a[j]);
                ans=del(0,ans);
                break;
            }
        }
        ckmul(ans,a[i][i]);
        int inv=ksm(a[i][i],mod-2);
        for(int j=i+1;j<=n;++j){
            int tmp=mul(a[j][i],inv);
            for(int k=i;k<=n;++k) ckdel(a[j][k],mul(a[i][k],tmp));
        }
    }
    return ans;       
}
vector > edge[2010],ont;
int n,K;
char s[N];
struct DSU{
    int fa[N];
    inline void init(int n){rep(i,1,n) fa[i]=i; return ;}
    inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    inline void merge(int x,int y){fa[find(x)]=find(y); return ;}
}Dsu;
int e[N][N];
bitset<2010>cons[N];

int id[N];
signed main(){
    freopen("treecnt.in","r",stdin); freopen("treecnt.out","w",stdout);
    n=read(); K=read();
    rep(i,1,n) rep(j,i+1,n) e[i][j]=read();
    int sig=0;
    rep(i,1,K){
        scanf("%s",s+1);
        bool fl=0;
        for(int j=1;j<=n;++j) if(s[j]=='1') sig+=(fl=cons[j][i]=1);
        if(!fl) --i,--K;
    }
    sig-=K;
    rep(i,1,n) rep(j,i+1,n){
        if(e[i][j]) edge[(cons[i]&cons[j]).count()].emplace_back(i,j,e[i][j]);
    }
    Dsu.init(n);
    for(int V=K;V>=0;--V){
        for(auto t:edge[V]){
            int u,v,w; tie(u,v,w)=t;
            if(Dsu.find(u)==Dsu.find(v)) continue;
            Dsu.merge(u,v);
            ont.emplace_back(u,v,V);
            sig-=V;
        }
    }
    if(sig) puts("0"),exit(0);
    int ans=1;
    for(int V=0;V<=K;++V){
        Dsu.init(n);
        for(auto e:ont){
            int u,v,w; tie(u,v,w)=e;
            if(w!=V) Dsu.merge(u,v);
        }
        int cnt=0;
        for(int i=1;i<=n;++i) id[i]=0;
        for(int i=1;i<=n;++i) if(Dsu.find(i)==i) id[i]=++cnt;
        rep(i,1,cnt) rep(j,1,cnt) a[i][j]=0;

        for(auto e:edge[V]){
            int u,v,w; tie(u,v,w)=e;
            int x1=Dsu.find(u),x2=Dsu.find(v);
            if(x1==x2) continue;
            x1=id[x1]; x2=id[x2];
            ckdel(a[x1][x2],w);
            ckdel(a[x2][x1],w);
            ckadd(a[x2][x2],w);
            ckadd(a[x1][x1],w);
        }
        if(cnt>1) ckmul(ans,det(cnt-1));
    }
    print(ans);
    return 0;
}

baseline

不难感性理解区间个数和最大总权值形成凸函数关系,那么使用 \(\rm WQS\) 二分

根据 \(\rm WQS\) 实际含义,此时需要将所有大于 \(0\) 的区间选中,如果不能覆盖再选择一些区间将整个序列覆盖满

考察所有当前正权区间能否覆盖整个序列,否则对于不能覆盖的区间需要另行涉及一个 \(\rm DP\) :设 \(f_i\) 表示覆盖了前 \(i\) 个点的最大权值

转移枚举 \(j 并加入 \([j+1,i]\) 为结尾的正权区间,如果不能形成对 \((j,i]\) 的覆盖需要找 \(l\in[0,j]\) 作为左端点,右端点为 \(i\) 的最大权区间加入即可

写成表达式后发现全都可以使用树状数组维护,时间复杂度 \(\Theta(n\log V\log n)\)

Code Display
const int N=1e5+10,inf=9e18,U=1e14;
int n,K,sum[N],Mn[N];
pair seg[N],dp[N];
int b[N],m,ids[N],idm[N];
struct Fenwick_Tree_Sum{
    int cnt[N],sum[N];
    inline void insert(int x,int v){
        for(;x<=m;x+=x&(-x)) cnt[x]++,sum[x]+=v;
        return ;
    }
    inline pii query(int x){
        int qs=0,qc=0;
        for(;x;x-=x&(-x)) qc+=cnt[x],qs+=sum[x];
        return make_pair(qs,qc);
    }
    inline void clear(){
        memset(cnt,0,sizeof(cnt));
        memset(sum,0,sizeof(sum));
        return ;
    }
}Sum;
struct Fenwick_Tree_Pre{
    pair c[N];
    inline void insert(int x,pii v){
        for(;x<=m;x+=x&(-x)) ckmax(c[x],v);
        return ;
    }
    inline pii query(int x){
        pii res={-inf,0};
        for(;x;x-=x&(-x)) ckmax(res,c[x]);
        return res;
    }
    inline void clear(){
        rep(i,1,m) c[i]={-inf,0};
        return ;
    }
}Pre;
struct Fenwick_Tree_Suf{
    pair c[N];
    inline void insert(int x,pii v){
        for(;x;x-=x&(-x)) ckmax(c[x],v);
        return ;
    }
    inline pii query(int x){
        pii res={-inf,0};
        for(;x<=m;x+=x&(-x)) ckmax(res,c[x]);
        return res;
    }
    inline void clear(){
        rep(i,1,m) c[i]={-inf,0};
        return ;
    }
}Suf;
int id[N];
inline pii calc(int sl){
    Sum.clear(); Pre.clear(); Suf.clear();
    Sum.insert(ids[0],0);
    Pre.insert(idm[0],{0,0});
    Suf.insert(idm[0],{0,0});
    rep(i,1,n){
        seg[i]=seg[i-1];
        pair cur=Sum.query(id[i]=upper_bound(b+1,b+m+1,sum[i]-sl)-b-1);
        seg[i].fir+=(sum[i]-sl)*cur.sec-cur.fir;
        seg[i].sec+=cur.sec;
        Sum.insert(ids[i],sum[i]);
    }
    rep(i,1,n){
        pii tr1=Pre.query(id[i]),tr2=Suf.query(id[i]+1);
        tr1.fir+=seg[i].fir;
        tr1.sec+=seg[i].sec;
        tr2.fir+=seg[i].fir+sum[i]-sl;
        tr2.sec+=seg[i].sec+1;

        pair cur=dp[i]=max(tr1,tr2);
        cur.fir-=seg[i].fir;
        cur.sec-=seg[i].sec;
        Pre.insert(idm[i],cur);
        cur.fir-=Mn[i];
        Suf.insert(idm[i],cur);
    }
    return dp[n];
}
signed main(){
    freopen("baseline.in","r",stdin); freopen("baseline.out","w",stdout);
    n=read(); K=read();  
    rep(i,1,n){
        sum[i]=sum[i-1]+read();
        Mn[i]=min(Mn[i-1],sum[i]);
        b[++m]=sum[i];
    }
    ++m;
    sort(b+1,b+m+1); 
    m=unique(b+1,b+m+1)-b-1;
    rep(i,0,n){
        ids[i]=lower_bound(b+1,b+m+1,sum[i])-b;
        idm[i]=lower_bound(b+1,b+m+1,Mn[i])-b;
    }
    int l=-1e8,r=1e8,tar=-inf;
    while(l<=r){
        int mid=(l+r)>>1;
        pair res=calc(mid);
        if(res.sec>=K) tar=mid,l=mid+1;
        else r=mid-1; 
    }
    pair ans=calc(tar);
    print(ans.fir+K*tar);
    return 0;
}

相关