【考试总结】2022-03-15


梦批糼

只会 \(\Theta(n^6)\),做法是三维前缀和求出来包含每个点的立方体个数

注意总选择方案并不是合法立方体个数而是 \(\frac 18 n(n+1)m(m+1)k(k+1)\)

Code Display
const int N=70;
int sum[N][N][N],a[N][N][N],n,m,o,w,squ[N][N][N],val[N][N][N];
inline int calc(int a,int b,int c,int d,int e,int f){
    return sum[d][e][f]-sum[a-1][e][f]-sum[d][b-1][f]-sum[d][e][c-1]+sum[a-1][b-1][f]+sum[a-1][e][c-1]+sum[d][b-1][c-1]-sum[a-1][b-1][c-1];
}
signed main(){
    freopen("dream.in","r",stdin); freopen("dream.out","w",stdout);
    n=read(),m=read(),o=read(),w=read();
    rep(i,1,n) rep(j,1,m) rep(k,1,o){
        a[i][j][k]=!read();
        sum[i][j][k]=sum[i-1][j][k]+sum[i][j][k-1]+sum[i][j-1][k]-sum[i-1][j-1][k]-sum[i-1][j][k-1]-sum[i][j-1][k-1]+sum[i-1][j-1][k-1]+a[i][j][k];
    }
    rep(i,1,n) rep(j,1,m) rep(k,1,o) val[i][j][k]=read()*(!a[i][j][k]);
    int all=n*(n+1)*m*o*(o+1)*(m+1)/8%mod;
    int invall=ksm(all,w*(mod-2));
    int cnt=0;
    rep(i,1,n) rep(j,1,m) rep(k,1,o){
        rep(i1,i,n) rep(j1,j,m) rep(k1,k,o){
            if(!calc(i,j,k,i1,j1,k1)){
                ++cnt;
                squ[i][j][k]++;
                squ[i][j1+1][k]--;
                squ[i1+1][j][k]--;
                squ[i][j][k1+1]--;
                squ[i1+1][j1+1][k]++;
                squ[i1+1][j][k1+1]++;
                squ[i][j1+1][k1+1]++;
                squ[i1+1][j1+1][k1+1]--;
            }else break;
        }
    }
    int ans=0;
    rep(i,1,n) rep(j,1,m) rep(k,1,o){
        squ[i][j][k]+=squ[i-1][j][k]+squ[i][j][k-1]+squ[i][j-1][k]-squ[i-1][j-1][k]-squ[i-1][j][k-1]-squ[i][j-1][k-1]+squ[i-1][j-1][k-1];
        if(val[i][j][k]&&squ[i][j][k]){
            int per=del(ksm(cnt,w),ksm(cnt-squ[i][j][k],w));
            ckadd(ans,mul(val[i][j][k],per));
        }
    }
    print(mul(ans,invall));
    return 0;
}

等你哈苏德

考虑将黑色白色分别视为 \(\pm1\),那么题目中的限制就变成了前缀和数组相邻两个差的绝对值不过 \(1\)

此时一个线段就变成了两个端点的操作,如果初始状态所有线段没有颜色,那么离散化后将在坐标轴上相邻奇数度数点连边跑欧拉回路就能得到一组答案

如果有些线段是有初始颜色的,先将其任意定向,并计算每个点出度减入度的数值 \(d_i\)

那么最终的调整形式应该为将一些点的正度数还给那些度数为负的边,方式也就是让边反向,所以正权点和负权点通过图上能改变方向的边构成了二分图,使用网络流就能找到最终哪些边发生了反向

注意这里奇数度数点 \(i\) 的处理方式应当是添加 \([corr_i,coor_{i+1}-1]\) 的线段

Code Display
const int N=5e5+10,inf=0x3f3f3f3f3f3f3f3f;
struct Network_Flow{
    struct edge{int to,nxt,lim,id;}e[N<<2];
    int head[N],dep[N],S,T,ecnt=1,cur[N];
    inline void adde(int u,int v,int w,int id){
        e[++ecnt]={v,head[u],w,id}; head[u]=ecnt;
        return ;
    }
    inline void add(int u,int v,int w,int id){return adde(u,v,w,id),adde(v,u,0,0);}
    inline bool bfs(){
        queue q; q.push(S); rep(i,1,T) cur[i]=head[i],dep[i]=0; dep[S]=1;
        while(q.size()){
            int fr=q.front(); q.pop();
            for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
                int t=e[i].to; if(dep[t]) continue;
                dep[t]=dep[fr]+1; q.push(t);
            }
        } return dep[T];
    }
    inline int dfs(int x,int in){
        if(x==T) return in; int out=0;
        for(int i=head[x];i;cur[x]=i,i=e[i].nxt) if(e[i].lim){
            int t=e[i].to; if(dep[t]!=dep[x]+1) continue;
            int res=dfs(t,min(in,e[i].lim));
            e[i].lim-=res; e[i^1].lim+=res;
            in-=res; out+=res;
            if(!in) break;
        }
        if(!out) dep[x]=0; return out;
    }
    inline int dinic(){
        int sum=0;
        while(bfs()) sum+=dfs(S,inf);
        return sum;
    }

}F;
bool vis[N];
int deg[N],n,m,lsh[N],cnt,l[N],r[N],dir[N];
int val[N];
signed main(){
    freopen("wait.in","r",stdin); freopen("wait.out","w",stdout);
    m=read(); n=read();
    rep(i,1,m){
        l[i]=read(),r[i]=read()+1; dir[i]=read();
        lsh[++cnt]=l[i],lsh[++cnt]=r[i];
    }
    sort(lsh+1,lsh+cnt+1); cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
    vector opt(cnt+1);
    rep(i,1,m){
        l[i]=lower_bound(lsh+1,lsh+cnt+1,l[i])-lsh;
        r[i]=lower_bound(lsh+1,lsh+cnt+1,r[i])-lsh;
        opt[l[i]]^=1; opt[r[i]]^=1;
        if(dir[i]<=0){
            deg[l[i]]--,deg[r[i]]++;
            if(dir[i]==-1) F.add(r[i],l[i],1,i);
            dir[i]=0;
        }else deg[r[i]]--,deg[l[i]]++;
    }
    rep(i,1,cnt) if((opt[i]^=opt[i-1])){
        opt[i]^=1; opt[i+1]^=1;
        F.add(i,i+1,1,0);
        deg[i]++; deg[i+1]--;
    }
    F.S=cnt+1; F.T=cnt+2;
    int Flow=0;
    rep(i,1,cnt) if(deg[i]){
        deg[i]/=2;
        if(deg[i]<0) F.add(i,F.T,-deg[i],0);
        else F.add(F.S,i,deg[i],0),Flow+=deg[i];
    }
    if(F.dinic() vals(cnt+1);
    rep(i,1,m) print(dir[i]); putchar('\n');
    return 0;
}

喜欢最最痛

考虑猜测并推广 \(m=2\) 的情况:在树上选择若干个不相交的链使得权值最大,再加上已经给出的新边的前若干小

使用类似模拟费用流的反悔操作:每次选出当前树上的带权直径,将直径的边权取反

不难发现选了 \(i\) 次带权直径就一定能找到 \(i\) 个带权链,如果被取反了多次就可以理解成没有被选择

根据 【林克卡特树】 发现选择的 链的数量 与 权值总和 构成的函数是斜率递减的函数

而给出若干数,选出前 \(k\) 小还是一个斜率单递增的函数,那么答案也是关于选择链数量的凸函数

那么这部分拿一个单调指针扫描即可

使用 \(\text{LCT}\) 找树上带权直径,需要维护的内容是每个点开始向下的链的最大前缀,子树里面最长链

那么为了合并,我们仍然需要维护每条链的最大后缀和链的边权和(其实就是最大字段和的模型)

为了 翻转/连边 那么需要维护从链底到链顶两份信息(和 【定位系统】 一题类似),为了支持链权取反,还要维护正权信息和负权信息

对于虚子树信息,使用两个 multiset 来分别维护虚儿子前缀链和子树里面最长链,记得更新答案也可以使用两个虚子树信息来合并

虚实转换的时候直接和 【定位系统】 写一样的 fetch,lost 即可,这样子的写法确实非常简便了

实现的时候需要重载路径结构体 和 每个点信息的结构体,这里有一个非常神仙的写法是强制代码里面最大前缀,后缀的两个端点相同,实际上另一个端点就是这个点本身

Code Display
const int N=2e5+10,inf=0x3f3f3f3f3f3f3f3f;
int n,m,ls[N],rs[N],fa[N],val[N];
bool rev[N],sig[N];
struct path{
    int u,v,w; path(){}
    path(int U,int V,int W){u=U,v=V,w=W;}
    bool operator <(const path &a)const{
        if(w^a.w) return wres.v) swap(res.u,res.v);
        return res;
    }//attention to the order of the nodes
    path operator +(const int &a)const{return path(u,v,w+a);}
    path operator -(const int &a)const{return path(u,v,w-a);}
    bool operator ==(const path &a)const{return u==a.u&&v==a.v&&w==a.w;}
};
struct data{
    path pre,suf,ans;
    int sum; 
    data(){}
    data(path a,path b,path c,int d){pre=a,suf=b; ans=c; sum=d;}
    data operator +(const data &a)const{
        data res;
        res.sum=sum+a.sum;
        res.pre=max(pre,a.pre+sum);
        res.suf=max(suf+a.sum,a.suf);
        res.ans=max(max(a.ans,ans),suf+a.pre);
        return res;
    }
}dp[N],lef[N][2],rig[N][2];
multiset pre[N],chain[N];
// Code Designer! here only data.ans's path may has a different u,v
// "pre" and "suf" has the meaning of u->current x
inline bool isroot(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
inline void push_rev(int x){
    swap(ls[x],rs[x]);
    swap(lef[x][0],rig[x][0]);
    swap(lef[x][1],rig[x][1]);
    rev[x]^=1;
    return ;
}
inline void push_up(int x){
    lef[x][0]=rig[x][0]=data(dp[x].pre+val[x],dp[x].pre+val[x],dp[x].ans,val[x]);
    lef[x][1]=rig[x][1]=data(dp[x].pre-val[x],dp[x].pre-val[x],dp[x].ans,-val[x]);
    if(ls[x]){
        lef[x][0]=lef[ls[x]][0]+lef[x][0];
        lef[x][1]=lef[ls[x]][1]+lef[x][1];
        rig[x][0]=rig[x][0]+rig[ls[x]][0];
        rig[x][1]=rig[x][1]+rig[ls[x]][1];
    }
    if(rs[x]){
        lef[x][0]=lef[x][0]+lef[rs[x]][0];
        lef[x][1]=lef[x][1]+lef[rs[x]][1];
        rig[x][0]=rig[rs[x]][0]+rig[x][0];
        rig[x][1]=rig[rs[x]][1]+rig[x][1];
    }
}
inline void push_sig(int x){
    swap(lef[x][0],lef[x][1]);
    swap(rig[x][0],rig[x][1]);
    sig[x]^=1; val[x]*=-1;
    return ;
}
inline void push_down(int x){
    if(rev[x]){
        if(ls[x]) push_rev(ls[x]);
        if(rs[x]) push_rev(rs[x]);
        rev[x]=0;
    }
    if(sig[x]){
        if(ls[x]) push_sig(ls[x]);
        if(rs[x]) push_sig(rs[x]);
        sig[x]=0;
    } return ;
}
inline void rotate(int x){
    int y=fa[x],z=fa[y];
    if(!isroot(y)) if(ls[z]==y) ls[z]=x; else rs[z]=x;
    if(ls[y]==x) ls[y]=rs[x],fa[rs[x]]=y,rs[x]=y;
    else rs[y]=ls[x],fa[ls[x]]=y,ls[x]=y;
    fa[x]=z; fa[y]=x; return push_up(y),push_up(x);
}
int stk[N],top;
inline void splay(int x){
    int y=x; stk[top=1]=y;
    while(!isroot(y)) stk[++top]=y=fa[y];
    while(top) push_down(stk[top--]);
    while(!isroot(x)){
        int y=fa[x],z=fa[y];
        if(!isroot(y)) rotate(((ls[z]==y)^(ls[y]==x))?x:y);
        rotate(x);
    } return ;
}
inline void recalc(int x){
    assert(chain[x].size()&&pre[x].size());    
    dp[x].pre=*pre[x].rbegin();
    dp[x].ans=*chain[x].rbegin();
    if(pre[x].size()>=2){
        multiset::iterator iter=prev(pre[x].end());
        ckmax(dp[x].ans,*prev(iter)+*iter+val[x]);
    } 
    return ;
}
inline void lost(int x,int son){
    chain[x].erase(chain[x].find(lef[son][0].ans));
    pre[x].erase(pre[x].find(lef[son][0].pre));
    assert(chain[x].size()&&pre[x].size());
    recalc(x);
}
inline void fetch(int x,int son){
    chain[x].insert(lef[son][0].ans);
    pre[x].insert(lef[son][0].pre);
    assert(chain[x].size()&&pre[x].size());
    recalc(x);
}
inline void access(int x){
    for(int y=0;x;x=fa[y=x]){
        splay(x);
        path now=lef[x][0].ans;   
        if(rs[x]) fetch(x,rs[x]);
        if(y) lost(x,y);
        rs[x]=y; push_up(x);
    }
}
inline void make_root(int x){
    access(x); splay(x); push_rev(x);
}
inline void link(int x,int y){
    make_root(x);
    access(y); splay(y);
    int tmp=y; while(ls[tmp]) tmp=ls[tmp]; if(tmp==x) return ;
    push_rev(y);
    fa[x]=y; fetch(y,x); push_up(y);
    return ;
}
int ans,sum[N];
signed main(){
    freopen("love.in","r",stdin); freopen("love.out","w",stdout);
    n=read(); m=read();
    rep(i,1,n){
        pre[i].insert({i,i,0});
        chain[i].insert({i,i,0});
        recalc(i); push_up(i);
    }
    rep(i,1,n-1){
        int u=read(),v=read(),w=read();
        ans+=2*w;
        val[i+n]=w; 
        pre[i+n].insert({0,0,-inf}); chain[i+n].insert({0,0,-inf});
        recalc(i+n); push_up(i+n);
        link(u,i+n); 
        link(v,i+n);
    }
    rep(i,1,m){
        make_root(1);
        path now=lef[1][0].ans;
        make_root(now.u);
        access(now.v); splay(now.v); 
        push_sig(now.v);
        sum[i]=now.w+sum[i-1];
    }
    print(ans);
    multiset vals,oth;
    int cnt=0,nowsum=0,indic=0;
    while(m--){
        int w=read(); ++cnt; 
        if(vals.size()){
            int vv=*prev(vals.end());
            if(w

学校训练已经出现很多次 LCT+DDP 的题目了,不清楚是什么风向。多项式这个大毒被 ban 之后的新药了是吧?

相关