【考试总结】2022-02-12

区间 DP

fl,r 表示将 [l,r] 全部删空的能得到的最大价值,dpj 表示可以 [1,j] 不删完所能得到的最大价值

转移 f 首先考虑 l,r 不在同一次操作中被删除的情况,那么可以枚举分界线

而对于在同一次操作中被删除的情况考虑该次操作删除的数字的值域形态:单峰

那么尝试计算 incl,r,decl,r 分别表示保留 l 和保留 r 的情况下将序列删成加一递增的最大获利和删成减一递减的最大获利即可

这两个状态的转移根据定义,枚举那些和 al/ar 差一的位置,该删空的删掉就行了

Code Display
const int N=410,inf=0x3f3f3f3f3f3f3f3f;
int a[N],v[N],ans[N],n,f[N][N],incr[N][N],decr[N][N];
signed main(){
    freopen("good.in","r",stdin); freopen("good.out","w",stdout);
    n=read(); 
    rep(i,1,n) v[i]=read();
    rep(i,1,n) rep(j,1,i-1) ckmax(v[i],v[j]+v[i-j]);
    rep(i,1,n) a[i]=read(); 
    memset(incr,-0x3f,sizeof(incr));
    memset(decr,-0x3f,sizeof(decr));
    memset(f,-0x3f,sizeof(f));
    rep(i,1,n){
        f[i][i]=v[1]; 
        f[i+1][i]=incr[i][i]=decr[i][i]=0;
    }
    f[1][0]=0;
    for(int len=2;len<=n;++len){
        for(int l=1;l+len-1<=n;++l){
            int r=l+len-1;
            rep(i,l,r-1) ckmax(f[l][r],f[l][i]+f[i+1][r]);
            
            rep(i,l+1,r) if(a[i]==a[l]-1){
                ckmax(decr[l][r],decr[i][r]+f[l+1][i-1]);
            }
            rep(i,l,r-1) if(a[i]==a[r]-1){
                ckmax(incr[l][r],incr[l][i]+f[i+1][r-1]);
            }
            rep(i,l,r){
                if(incr[l][i]==inf||decr[i][r]==inf) continue;
                if(a[i]<a[l]||a[i]<a[r]) continue;
                ckmax(f[l][r],v[a[i]-a[l]+a[i]-a[r]+1]+incr[l][i]+decr[i][r]);
            }
        }
    }
    for(int i=1;i<=n;++i){
        ans[i]=ans[i-1];
        for(int j=i;j>=1;--j) ckmax(ans[i],ans[j-1]+f[j][i]);
    }
    print(ans[n]);
    return 0;
}

不难发现这个最短距离是一条边的长度,这条边一定在全图的 MST

关注到答案的来源一定是父亲和儿子的边,那么对于每个父亲,维护每种颜色的儿子在 MST 上返祖边的权值,并将与自己异色的每个颜色里面最小值放到全局维护的 std::multiset

在修改颜色的时候更改当前点 x 的父亲处维护的信息,然后再考虑与其儿子的连边中产生对 std::multiset 的变化即可

时间复杂度 Θ(qlogn)

Code Display
const int N=3e5+10;
multiset<int> ans;
int n,m,col[N],val[N],Q,fa[N];
struct edge{int u,v,w;}e[N];
struct DSU{
    int fa[N];
    inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    inline void merge(int x,int y){return fa[find(x)]=find(y),void();}
    inline void init(){rep(i,1,n) fa[i]=i; return ;}
}Dsu;
vector<pii> g[N];
map<int,multiset<int> >mp[N];
inline void get_fa(int x,int fat){
    fa[x]=fat;
    for(auto e:g[x]) if(e.fir!=fat) val[e.fir]=e.sec,get_fa(e.fir,x);
    return ;
}
inline void del(int x,int mycol){
    if(!fa[x]) return ;
    assert(mp[fa[x]].count(mycol));
    multiset<int> &now=mp[fa[x]][mycol];
    assert(now.find(val[x])!=now.end());
    now.erase(now.find(val[x])); 
    if(!now.size()||(*now.begin())>val[x]){
        if(mycol!=col[fa[x]]){
            assert(ans.find(val[x])!=ans.end());
            ans.erase(ans.find(val[x]));
        }
        if(!now.size()){
            mp[fa[x]].erase(mycol);
        }else{
            if(mycol!=col[fa[x]]) ans.insert(*now.begin());
        }
    }
    return ;
}
inline void ins(int x,int mycol){
    if(!fa[x]) return ;
    if(!mp[fa[x]].count(mycol)){
        mp[fa[x]][mycol].insert(val[x]);
        if(mycol!=col[fa[x]]) ans.insert(val[x]);
        return ;
    }
    assert(mp[fa[x]][mycol].size());
    if(mycol!=col[fa[x]]){
        assert(ans.find(*mp[fa[x]][mycol].begin())!=ans.end());
        ans.erase(ans.find(*mp[fa[x]][mycol].begin()));
    }
    mp[fa[x]][mycol].insert(val[x]);
    if(mycol!=col[fa[x]]) ans.insert(*mp[fa[x]][mycol].begin());
    return ;
}
signed main(){
    freopen("color.in","r",stdin); freopen("color.out","w",stdout);
    n=read(); m=read(); read(); Q=read();
    rep(i,1,m){
        int u=read(),v=read(),w=read();
        e[i]={u,v,w};
    }
    sort(e+1,e+m+1,[&](const edge a,const edge b){return a.w<b.w;});
    Dsu.init();
    for(int i=1;i<=m;++i){
        int u=e[i].u,v=e[i].v;
        if(Dsu.find(u)==Dsu.find(v)) continue;
        Dsu.merge(Dsu.find(u),Dsu.find(v));
        g[u].emplace_back(v,e[i].w);
        g[v].emplace_back(u,e[i].w);
    }
    get_fa(1,0);
    rep(i,1,n) col[i]=read();
    rep(i,1,n) ins(i,col[i]);
    while(Q--){
        int x=read(),c=read();
        if(c==col[x]){
            print(*ans.begin());
            continue;
        }
        del(x,col[x]);
        if(mp[x].count(col[x])) ans.insert(*mp[x][col[x]].begin());
        if(mp[x].count(c)) ans.erase(ans.find(*mp[x][c].begin()));
        ins(x,col[x]=c);
        col[x]=c;
        print(*ans.begin());
    }
    return 0;
}

注意 vivi+1,那么设 dpi 表示已考虑 [1,i] 的前缀,其最小 borderi 的方案数

转移考虑删掉最小 border[1,i) 的方案数即可,形式化地讲:

fn=i=1nvi2infij=i+1nj

使用前缀积维护,那么显然该和 i 有关就只和 i 有关,还有一项之和 ni 有关,使用半在线卷积求就行了

问题的限制是 2in 的时候才能转移,那么在半在线卷积的过程中,分成 rlmid+1 的部分直接乘和 [1,rl][l,mid] 的部分再写一个分治即可

注意这个额外的分治只会在 l=1,r=n2i 时调用,那么复杂度仍然是 Θ(nlog2n),配合 vi 全相同时 Θ(n) 求解即可通过了!

Code Display
int cl,cr;
int f[N],g[N],tmpf[N],tmpg[N];
inline void solve2(int l1,int r1,int l2,int r2){
    if(2*r2<cl) return ;
    if(r2<l1||r1<l1||r2<l2) return ;
    if(l1==r1&&l2==r2){
        if(l1+l2>=cl&&l2+l1<=cr) ckadd(f[l1+l2],mul(prd[l2],mul(iprd[l1],f[l1])));
        return ;
    }
    int mid=(l1+r1)>>1;
    if(r2<=mid) return solve2(l1,mid,l2,r2);
    int lim=1; while(lim<=(r2-l1+1)<<1) lim<<=1;
    rep(i,0,lim-1) tmpf[i]=tmpg[i]=0;
    for(int i=mid+1;i<=r2;++i) tmpg[i-mid-1]=prd[i];
    for(int i=l1;i<=mid;++i) tmpf[i-l1]=mul(iprd[i],f[i]);
    NTT(tmpf,lim,1); NTT(tmpg,lim,1);
    for(int i=0;i<lim;++i) ckmul(tmpf[i],tmpg[i]);
    NTT(tmpf,lim,-1);
    for(int i=max(cl,l1+mid+1);i<=min(cr,r2+mid);++i) ckadd(f[i],tmpf[i-mid-l1-1]);
    solve2(l1,mid,l2,mid); solve2(mid+1,r1,mid+1,r2);
    return ;
}
int lst;
inline void solve(int l,int r){
    if(l==r){
        f[l]=del(prd[l],f[l]);
        return ;
    }
    int mid=(l+r)>>1; solve(l,mid);
    if(r-l>=mid+1){
        int lim=1; while(lim<=((r-l+1)<<1)) lim<<=1;
        rep(i,0,lim-1) tmpf[i]=tmpg[i]=0;
        for(int i=mid+1;i<=r-l;++i) tmpg[i-mid-1]=prd[i];
        for(int i=l;i<=mid;++i) tmpf[i-l]=mul(iprd[i],f[i]);
        NTT(tmpf,lim,1); NTT(tmpg,lim,1);
        for(int i=0;i<lim;++i) ckmul(tmpf[i],tmpg[i]);
        NTT(tmpf,lim,-1);
        for(int i=mid+1+l;i<=r;++i) ckadd(f[i],tmpf[i-l-mid-1]);        
    }
    if(r-l>=l){
        cl=mid+1; cr=r;
        solve2(lst+1,mid,lst+1,mid); 
        solve2(1,lst,lst+1,mid);
        lst=mid;
    }
    return solve(mid+1,r);
}
bool sam;
int dp[N];
signed main(){
    freopen("music.in","r",stdin); freopen("music.out","w",stdout);
    n=read(); prd[0]=iprd[0]=1;
    sam=1;
    rep(i,1,n){
        v[i]=read();
        sam&=v[1]==v[i];
        prd[i]=mul(prd[i-1],v[i]),iprd[i]=ksm(prd[i],mod-2);
    }
    if(sam){
        int pw=1,sum=0;
        rep(i,1,n){
            ckmul(pw,v[1]);
            dp[i]=mul(pw,del(1,sum));
            if(i&1){
                int tar=i/2+1;
                ckadd(sum,mul(dp[tar],ksm(v[1],mod-1-2*tar)));
            }
        }
        print(dp[n]);
        exit(0);
    }
    solve(1,(n+1)/2);
    for(int i=1;2*i<=n;++i) ckadd(f[n],mul(prd[n-i],mul(iprd[i],f[i])));
    print(del(prd[n],f[n]));
    return 0;
}
posted @   没学完四大礼包不改名  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示