省选武汉联测 9

今天考昨天的题。因为不算 rating 所以交了几发爆大零。

背包问题模板

曾经有一个题是所有物品大小都是 \(a\times 2^b\)\(01\) 背包。那个题是先把每个 \(2^b\) 分组后组内跑背包再合并。这个题二进制分组一下就变成那个题了。那个题是 P3188。

合并的时候设 \(g_{i,j}\)\(2^i\) 层的物品总大小为 \(j\times 2^i\) 的最大价值,\(dp_{i,j}\) 为合并到第 \(2^i\) 层,\(i\) 位之前的所有位都和 \(m\) 匹配,之后的大小为 \(j\times 2^i\) 的最大价值。合并显然:考虑之前一位留下多少,新的就是之前一位留下的去掉给 \(m\) 的第 \(i-1\) 位匹配的,再除以 \(2\)。即:

\[dp_{i,j}=dp{i-1,2(j-k)+m_{i-1}}+g_{i,k} \]

答案就是 \(dp_{\log m,1}\)。开 int128。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define int long long
using namespace std;
int n,m;
vector<pair<int,__int128> >v[100];
__int128 dp[110][5010],g[110][5010];
void print(__int128 x){
    if(x>9)print(x/10);
    putchar(x%10+'0');
}
void getg(__int128 dp[],vector<pair<int,__int128> >v){
    for(pair<int,__int128>p:v){
        int b=p.first;__int128 c=p.second;
        for(int j=2000;j>=b;j--)dp[j]=max(dp[j],dp[j-b]+c);
    }
}
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        int a,b,c;scanf("%lld%lld%lld",&a,&b,&c);
        for(int j=0;;j++){
            int val=1ll<<j;
            if(a<val)break;
            v[j].push_back(make_pair(b,(__int128)c*val));
            a-=val;
        }
        for(int j=0;a;j++){
            if((a>>j)&1){
                int val=1ll<<j;
                v[j].push_back(make_pair(b,(__int128)c*val));
                a-=val;
            }
        }
    }
    scanf("%lld",&m);
    for(int i=0;i<=__lg(m);i++)getg(g[i],v[i]);
    for(int i=0;i<=2000;i++)dp[0][i]=g[0][i];
    for(int i=1;i<=__lg(m);i++){
        int w=(m>>i-1)&1;
        for(int j=0;j<=2000;j++){
            for(int k=0;k<=j;k++){
                dp[i][j]=max(dp[i][j],dp[i-1][2*(j-k)+w]+g[i][k]);
            }
        }
    }
    print(dp[__lg(m)][1]);puts("");
    return 0;
}

南河泄露

神秘 dp。

正着不是很好推,考虑反过来,从小到大区间考虑。设 \(dp_{i,j,0/1}\) 为查 \([i,j]\) 区间,从 \(i/j\) 点开始走的代价。转移考虑走到左边或右边:

\[dp_{i,j,0}=\max(\min(dp_{i,k,1},dp_{k,j,0})+d_k+(s_k-s_i)^2) \]

\[dp_{i,j,1}=\max(\min(dp_{i,k,1},dp_{k,j,0})+d_k+(s_j-s_k)^2) \]

爆跑区间 dp 是 \(O(n^3)\) 的。观察性质发现中间那个 \(\min\) 是存在 \(p\) 使得 \(k\le p\)\(\min\) 取左边,大于时取右边。

那么分开考虑。以 \(dp_{i,j,0}\) 的式子为例。

对于 \(p\) 左边,是 \(dp_{i,k,1}+(s_k-s_i)^2+d_k\),容易发现和 \(j\) 没有关系,整个前缀最大值扫一遍就行了。

对于右边,是 \(dp_{k,j,0}+(s_j-s_k)^2+d_k\),拆一下发现是个斜率优化。那么对每个位置维护它对应的分界点 \(p\) 位置和一个上凸包,每次 \(p\) 单调左移,需要往凸包左边加上得到的点。

\(dp_{i,j,1}\) 的式子需要对称地整个前缀凸包和后缀最大值。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define int long long
using namespace std;
int n,s[3010],d[3010],p[3010],dp[3010][3010][2];
struct tubao{
    int stk[3010],top;
    long long y[3010];
    void addl(int i,int val){
        y[i]=val;
        while(top>1&&(s[stk[top]]-s[stk[top-1]])*(y[i]-y[stk[top]])<=(s[i]-s[stk[top]])*(y[stk[top]]-y[stk[top-1]]))top--;
        stk[++top]=i;
    }
    void addr(int i,int val){
        y[i]=val;
        while(top>1&&(s[stk[top]]-s[stk[top-1]])*(y[i]-y[stk[top]])>=(s[i]-s[stk[top]])*(y[stk[top]]-y[stk[top-1]]))top--;
        stk[++top]=i;
    }
    int query(int k){
        if(!top)return -0x3f3f3f3f3f3f3f3f;
        while(top>1&&y[stk[top]]-k*s[stk[top]]<y[stk[top-1]]-k*s[stk[top-1]])top--;
        return y[stk[top]]-k*s[stk[top]];
    }
}L,R[3010];
int mxl,mxr[3010];
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&s[i]);
    s[n+1]=114514;
    for(int i=1;i<=n;i++)scanf("%lld",&d[i]);
    p[n+1]=n+1;
    for(int i=n;i>=0;i--){
        p[i]=i;mxl=0;
        L.top=0;
        int pos=i+1;
        for(int j=i+2;j<=n+1;j++){
            while(pos<j&&dp[i][pos][1]<dp[pos][j][0]){
                mxl=max(mxl,dp[i][pos][1]+(s[pos]-s[i])*(s[pos]-s[i])+d[pos]);
                L.addr(pos,dp[i][pos][1]+s[pos]*s[pos]+d[pos]);
                pos++;
            }
            while(p[j]>pos){
                p[j]--;
                mxr[j]=max(mxr[j],dp[p[j]][j][0]+(s[j]-s[p[j]])*(s[j]-s[p[j]])+d[p[j]]);
                R[j].addl(p[j],dp[p[j]][j][0]+s[p[j]]*s[p[j]]+d[p[j]]);
            }
            dp[i][j][0]=max(mxl,R[j].query(2*s[i])+s[i]*s[i]);
            dp[i][j][1]=max(L.query(2*s[j])+s[j]*s[j],mxr[j]);
        }
    }
    printf("%lld\n",dp[0][n+1][0]);
    return 0;
}

最短路问题模板

暴力跑树剖线段树优化建图然后最短路看上去是两只 \(\log\) 的,实际上如果堆优化最短路是三个。然而过了。如果线段树优化的话可以用一些操作做到两只,Kaguya 好像是这么写的。

好像有玄妙做法,咕了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int K=1000000;
int n,m;
vector<pair<int,int> >g[1200010];
void add(int u,int v,int w){
    g[u].push_back(make_pair(v,w));
}
int num,dfn[200010],rk[200010],top[200010],fa[200010],son[200010],size[200010],dep[200010];
void dfs1(int x,int f){
    dep[x]=dep[f]+1;size[x]=1;fa[x]=f;
    for(pair<int,int> p:g[x]){
        int v=p.first;
        if(v!=f){
            dfs1(v,x);
            size[x]+=size[v];
            if(size[son[x]]<size[v])son[x]=v;
        }
    }
}
void dfs2(int x,int f,int tp){
    dfn[x]=++num;rk[num]=x;top[x]=tp;
    if(son[x])dfs2(son[x],x,tp);
    for(pair<int,int> p:g[x]){
        int v=p.first;
        if(v!=f&&v!=son[x])dfs2(v,x,v);
    }
}
int a[200010];
void build(int rt,int l,int r){
    if(l==r){
        a[rk[l]]=rt;return;
    }
    int mid=(l+r)>>1;
    add(rt,lson,0);add(rt,rson,0);
    build(lson,l,mid);build(rson,mid+1,r);
}
void update(int rt,int L,int R,int l,int r,int x,int w){
    if(l<=L&&R<=r){
        add(x+K,rt,w);return;
    }
    int mid=(L+R)>>1;
    if(l<=mid)update(lson,L,mid,l,r,x,w);
    if(mid<r)update(rson,mid+1,R,l,r,x,w);
}
void modify(int u,int x,int y,int w){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],u,w);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,dfn[x],dfn[y],u,w);
}
struct stu{
    int x;long long w;
    bool operator<(const stu&s)const{
        return w>s.w;
    }
};
priority_queue<stu>q;
long long dis[1200010];
bool vis[1200010];
void dijkstra(int st){
    memset(dis,0x3f,sizeof(dis));
    dis[st]=0;q.push({st,0});
    while(!q.empty()){
        int x=q.top().x;q.pop();
        if(!vis[x]){
            vis[x]=true;
            for(pair<int,int> p:g[x]){
                int v=p.first,w=p.second;
                if(dis[v]>dis[x]+w){
                    dis[v]=dis[x]+w;
                    if(!vis[v])q.push({v,dis[v]});
                }
            }
        }
    }
}
struct eg{
    int u,v,w;
}edge[200010];
int read(){
    int x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
    return x;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read(),w=read();
        add(u,v,w);add(v,u,w);edge[i]={u,v,w};
    }
    dfs1(1,0);dfs2(1,0,1);
    for(int i=1;i<=n;i++)g[i].clear();
    build(1,1,n);
    for(int i=1;i<=n;i++)add(a[i],i+K,0),add(i+K,a[i],0);
    for(int i=1;i<n;i++)add(edge[i].u+K,a[edge[i].v],edge[i].w),add(edge[i].v+K,a[edge[i].u],edge[i].w);
    while(m--){
        int od=read(),x=read(),u,v,w;
        if(od==1){
            u=read(),v=read(),w=read();
            modify(x,u,v,w);
        }
        else{
            u=read(),w=read();
            update(1,1,n,dfn[u],dfn[u]+size[u]-1,x,w);
        }
    }
    dijkstra(1+K);
    for(int i=1;i<=n;i++)printf("%lld\n",dis[a[i]]);
    return 0;
}
posted @ 2023-03-23 17:21  gtm1514  阅读(17)  评论(0编辑  收藏  举报