记502 dp专练

  趁着503的清早 我还算清醒把昨天老师讲的内容总结一下,昨天有点迷了 至使我A的几道题都迷迷糊糊的。(可能是我太菜了)

这道题显然是 数字三角形的变形 好没有经过认真思考然后直接暴力了 这是很不应该的 但正解 是需要你能深刻理解数字三角形的模板式究竟是什么含义这显然是我这种 感觉很简单的东西没有认真思考每一个f值所以造成这道题没有写出正解。

bf: 求出一条答案路径,如果询问在这条路径上那就直接暴力跑一遍不在的话直接输出最大值 复杂度n^3 实际低分很低貌似写挂了。

当然 100分的做法也很简单 我们只需考虑原本的f数组的含义表示右上到下到此点的最大的和。

对于一个答案显然是某一行中的一个点的由上到下和由下到上的和。 于是可以加一个数组g[i][j]表示右下到上的到达这个点的最大和。

显然 对于ban掉的一个点,不在1,1 那就不是-1 然后次大值是由 当前这行旁边的点构成的 那么我们再在当前这行再求一个次大值直接用f 和g数组求即可。解法就是这样了 对于每一行求出最大值 求出最大值 路径 求出次大值,询问输出即可。n^2

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define INF 2147483646
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define max(x,y) (x>y?x:y)
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=1003;
int n,m,s1,s2,t;
int ans,maxx;
int a[MAXN][MAXN];
int b[MAXN],vis[MAXN][MAXN];
int f[MAXN][MAXN];
int g[MAXN][MAXN],w[MAXN][MAXN];//f[i][j]表示到了第i j个点所得到的价值最大
int main()
{
    //freopen("1.in","r",stdin);
    freopen("tower.in","r",stdin);
    freopen("tower.out","w",stdout);
    memset(a,0,sizeof(a));
    n=read();m=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            a[i][j]=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
        {
            f[i][j]=max(f[i][j],f[i-1][j]+a[i][j]);
            f[i][j]=max(f[i][j],f[i-1][j-1]+a[i][j]);
        }
    for(int i=n;i>=1;--i)for(int j=1;j<=i;++j)
    {
        g[i][j]=max(g[i][j],g[i+1][j]+a[i][j]);
        g[i][j]=max(g[i][j],g[i+1][j+1]+a[i][j]);
    }
    //for(int i=1;i<=n;++i)maxx=max(maxx,f[n][i]);
    maxx=g[1][1];
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=i;++j)if(g[i][j]+f[i][j]-a[i][j]==maxx){vis[i][j]=1;break;}
        for(int j=1;j<=i;++j)
        {
            if(vis[i][j])continue;
            b[i]=max(g[i][j]+f[i][j]-a[i][j],b[i]);
            //cout<<b[i]<<endl;
        }
    }
    //puts("");
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        //cout<<vis[x][y]<<endl;
        if(x==1&&y==1){puts("-1");continue;}
        if(vis[x][y])put(b[x]);
        else put(maxx);
    }
    return 0;
}
View Code

这道题 就很有意思了,是这几道题我暴力都写的比较难受的一道想出设多维来想办法进行状态转移 我觉得不管怎样转移都是具有极强的后效性的,2h后放弃了对于m==1的情况 我写了一个n^2k的暴力dp 写完了还觉得没什么问题 事实上我这种做法是不必要的。

考虑 m==1怎么写其实无非就是对于一个长度为n的序列 分成m+1段然后使每一段的价值等差数列的和的和最小。其实直接分就好了不需要dp

可能很显然的想出这是正确的,然后 无非是余数的问题 考虑每一段后都跟一个余数直至把余数数完。

那对于每一个窝的 我们使用k次以后这个窝所带来的价值 其实现在问题再次转换 转换成了 m个窝 k次清空操作 然后询问最小价值。

显然了 这是一个资源分配类型的dp 直接dp 转移一下就好了。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 21474836460000000ll
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=1000002,maxn=102,Maxn=502;
ll n,m,p,T;
ll ans;
ll a[MAXN],c[maxn];
//对于 m==1 显然采用贪心可以 将一段序列分成p+1段的最小值 取平均即可 
ll w[maxn][Maxn];//w[i][j]表示 对于第i种物品分成j段的代价 
ll f[maxn][Maxn];//表示前i种牛分配到j个清理 得到了多少价值 
ll cost(ll x,ll y)
{
    ++y;
    ll cnt=x/y;
    ll mod=x%y;
    return ((cnt+1)*cnt/2)*y+mod*(cnt+1);
}
int main()
{
    //freopen("1.in","r",stdin);
    freopen("noise.in","r",stdin);
    freopen("noise.out","w",stdout);
    n=read();m=read();p=read();
    for(int i=1;i<=n;++i)a[i]=read(),++c[a[i]];
    for(int i=1;i<=m;++i)
        for(int j=0;j<=p;++j)w[i][j]=cost(c[i],j);
    for(int i=1;i<=m;++i)
        for(int j=0;j<=p;++j)
        {
            f[i][j]=INF;
            for(int k=0;k<=j;++k)
                f[i][j]=min(f[i][j],f[i-1][k]+w[i][j-k]);
        }
    put(f[m][p]);
    return 0;
}
View Code

其实很简单 但是针对m==1的情况我没有好好的思考所以 造成了整道题的崩溃。或者说我对模板只是感觉简单并没有利用它。

值得一提的是上述式子还可以进行单调性优化时间复杂度为mk 发现n>mk时间复杂度为O(n)(哇如此优秀。

这道题的暴力超级好 直接01背包都有40分 加两个排序可以做到Mn的复杂度了。期望得分60 实际得分:60;

考虑后40分怎么办。看出M非常的大v非常的小 这又是01背包的变换形式了 仔细思考我们背包是根据容量来确定价值的,容量一定价值一定、

那如果是根据价值来确定容量呢 这样的01背包也是可以的吧 这样就价值为多少的时候 需要的金钱数。

显然 这个01背包的变形 是没有那么简单的,需要考虑 时间 这个我们按照时间排序物品即可。然后在f数组里进行二分查找即可。

细节需要处理 f数组可能不具单调性 O(n)扫一遍使其具有单调性即可。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 21474836460000ll
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=100002,maxn=302;
int n,m,maxx,flag=1;
struct wy
{
    int w,v,ti;
    int friend operator <(wy x,wy y){return x.ti<y.ti;}
}t[maxn];
struct wy1
{
    int x,y;//x为时间y为金钱
}s[MAXN];
ll f[maxn][maxn*maxn];//f[i][j]表示前i个物品 价值为j的最小花费 
inline int find(int xx)
{
    int l=0,r=n;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(ti(mid)<=xx)l=mid;
        else r=mid;
    }
    if(ti(r)<=xx)return r;
    return l;
}
inline int carculate(int i,int x)
{
    int l=0,r=maxx;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(f[i][mid]<=x)l=mid;
        else r=mid;
    }
    if(f[i][r]<=x)return r;
    return l;
}
int main()
{
    //freopen("1.in","r",stdin);
    freopen("market.in","r",stdin);
    freopen("market.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;++i)
    {
        w(i)=read();
        v(i)=read();
        ti(i)=read();
        maxx+=v(i);
    }
    //put(maxx);
    sort(t+1,t+1+n);
    for(int i=1;i<=m;++i)s[i].x=read(),s[i].y=read();
    for(int i=1;i<=maxx;++i)f[0][i]=INF;
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=maxx;j>=0;j--)
        {
            f[i][j]=f[i-1][j];
            if(j>=v(i))f[i][j]=min(f[i-1][j-v(i)]+w(i),f[i][j]);
        }
    for(int i=1;i<=n;++i)
        for(int j=maxx-1;j>=0;--j)f[i][j]=min(f[i][j+1],f[i][j]);
    for(int i=1;i<=m;++i)
    {
        int xx=find(s[i].x);
        //put(xx);
        put(carculate(xx,s[i].y));
    }
    return 0;
}
View Code

值得一提的是01二维01背包 注意不要少一句话承接上一层的值 我wa了半天因为这个。时间复杂度为 n*价值和+mlogn+mlog价值和+nlogn

一维的01背包 时间复杂度 n*价值和+nlogn+mlogm+mlogm+mlogm上下比较上面更为优秀 少了两次排序对了一次二分查找。

代码中的细节还算是比较ex的 调了一会才发现又犯sb错误。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 21474836460000ll
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=100002,maxn=302;
int n,m,maxx,flag=1;
struct wy
{
    int w,v,ti;
    int friend operator <(wy x,wy y){return x.ti<y.ti;}
}t[maxn];
struct wy1
{
    int x,y;//x为时间y为金钱
    int id;
    int ans;
}s[MAXN];
int cmp(wy1 a,wy1 b){return a.x<b.x;}
int cmp1(wy1 a,wy1 b){return a.id<b.id;}
ll f[maxn*maxn];//f[i][j]表示前i个物品 价值为j的最小花费 
inline int carculate(int x)
{
    int l=0,r=maxx;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(f[mid]<=x)l=mid;
        else r=mid;
    }
    if(f[r]<=x)return r;
    return l;
}
int main()
{
    //freopen("1.in","r",stdin);
    freopen("market.in","r",stdin);
    freopen("market.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;++i)
    {
        w(i)=read();
        v(i)=read();
        ti(i)=read();
        maxx+=v(i);
    }
    //put(maxx);
    sort(t+1,t+1+n);
    for(int i=1;i<=m;++i)s[i].x=read(),s[i].y=read(),s[i].id=i;
    sort(s+1,s+1+m,cmp);
    for(int i=1;i<=maxx;++i)f[i]=INF;
    for(int i=1;i<=n;++i)
    {
        if(s[flag].x<ti(i)&&flag<=m)
            for(int j=maxx-1;j>=0;--j)f[j]=min(f[j],f[j+1]);
        while(s[flag].x<ti(i)&&flag<=m)s[flag].ans=carculate(s[flag].y),++flag;
        for(int j=maxx;j>=v(i);--j)f[j]=min(f[j],f[j-v(i)]+w(i));
    }
    if(flag<=m)for(int j=maxx-1;j>=0;--j)f[j]=min(f[j],f[j+1]);
    for(int i=flag;i<=m;++i)s[i].ans=carculate(s[i].y);
    sort(s+1,s+1+m,cmp1);
    for(int i=1;i<=m;++i)put(s[i].ans);
    return 0;
}
View Code

这道题 我感觉是上面的题目中最难的题目原因是 感觉无论怎么选后效性极强。于是对着40分写了一个爆搜。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 2147483646
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=20;
int n,ans;
int vis[MAXN],v[MAXN],w[MAXN];
int q[MAXN],t;
void dfs(int depth)
{
    if(depth<=n&&depth)
    {
        int cnt=0,sum=0;
        for(int i=1;i<=depth;++i)
        {
            sum+=v[q[i]]-cnt;
            cnt+=w[q[i]];
        }
        ans=max(ans,sum);
        if(depth==n)return;
    }
    for(int i=1;i<=n;++i)
    {
        if(vis[i]==0)
        {
            q[depth+1]=i;
            vis[i]=1;
            dfs(depth+1);
            vis[i]=0;
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    freopen("value.in","r",stdin);
    freopen("value.out","w",stdout);
    n=read();
    if(n<=10)
    {
        for(int i=1;i<=n;++i)v[i]=read(),w[i]=read();
        dfs(0);
    }
    put(ans);
    return 0;
}
View Code

显然爆搜不是搜全排列连子排列都要搜出来。然后计算答案即可。复杂度 8!其实子排列可以创造出全排列所以 复杂度认为8!

那好我要开车了,针对这道题老师也是点了一下思路接下来是我今天的分析这道题怎么写。

1 首先我们要选的话一定选wi较小的,因为我们如果还想选下一个的话 首先对于这两个物品 其最小的wi必然是被先选的 然后剩下的那个价值量会减小很少的 如果选wi较大的那么价值就会减小很大。反而不优了。需要按wi排一下序。

2 首先我们f[i][j]表示前i件物品选择j件物品的最大价值那么此时价值会灰常不好算因为我们不知道后面还要选多少个物品。

3 那么显然 f[i][j]表示前i件物品中选择后j个物品的最大价值。这样来进行就可以了。

注意排序是必然的因为 wi较小的选择是必然的 而且观察我们这个状态 选择了后j个物品 显然这个选择了后j个物品我们进行状态转移时如果不倒序就无法进行有效的状态转移,因为你的状态为空。当然记忆化搜索也行 反正都是拓扑序。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 2147483646
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=5002;
int n,maxx;
int f[MAXN][MAXN];//f[i][j] 表示 对于前i个物品 后面选择j个物品的最大价值 
struct wy
{
    int v,w;
    int friend operator <(wy x,wy y)
    {
        return x.w>y.w;
    }
}t[MAXN];
int main()
{
    freopen("1.in","r",stdin);
    //freopen("value.in","r",stdin);
    //freopen("value.out","w",stdout);
    n=read();
    for(int i=1;i<=n;++i)v(i)=read(),w(i)=read();
    sort(t+1,t+1+n);
    //for(int i=1;i<=n;++i)cout<<w(i)<<endl;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=i;++j)
        {
            f[i][j]=max(f[i-1][j],f[i-1][j-1]+v(i)-w(i)*(j-1));
            maxx=max(maxx,f[i][j]);
        }
    }
    put(maxx);
    return 0;
}
View Code

对于这道题 其实最暴力也最简单的是最短路问题 由于是树也就最短路只有一条直接dfs即可 然后也变成了一个树形换根dp

所幸这道题比较简单我仔细思考之后是独立的写出来了 这种换根还是比较简单的。

考虑到我们已经求出的东西能给我们带来什么效果。

设出f[i][0]表示以其为根的子树内到此点的距离之和 d[i]表示以i为根的子树的人数 f[i][1]表示以i为中心的最小不方便值。

对于我们第一次dfs 求出 f[i][0] & d[i] 此时我以1为根 那么显然 f[1][0]=f[1][1]; 

然后通过f[1][1] 进行换根dp 状态转移很好推 f[tn][1]=f[tn][0]+f[x][1]-f[tn][0]-d[tn]*e[i]+(cnt-d[tn])*e[i];

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    ll num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const ll MAXN=200002;
ll n,len;
ll cnt,ans=100000000000000000ll;
ll f[MAXN][2];//f[i][0]表示以i为根节点且不为集会地点时的方便程度
ll d[MAXN],a[MAXN];//d[i]表示以1为根节点时的牛数
ll lin[MAXN<<1],nex[MAXN<<1],ver[MAXN<<1];
ll e[MAXN<<1];
inline ll min(ll x,ll y){return x>y?y:x;}
inline void add(ll x,ll y,ll z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void dp(ll x,ll father)
{
    d[x]=a[x];
    for(ll i=lin[x];i;i=nex[i])
    {
        ll tn=ver[i];
        if(tn==father)continue;
        dp(tn,x);
        f[x][0]+=d[tn]*e[i]+f[tn][0];
        d[x]+=d[tn];
    }
    return;
}
void dfs(ll x,ll father)
{
    for(ll i=lin[x];i;i=nex[i])
    {
        ll tn=ver[i];
        if(tn==father)continue;
        f[tn][1]=f[tn][0]+(cnt-d[tn])*e[i]+f[x][1]-d[tn]*e[i]-f[tn][0];
        dfs(tn,x);
    }
    return;
}
int main()
{
    //freopen("1.in","r",stdin);
    freopen("gather.in","r",stdin);
    freopen("gather.out","w",stdout);
    n=read();
    for(ll i=1;i<=n;++i)a[i]=read(),cnt+=a[i];
    for(ll i=1;i<n;++i)
    {
        ll x,y,z;
        x=read();y=read();z=read();
        add(x,y,z);add(y,x,z);
    }
    dp(1,0);
    f[1][1]=f[1][0];
    dfs(1,0);
    for(ll i=1;i<=n;++i)ans=min(ans,f[i][1]);
    //for(ll i=1;i<=n;++i)put(f[i][1]);
    put(ans);
    return 0;
}
View Code

这是压轴的dp 了我感觉一般吧 还算是比较基础的决策优化 但是这个优化和平常的不太一样 ,我感觉是比较难受的优化。

对于40分考虑一个 n^2的做法 设 f[MAXN];//f[i]表示前i个原子分成若干段的最小代价 

显然状态转移f[i]=min(f[i],f[j]+cost[j+1][i]); 当且仅当 j+1~i 不起任何冲突。这个不起冲突我是一个区间dp 来写的麻烦了O(n)即可。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 2147483646
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=1002;
int n;
int a[MAXN],b[MAXN];
int w[MAXN][MAXN],cost[MAXN][MAXN];
int f[MAXN];//f[i]表示前i个原子分成若干段的最小代价
int main()
{
    //freopen("1.in","r",stdin);
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    n=read();
    memset(f,0x3f3f3f3f,sizeof(f));
    for(int i=1;i<=n;++i)
    {
        a[i]=read();b[i]=read();
        w[i][i]=0;cost[i][i]=b[i];
    }
    for(int len=2;len<=n;++len)
    {
        for(int i=1;i<=n-len+1;++i)
        {
            int j=i+len-1;
            w[i][j]|=w[i][j-1];
            w[i][j]|=w[i+1][j];
            w[i][j]|=(a[i]==a[j]?1:0);
            cost[i][j]=max(cost[i][j-1],b[j]);
        }
    }
    /*for(int i=1;i<=n;++i)
        for(int j=i;j<=n;++j)
            cout<<i<<' '<<j<<' '<<w[i][j]<<endl;*/
    f[0]=0;
    for(int i=1;i<=n;++i)
        for(int j=0;j<i;++j)
        {
            if(!w[j+1][i])f[i]=min(f[i],f[j]+cost[j+1][i]);
        }
    put(f[n]);
    return 0;
}
View Code

考虑100分的做法 刚刚那个不起冲突可以O(n) 但是总dp式其实就是一个n^2的 优化其实是 观察决策具有单调性。

真的是有单调性啊 尽管不太明显 但是 我仍是为出题人感动 这都有单调性真是为了防AK的题目没有2个小时拿不下来。。(对我来说是这样的

大雾! 单调性在 很隐秘的地方 首先 对于 f数组 f[j]<=f[i] j<=i f数组单调不下降 对于cost数组呢貌似状似形似 也是一个单调的 是一个单调不上升的 因为如果有一个更大的cost 那么这整个区间都将被更新成这个cost值。一个单调不下降一个单调不上升很烦。这里我们把f[j]可以抽象成一个点 而cost 抽象成一个区间 显然对于cost形成的每一个区间我们选择那个区间最左边的决策时最优秀的。我这样说就把问题规划的很清楚了 我好棒!

其实对于每一个i 最左边的端点 确定然后就是i-最左边端点之中寻找一个 决策使其最优当然对于cost划分的不同的区域我们尽量选择靠近左边的端点这样有了单调性 考虑用什么来维护这个东西 单调队列?貌似不行因为这样的话不只是队首造成贡献了 这个队伍很可能整个都会造成贡献 线段树?

貌似不太行 因为f[j]不会变 cost 会变随着i的增加 这个区域的本质会改变我们修改cost的区间 经过和同学的一番讨论 我 更加深刻的理解了这道题的优化过程 首先是 单调队列 q 维护cost 单调不下降的的区间的左端点 其实就是在众多的不合法区间之内寻找到一个最优的决策。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define INF 2147483646
#define v(x) t[x].v
#define w(x) t[x].w
#define ti(x) t[x].ti
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=100002;
int n,flag;
int a[MAXN],b[MAXN],last[MAXN];
int q[MAXN],l,r;
int f[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    n=read();
    for(int i=1;i<=n;++i)a[i]=read(),b[i]=read(),f[i]=INF;
    q[++r]=0;++l;
    for(int i=1;i<=n;++i)
    {
        if(last[a[i]])flag=max(flag,last[a[i]]);
        last[a[i]]=i;
        while(l<=r&&q[l]<=flag)++l;
        while(l<=r&&b[q[r]]<=b[i])--r;
        q[++r]=i;
        for(int j=l+1;j<=r;++j)f[i]=min(f[i],f[q[j-1]]+b[q[j]]);
        f[i]=min(f[i],f[flag]+b[q[l]]);
    }
    put(f[n]);
    return 0;
}
View Code

这个直接在单调队列中寻找决策了 貌似是可以直接加进set里 logn寻找最优决策。

利用find的话直观性很差 但是 可以照着上面的板子打就不容易出现错误了 这里我采用的是迭代器的做法。

其实就很显然了加上一个set 即可 。线段树的我觉得不太行写不了。

set也比较ex的细节 我调了好久而且没有上面好理解 不推荐。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<cstdlib>
#define ll long long
#define db double
#define min(x,y) (x>y?y:x)
#define max(x,y) (x>y?x:y)
#define INF 2147483646
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[70];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=500002;
int n,flag;
int a[MAXN],b[MAXN],last[MAXN];
int q[MAXN],l,r;
int f[MAXN],w[MAXN],sum[MAXN];
multiset<int> s;
//multiset<int> :: iterator id[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    n=read();++l;
    for(int i=1;i<=n;++i)a[i]=read(),b[i]=read();
    for(int i=1;i<=n;++i)
    {
        if(last[a[i]])flag=max(flag,last[a[i]]);
        last[a[i]]=i;
        int k=i-1;
        while(l<r&&w[l+1]<=flag)s.erase(s.find(sum[l])),++l;
        while(l<=r&&b[q[r]]<=b[i])s.erase(s.find(sum[r])),k=w[r],--r;
        q[++r]=i;w[r]=k;
        if(l!=r)s.insert(f[w[r]]+b[q[r]]),sum[r]=f[w[r]]+b[q[r]],s.erase(s.find(sum[l]));
        s.insert(f[flag]+b[q[l]]);sum[l]=f[flag]+b[q[l]];
        f[i]=*s.begin();
    }
    put(f[n]);
    return 0;
}
View Code

值得一提的是set的find如果找不到值得话就会返回end 然后删除end 不会RE但是如果最优值冲突后且删除了最优值此时答案就错了。

所以要开multiset 敲黑板正解了 然后 迭代器一直被卡四组数据不知道为什么可能是因为多删除了 还是find好。

那么这几dp 题目总结到这里 关键是观察决策。

老师说 要对模型有深刻的理解 有的时候往往想不出来的问题都是没有把问题转换好 所以才 不会做。

这几道题都是对模型的升华 也算是比较有意义和价值的。

正在做splay搜到了这道题目,以为是splay 半天想不出来怎么写。毒瘤当我准备看题解之时想了想还是dp吧。

设f[i]表示前i本书放入若干个书架的最小花费。咦 f[i]=min(f[i],f[j]+v(j+1~i)) 这和上面 的近乎一模一样啊。

考虑优化我再也不写set优化了,根本搞不出来这玩意。直接单调队列了 发现被卡一个点。吸氧后过了。

// luogu-judger-enable-o2
#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 1000000000
#define ll long long
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
#define v(x) t[x].v
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const ll MAXN=100010;
ll n,m,maxx,cnt,flag;
ll a[MAXN],b[MAXN];
ll f[MAXN];//f[i]表示前i本书放入若干个书架的最小花费。
ll q[MAXN],l,r;
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(ll i=1;i<=n;++i)b[i]=read(),a[i]=read();
    memset(f,0x3f3f3f3f,sizeof(f));
    f[0]=0;flag=1;q[++r]=0;++l;
    for(ll i=1;i<=n;++i)
    {
        cnt+=a[i];
        while(cnt>m)++flag,cnt-=a[flag-1];
        while(l<=r&&q[l]<flag)++l;
        while(l<=r&&b[q[r]]<=b[i])--r;
        q[++r]=i;
        for(ll j=l;j<r;++j)f[i]=min(f[i],f[q[j]]+b[q[j+1]]);
        f[i]=min(f[i],f[flag-1]+b[q[l]]);
    }
    put(f[n]);
    return 0;
}
View Code

看题解发现是可以线段树优化的那么我线段树优化好了。调了2h 才发现这种类型的线段树并不是随便优化的。有讲究。

考虑我们线段树中每个点都记录一个最小f值和g值 每次一个新的i过来以后我们直接区间修改因为这是dp的转移。

然后区间查询最小值即可。注意这里的区间修改是维护两个东西H h 一个取max 一个取min 如果当前H<=x直接懒标记了。h>=x直接跳过。

至于最大值的维护我们考虑取最小g 然后加上 当前区间内的一个h 不能乱加也不能那样加总之我写了一个85分代码 发现这点自己维护错了,瞬间比较难受。我们不能那样搞就是 如下维护 g数组不取min 先维护f数组这样贪心我想是错的。

#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 10000000000000ll
#define ll long long
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
#define s(x) t[x].s
#define h(x) t[x].h
#define tag(x) t[x].tag
#define g(x) t[x].g
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=100010;
int n,m,flag;
int a[MAXN],b[MAXN];
ll f[MAXN],maxx,cnt;//f[i]表示前i本书放入若干个书架的最小花费。
inline ll min1(ll x,ll y){return x>y?y:x;}
inline ll max1(ll x,ll y){return x>y?x:y;}
struct wy
{
    int l,r;
    ll sum,g;
    ll h,s;
    int tag;
}t[MAXN<<2];
inline void build(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    return;
}
inline void pushdown(int p)
{
    tag(p<<1)=tag(p<<1|1)=tag(p);
    s(p<<1)=s(p<<1|1)=h(p<<1)=h(p<<1|1)=tag(p);
    sum(p<<1)=g(p<<1)+tag(p);
    sum(p<<1|1)=g(p<<1|1)+tag(p);
    tag(p)=0;return;
}
inline void change(int p,int l,int r,int x)
{
    if(h(p)>=x)return;
    if(l<=l(p)&&r>=r(p))
        if(s(p)<=x)
        {
            tag(p)=x;s(p)=h(p)=x;
            sum(p)=h(p)+g(p);
            return;
        }
    if(tag(p))pushdown(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid)change(p<<1,l,r,x);
    if(r>mid)change(p<<1|1,l,r,x);
    s(p)=max(s(p<<1),s(p<<1|1));
    h(p)=min(h(p<<1),h(p<<1|1));
    if(sum(p<<1)<=sum(p<<1|1))sum(p)=sum(p<<1),g(p)=g(p<<1);
    else sum(p)=sum(p<<1|1),g(p)=g(p<<1|1);return;
}
inline ll ask(int p,int l,int r)
{
    if(l<=l(p)&&r>=r(p))return sum(p);
    int mid=(l(p)+r(p))>>1;
    ll cnt=INF;
    if(tag(p))pushdown(p);
    if(l<=mid)cnt=min1(cnt,ask(p<<1,l,r));
    if(r>mid)cnt=min1(cnt,ask(p<<1|1,l,r));
    s(p)=max(s(p<<1),s(p<<1|1));
    h(p)=min(h(p<<1),h(p<<1|1));
    if(sum(p<<1)<=sum(p<<1|1))sum(p)=sum(p<<1),g(p)=g(p<<1);
    else sum(p)=sum(p<<1|1),g(p)=g(p<<1|1);
    return cnt;
}
inline void insert(int p,int x)
{
    if(l(p)==r(p)){g(p)=f[x];sum(p)=h(p)+g(p);return;}
    int mid=(l(p)+r(p))>>1;
    if(tag(p))pushdown(p);
    if(x<=mid)insert(p<<1,x);
    else insert(p<<1|1,x);
    s(p)=max(s(p<<1),s(p<<1|1));
    h(p)=min(h(p<<1),h(p<<1|1));
    //if(x==1)cout<<sum(p<<1)<<' '<<sum(p<<1|1)<<endl;
    if(sum(p<<1)<=sum(p<<1|1))sum(p)=sum(p<<1),g(p)=g(p<<1);//注意等号
    else sum(p)=sum(p<<1|1),g(p)=g(p<<1|1);return;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)b[i]=read(),a[i]=read();
    build(1,0,n);f[0]=0;flag=1;
    for(int i=1;i<=n;++i)
    {
        cnt+=a[i];
        while(cnt>m)++flag,cnt-=a[flag-1];
        change(1,flag-1,i-1,b[i]);
        f[i]=ask(1,flag-1,i-1);
        //cout<<f[i]<<' ';
        if(i!=n)insert(1,i);
    }
    put(f[n]);
    return 0;
}
View Code

然后应先维护g数组加上一个h来更新f数组即可。

#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 10000000000000ll
#define ll long long
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
#define s(x) t[x].s
#define h(x) t[x].h
#define tag(x) t[x].tag
#define g(x) t[x].g
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=100010;
int n,m,flag;
int a[MAXN],b[MAXN];
ll f[MAXN],maxx,cnt;//f[i]表示前i本书放入若干个书架的最小花费。
inline ll min1(ll x,ll y){return x>y?y:x;}
inline ll max1(ll x,ll y){return x>y?x:y;}
struct wy
{
    int l,r;
    ll sum,g;
    ll h,s;
    int tag;
}t[MAXN<<2];
inline void build(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    return;
}
inline void pushdown(int p)
{
    tag(p<<1)=tag(p<<1|1)=tag(p);
    s(p<<1)=s(p<<1|1)=h(p<<1)=h(p<<1|1)=tag(p);
    sum(p<<1)=g(p<<1)+tag(p);
    sum(p<<1|1)=g(p<<1|1)+tag(p);
    tag(p)=0;return;
}
inline void change(int p,int l,int r,int x)
{
    if(h(p)>=x)return;
    if(l<=l(p)&&r>=r(p))
        if(s(p)<=x)
        {
            tag(p)=x;s(p)=h(p)=x;
            sum(p)=h(p)+g(p);
            return;
        }
    if(tag(p))pushdown(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid)change(p<<1,l,r,x);
    if(r>mid)change(p<<1|1,l,r,x);
    s(p)=max(s(p<<1),s(p<<1|1));
    h(p)=min(h(p<<1),h(p<<1|1));
    g(p)=min(g(p<<1),g(p<<1|1));
    sum(p)=min(sum(p<<1),sum(p<<1|1));
}
inline ll ask(int p,int l,int r)
{
    if(l<=l(p)&&r>=r(p))return sum(p);
    int mid=(l(p)+r(p))>>1;
    ll cnt=INF;
    if(tag(p))pushdown(p);
    if(l<=mid)cnt=min1(cnt,ask(p<<1,l,r));
    if(r>mid)cnt=min1(cnt,ask(p<<1|1,l,r));
    s(p)=max(s(p<<1),s(p<<1|1));
    h(p)=min(h(p<<1),h(p<<1|1));
    sum(p)=min(sum(p<<1),sum(p<<1|1));
    g(p)=min(g(p<<1),g(p<<1|1));
    return cnt;
}
inline void insert(int p,int x)
{
    if(l(p)==r(p)){g(p)=f[x];return;}
    int mid=(l(p)+r(p))>>1;
    if(tag(p))pushdown(p);
    if(x<=mid)insert(p<<1,x);
    else insert(p<<1|1,x);
    g(p)=min(g(p<<1),g(p<<1|1));
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)b[i]=read(),a[i]=read();
    build(1,0,n);f[0]=0;flag=1;
    for(int i=1;i<=n;++i)
    {
        cnt+=a[i];
        while(cnt>m)++flag,cnt-=a[flag-1];
        change(1,flag-1,i-1,b[i]);
        f[i]=ask(1,flag-1,i-1);
        //cout<<f[i]<<' ';
        if(i!=n)insert(1,i);
    }
    put(f[n]);
    return 0;
}
View Code

复杂度的证明:对于一个值各不相同的区间第一次肯定是暴力覆盖 然后变成了一个单调递减的 这样的话下一次修改一部分是直接区间覆盖了,一部分是跳过了跳过原因就是上述原因,然后均摊复杂度nlogn.很完美的解决了这种类型的dp优化。

2019 11 15 update:我再次 看了一下上述做法 有点没有必要 复杂度 有点假说实话一个绝佳的做法是利用单调栈 来维护 然后直接区间 修改即可。

单调栈 所以复杂度 是nlogn的 最多弹栈n次每次修改logn。上述做法 好像跟单调栈一样的好像qwq 好像直接忽略了 单调栈的存在 这样也行吧 复杂度都是 nlogn的 但是单调栈好理解。

什么垃圾set 优化 付了..

挥羽扇,整纶巾,少年鞍马尘。如今憔悴赋招魂,儒冠多误身。

posted @ 2019-05-03 11:05  chdy  阅读(215)  评论(0编辑  收藏  举报