• 省选前的动态规划大杂烩


    这是一个不知道为什么反正就是想写成汇总的东西,整合一些DP题(其实是懒得一个个写题解)

    因为沙茶博主DP很菜(什么都很菜,只是DP尤其菜)又做题少,所以写了这样一个记录沙茶博主在省选退役前刷的DP题的东西

    一些零碎的知识点在知识总结里

    好像上一行两句没有什么因果关系

    好了沙茶博主把DP知识点也丢进这个东西里了,感觉这个可以拿来学(虽然除了沙茶自己没人看这玩意),因为沙茶博主写的时候基本那些东西都不扎实,跟重新学了一遍差不多


    前一阵的链接们:

    虚树DP:SDOI 2011 消耗战 HNOI 2014 世界树

    凸优化:八省联考2018 林克卡特树

    决策单调性:NOI 2009 诗人小G

    树形DP:CF1118F2 Tree Cutting 国家集训队 Crash的文明世界

    乱七八糟的东西们:九省联考2018 CoaT(写的暴力) WC 2018 州区划分(并没搞太懂)  

    ----------------------------------------------------------

    HAOI 2011 problem a

    转化

    注意每个人的分数区间不可相交,然后莫得了

     1 #include<map>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 const int N=100005,inf=1e9; 
     7 struct a
     8 {
     9     int ll,rr,val;
    10 }mem[N];
    11 int n,t1,t2,cnt,ans,va[N],maxi[4*N];
    12 map<pair<int,int>,int> mp;
    13 void Mini(int &x,int y)
    14 {
    15     if(x>y) x=y;
    16 }
    17 bool cmp(a x,a y)
    18 {
    19     return x.ll==y.ll?x.rr<y.rr:x.ll<y.ll;
    20 }
    21 void Count(int l,int r)
    22 {
    23     pair<int,int> pr=make_pair(l,r);
    24     if(!mp.count(pr)) mp[pr]=++cnt;
    25     int id=mp[pr]; va[id]++;
    26     mem[id]=(a){l,r,va[id]};
    27 }
    28 void Change(int nde,int l,int r,int pos,int tsk)
    29 {
    30     if(l==r)
    31         maxi[nde]=tsk; 
    32     else
    33     {
    34         int mid=(l+r)>>1,ls=2*nde,rs=2*nde+1;
    35         if(pos<=mid) Change(ls,l,mid,pos,tsk);
    36         else Change(rs,mid+1,r,pos,tsk);
    37         maxi[nde]=max(maxi[ls],maxi[rs]);
    38     }
    39 }
    40 int Query(int nde,int l,int r,int ll,int rr)
    41 {
    42     if(l>rr||r<ll)
    43         return -inf;
    44     else if(l>=ll&&r<=rr)
    45         return maxi[nde];
    46     else
    47     {
    48         int mid=(l+r)>>1,ls=2*nde,rs=2*nde+1;
    49         return max(Query(ls,l,mid,ll,rr),Query(rs,mid+1,r,ll,rr));
    50     }
    51 }
    52 int main()
    53 {
    54     scanf("%d",&n);
    55     for(int i=1;i<=n;i++)
    56     {
    57         scanf("%d%d",&t1,&t2);
    58         if(t2+1>n-t1) continue;
    59         Count(t2+1,n-t1);
    60     }
    61     for(int i=1;i<=cnt;i++)
    62         Mini(mem[i].val,mem[i].rr-mem[i].ll+1);
    63     sort(mem+1,mem+1+cnt,cmp);
    64 //    for(int i=1;i<=cnt;i++) printf("%d %d %d
    ",mem[i].ll,mem[i].rr,mem[i].val);
    65     for(int i=1;i<=cnt;i++)
    66     {
    67         int qry=Query(1,0,n,0,mem[i].ll-1);
    68         int newa=qry+mem[i].val;
    69         Change(1,0,n,mem[i].rr,newa),ans=max(ans,newa);
    70     }
    71     printf("%d ",n-ans);
    72     return 0;
    73 }
    View Code

    NOI 2015 寿司晚宴

    转化

    互质归根结底是没有相同质因数,而每个数只可能有一个大于$sqrt n$的质因数。搞出来每个数小于$sqrt n$的质因数的状态和(是否有)大于$sqrt n$的质因数,按后者从小到大排序,大于$sqrt n$的质因数相同的一块DP(没有的每个单独DP)。然后基本莫得了,注意容斥掉两个人都没选的情况

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int N=510,M=260,all=255;
     6 const int pri[8]={2,3,5,7,11,13,17,19};
     7 long long n,mod,cnt,ans,dp[M][M],fir[M][M],sec[M][M];
     8 struct a
     9 {
    10     int sta,hug;
    11 }num[N];
    12 bool cmp(a x,a y)
    13 {
    14     return x.hug==y.hug?x.sta<y.sta:x.hug<y.hug;
    15 }
    16 void Add(long long &x,long long y)
    17 {
    18     x+=y;
    19     if(x>=mod) x-=mod;
    20 }
    21 void Pre()
    22 {
    23     for(int i=2;i<=n;i++)
    24     {
    25         int tmp=i;
    26         for(int j=0;j<=7;j++)
    27             if(tmp%pri[j]==0)
    28             {
    29                 num[i].sta|=1<<j;
    30                 while(tmp%pri[j]==0) tmp/=pri[j];
    31             }
    32         num[i].hug=tmp;
    33     }
    34     sort(num+2,num+1+n,cmp),dp[0][0]=1;
    35 }
    36 int main()
    37 {
    38     scanf("%lld%lld",&n,&mod),Pre();
    39     for(int i=2;i<=n;i++)
    40     {
    41         if(i==2||num[i].hug==1||num[i].hug!=num[i-1].hug)
    42         {
    43             for(int j=0;j<=all;j++)
    44                 for(int k=0;k<=all;k++)
    45                     fir[j][k]=sec[j][k]=dp[j][k];
    46         }
    47         for(int j=all;~j;j--)
    48             for(int k=all;~k;k--)
    49                 if(!(j&k))
    50                 {
    51                     if(!(k&num[i].sta)) Add(fir[j|num[i].sta][k],fir[j][k]);
    52                     if(!(j&num[i].sta)) Add(sec[j][k|num[i].sta],sec[j][k]);
    53                 }
    54         if(i==n||num[i].hug==1||num[i].hug!=num[i+1].hug)
    55         {
    56             for(int j=all;~j;j--)
    57                 for(int k=all;~k;k--)
    58                     if(!(j&k)) dp[j][k]=(fir[j][k]+sec[j][k]-dp[j][k]+mod)%mod;
    59         }
    60     }
    61     for(int i=0;i<=all;i++)
    62         for(int j=0;j<=all;j++)
    63             if(!(i&j)) Add(ans,dp[i][j]);
    64     printf("%lld",ans);
    65     return 0;
    66 }
    View Code

    SCOI 2014 方伯伯的玉米田

    分析

    直接做不可做,分析性质,发现每次一定是拔一个后缀。因为后面不比它矮的拔了不会使答案变劣,后面比它矮的拔了还是没贡献,最后还拔走就行。

    于是可以设计一个DP:$dp[i][j]$表示到$i$为止(i被)拔了$j$次的最长不下降子序列,转移为:

    $dp[i][j]=max(dp[i][j],dp[k][h]+1)(a[i]+j>a[k]+h&&j>h)$

    树状数组维护二维(前缀)最大值

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int N=10005,M=505;
     6 int n,k,mx,ans,a[N],bit[N][M];
     7 int Query(int x,int y)
     8 {
     9     int ret=0;
    10     for(int i=x;i;i-=i&-i)
    11         for(int j=y;j;j-=j&-j)
    12             ret=max(ret,bit[i][j]);
    13     return ret;
    14 }
    15 void Change(int x,int y,int v)
    16 {
    17     for(int i=x;i<=mx+k;i+=i&-i)
    18         for(int j=y;j<=k;j+=j&-j)
    19             bit[i][j]=max(bit[i][j],v);
    20 }
    21 int main()
    22 {
    23     scanf("%d%d",&n,&k),k++;
    24     for(int i=1;i<=n;i++)
    25         scanf("%d",&a[i]),mx=max(mx,a[i]);
    26     for(int i=1;i<=n;i++)
    27         for(int j=k;j;j--)
    28         {
    29             int len=Query(a[i]+j,j)+1;
    30             ans=max(ans,len),Change(a[i]+j,j,len);
    31         }
    32     printf("%d ",ans);
    33     return 0;
    34 }
    View Code

    IOI 2005 河流

    对未来的承诺

    因为当前节点的决策影响了之后父亲的决策,一般设状态转移不了。所以在dp状态里先对未来承诺:设$dp[i][j][k]$表示以$i$为根的子树里放了$k$个伐木场,承诺$i$上面的第一个伐木场是$j$的最小代价,然后每次在当前节点到根的链上DP。

    不知为何转移时加上子树size的限制就错了

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int N=110,M=55;
     6 int n,m,t1,t2,cnt,top;
     7 int p[N],noww[N],goal[N],val[N];
     8 int wood[N],dep[N],stk[N],dp[N][N][M];
     9 void Mini(int &x,int y)
    10 {
    11     if(x>y) x=y;
    12 }
    13 void Link(int f,int t,int v)
    14 {
    15     noww[++cnt]=p[f],p[f]=cnt;
    16     goal[cnt]=t,val[cnt]=v;
    17 }
    18 void DFS(int nde)
    19 {
    20     stk[++top]=nde;
    21     for(int i=p[nde];i;i=noww[i])
    22     {
    23         int g=goal[i];
    24         dep[g]=dep[nde]+val[i],DFS(g);
    25         for(int j=1;j<=top;j++)
    26             for(int k=m;~k;k--)
    27             {
    28                 int anc=stk[j];
    29                 dp[nde][anc][k]+=dp[g][anc][0];
    30                 for(int h=1;h<=k;h++)
    31                     Mini(dp[nde][anc][k],dp[nde][anc][k-h]+dp[g][anc][h]);
    32             }    
    33     }
    34     top--;
    35     for(int i=1;i<=top;i++)
    36     {
    37         int anc=stk[i],cst=wood[nde]*(dep[nde]-dep[anc]);
    38         dp[nde][anc][0]+=cst;
    39         for(int j=1;j<=m;j++)
    40             dp[nde][anc][j]=min(dp[nde][anc][j]+cst,dp[nde][nde][j-1]);
    41     }
    42 }
    43 int main()
    44 {
    45     scanf("%d%d",&n,&m),n++;
    46     for(int i=2;i<=n;i++)
    47         scanf("%d%d%d",&wood[i],&t1,&t2),Link(t1+1,i,t2);
    48     DFS(1),printf("%d",dp[1][1][m]);
    49     return 0;
    50 }
    View Code

    HNOI 2007 梦幻岛宝珠

    明示你按$b$分组,每组内先做背包再合并起来。合并就是从低位向高位合并,同时注意m的这一位是否有值

     1 // luogu-judger-enable-o2
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 int n,m,t1,t2,len;
     7 long long dp[35][1100];
     8 int main()
     9 {
    10     while(scanf("%d%d",&n,&m)!=EOF)
    11     {
    12         if(n==-1) break;
    13         memset(dp,len=0,sizeof dp);
    14         for(int i=1;i<=n;i++)
    15         {
    16             scanf("%d%d",&t1,&t2); int pw=0;
    17             while(t1%2==0) t1/=2,pw++; 
    18             for(int j=1000;j>=t1;j--)    
    19                 dp[pw][j]=max(dp[pw][j],dp[pw][j-t1]+t2);
    20         }
    21         int tmp=m;
    22         while(tmp) tmp>>=1,len++; len--;
    23         for(int i=1;i<=len;i++)
    24             for(int j=1000;~j;j--)
    25                 for(int k=0;k<=j;k++)
    26                 {
    27                     int lst=min(1000,(k<<1)+((m>>(i-1))&1));
    28                     dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[i-1][lst]);
    29                 }
    30         printf("%lld
    ",dp[len][1]);
    31     }
    32     return 0;
    33 }
    View Code

    洛谷 P5241 序列

    这题好的

    我直接转洛谷题解

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int N=410,mod=1e9+7;
     6 int n,m,nm,lim[N];
     7 int f1[2][N][N],s1[N][N];
     8 int f2[2][N],s2[N],ans[N*N];
     9 int Add(int x,int y)
    10 {
    11     x+=y;
    12     if(x>=mod) x-=mod;
    13     return x;
    14 }
    15 void Write(int x)
    16 {
    17     if(x>9) Write(x/10);
    18     putchar(x%10|48);
    19 }
    20 int main()
    21 {
    22     register int i,j,k;
    23     scanf("%d",&n);
    24     for(i=1;i<=n;i++)
    25         lim[i]=(n-i+1)*(n-1)+(i-1)*(i-2)/2;
    26     auto p1=f1[0],p2=f1[1]; 
    27     m=n*min(2,n-1),nm=n*(n-1),ans[1]=p2[n][1]=1;
    28     for(i=1;i<=n;i++) s1[n][1]=1;
    29     for(i=2;i<=m;i++)
    30     {
    31         for(j=1;j<=n;j++)
    32             if(i<=lim[j])
    33                 for(k=1;k<=n;k++)
    34                     if(i-k+1>=n-j)
    35                         p1[j][k]=Add(p2[j][k],s1[j+1][k-1]);
    36         for(j=n;j;j--)
    37             for(k=1;k<=n;k++)
    38             {
    39                 s1[j][k]=Add(s1[j+1][k],p1[j][k]);
    40                 ans[i]=Add(ans[i],p1[j][k]),p2[j][k]=0;
    41             }
    42         swap(p1,p2);
    43     }
    44     for(i=1;i<=n;i++)
    45         for(j=1;j<=n;j++)
    46             f2[1][i]=Add(f2[1][i],p2[i][j]),s2[i]=Add(s2[i],s1[i][j]);
    47     auto q1=f2[0],q2=f2[1];
    48     for(i=m+1;i<=nm;i++)
    49     {
    50         for(j=1;j<=n;j++)
    51             if(i<=lim[j])
    52                 q1[j]=Add(q2[j],s2[j+1]);
    53         for(j=n;j;j--)
    54         {
    55             s2[j]=Add(s2[j+1],q1[j]);
    56             ans[i]=Add(ans[i],q1[j]),q2[j]=0;
    57         }
    58         swap(q1,q2);
    59     }
    60     for(i=1;i<=nm;i++) Write(ans[i]),putchar(' ');
    61     return 0;
    62 }
    View Code

    洛谷 P4890 Never·island

    学了一个处理这种序列上线段乱搞问题的套路

    把每个线段抽象成一个点,线段本身分成左右端点讨论建图,最后把问题转成一个图上问题

    这里把线段的左右端点从大到小排序后讨论每个点$x$和它的下一个点$y$:

    如果$x$是左端点,$y$是右端点:如果$x,y$就是一条线段直接把点权加上长度;否则用权值为长度的边连接$x->y$所代表的线段,表示他们中间这段如果给$y$队钥匙是可以省掉的

    如果两个都是左/右端点就把左/右边那个的点权加上长度

    如果$x$是右端点,$y$是左端点,那么说明这中间是断开的,直接贡献进去

    最后我们得到了一坨链,就可以依次DP这些链了。设$dp[i][j][2]$表示决策到第$i$个队伍给出去$j$把钥匙,当前队伍给没给钥匙,$O(n^2)$DP即可

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int N=4005;
     6 struct a
     7 {
     8     int typ,pos,idx;
     9 }pts[N];
    10 bool cmp(a x,a y)
    11 {
    12     return x.pos<y.pos;
    13 }
    14 int n,m,t1,t2,cnt,tot,mem;
    15 int per[N],vis[N],dp[2][N][N];
    16 int pre[N],nxt[N],val[N],len[N],deg[N];
    17 int main()
    18 {
    19     scanf("%d%d",&n,&m);
    20     for(int i=1;i<=n;i++)
    21     {
    22         scanf("%d%d",&t1,&t2);
    23         pts[++cnt]=(a){0,t1,i};
    24         pts[++cnt]=(a){1,t2,i};
    25     }
    26     sort(pts+1,pts+1+cnt,cmp);
    27     for(int i=1;i<cnt;i++)
    28     {
    29         a a1=pts[i],a2=pts[i+1];
    30         int lth=a2.pos-a1.pos,x=a1.idx,y=a2.idx;
    31         int xx=a1.typ,yy=a2.typ;
    32         if(!xx&&yy) 
    33         {
    34             if(x==y) mem+=lth;
    35             else pre[y]=x,nxt[x]=y,len[x]=lth,deg[y]++;
    36         }
    37         else if(!xx&&!yy) val[x]+=lth;
    38         else if(xx&&yy) val[y]+=lth;
    39         else mem+=lth;
    40     }
    41     for(int i=1;i<=cnt;i++)
    42         if(!vis[pts[i].idx]&&!deg[pts[i].idx])
    43         {
    44             int nde=pts[i].idx;
    45             per[++tot]=nde,vis[nde]=true;
    46             while(nxt[nde]) nde=nxt[nde],per[++tot]=nde,vis[nde]=true;
    47         }
    48     auto p1=dp[0],p2=dp[1];
    49     for(int i=1;i<=n;i++)
    50     {
    51         int nde=per[i];
    52         for(int j=1;j<=m;j++)
    53         {
    54             p1[i][j]=max(p1[i-1][j],p2[i-1][j]);
    55             if(!pre[nde]) p2[i][j]=max(p1[i-1][j-1],p2[i-1][j-1])+val[nde];
    56             else p2[i][j]=max(p1[i-1][j-1],p2[i-1][j-1]+len[pre[nde]])+val[nde];
    57         }
    58     }
    59     printf("%d",pts[cnt].pos-pts[1].pos-max(p1[n][m],p2[n][m])-mem);
    60     return 0;
    61 }
    View Code

    洛谷 P2300 合并神犇

    我太菜了,被高一学长吊打了

    (按着一个错误的沙雕思路WA了一大串

    设$dp[i]$表示合并到i为止的最少合并次数,转移是$dp[i]=dp[j]+i-j-1$,其中$j$是最靠后的满足$sum[i]-sum[j]>=lst[j]$的位置,$lst[j]$表示到$j$为止的最后一个数

    显然移项之后就要找满足$sum[i]>=sum[j]+lst[j]$的最大的j,然后我不知道怎么想的认为这个东西可以set搞(显然tm是假的,就开始摁着改。

    都省选了怎么还有这么制杖的人?

    事实上权值线段树即可,注意开long long

     1 #include<set>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define lli long long
     6 using namespace std;
     7 const int N=200005;
     8 int n,tot,root;
     9 lli sum[N],dp[N],lst[N];
    10 int val[48*N],son[48*N][2]; 
    11 int Query(int &nde,lli l,lli r,lli ll,lli rr)
    12 {
    13     if(l>=ll&&r<=rr)
    14         return val[nde];
    15     else 
    16     {
    17         lli mid=(l+r)>>1;int ret=0;
    18         if(mid>=ll) ret=max(ret,Query(son[nde][0],l,mid,ll,rr));
    19         if(mid<rr) ret=max(ret,Query(son[nde][1],mid+1,r,ll,rr)); 
    20         val[nde]=max(val[son[nde][0]],val[son[nde][1]]); return ret;
    21     }
    22 }
    23 void Insert(int &nde,lli l,lli r,lli pos,int tsk)
    24 {
    25     if(!nde) nde=++tot;
    26     if(l==r) 
    27         val[nde]=max(val[nde],tsk);
    28     else
    29     {
    30         lli mid=(l+r)>>1;
    31         if(pos<=mid) Insert(son[nde][0],l,mid,pos,tsk);
    32         else Insert(son[nde][1],mid+1,r,pos,tsk); 
    33         val[nde]=max(val[son[nde][0]],val[son[nde][1]]);
    34     }
    35 }
    36 int main()
    37 {
    38     scanf("%d",&n);
    39     for(int i=1;i<=n;i++)
    40         scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
    41     Insert(root,0,sum[n],0,0);
    42     for(int i=1;i<=n;i++)
    43     {
    44         int pos=Query(root,0,sum[n],0,sum[i]);
    45         dp[i]=dp[pos]+i-pos-1,lst[i]=sum[i]-sum[pos];
    46         if(lst[i]+sum[i]<=sum[n]) Insert(root,0,sum[n],lst[i]+sum[i],i);
    47     }
    48 //    for(int i=1;i<=n;i++) printf("%d ",dp[i]);puts("");
    49 //    for(int i=1;i<=n;i++) printf("%d ",lst[i]);puts("");
    50     printf("%lld",dp[n]);
    51     return 0;
    52 }
    View Code

    各种DP及他们的优化

    DP优化的本质要么是删状态要么是改转移(废话

    感觉DP这个东西更重理解

    1.线性DP(基本看的Flashhu的博客)

    就是当前的DP值由前面一个DP值加上个代价转移过来,这种DP我统称为线性DP

    优化们

    ①做前缀和

    这好像是通用的......例题的话 逆序对数列

    ②单调队列

    1.转移是一块区间,而且区间单调地移动

    2.当前不优的转移点将来一定不会更优

    所以维护一个合法转移点的区间,每次从队头踢掉不合法的点,用队头更新,然后从队尾加入当前点,顺便把所有不优的都踢掉

    例题有什么 瑰丽华尔兹 和 股票交易 之类的

    这东西可以用来优化多重背包,对每个物品维护同余的单调队列,比二进制压缩还快那么一个log,然并卵

    ③数据结构优化

    有个经典的东西叫做线段树优化DP

    这种有时候会和单调线性结构结合

    比如说 USACO12OPEN Bookshelf

    现在感觉当时写麻烦了,可以看这个

    线段树不仅是可以优化复杂度,还有整合信息的作用

    比如说 CF833B The Bakery

    ④决策单调性优化

    顾名思义,DP的决策点单调移动

    有两种优化方法:单调线性结构(栈或队列)或者分治

    (其实本质上没有区别,看你写哪个)

    分治就记录当前求解区间和当前决策区间,每次暴力扫出来当前的最优决策点,往下递归就完了

    好像是雅礼集训的题 珠宝(体积很小的01背包)

     1 #include<cstdio>
     2 #include<vector>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define ll long long
     6 using namespace std;
     7 const int N=1000005,M=305;
     8 ll tr1[N],tr2[N],tmp[N],tep[N],sum[N];
     9 int n,m,t1,t2,sz,mx; vector<int> ve[M];
    10 bool cmp(ll a,ll b)
    11 {
    12     return a>b;
    13 }
    14 void Solve(ll *lst,ll *cur,int l,int r,int nl,int nr)
    15 {
    16     if(l>r) return;
    17     if(nl==nr)
    18         for(int i=l;i<=r;i++)    
    19             cur[i]=lst[nl]+sum[i-nl];
    20     else 
    21     {
    22         if(l==r)
    23         {
    24             cur[l]=0; int lp=max(nl,l-sz),rp=min(nr,l);
    25             for(int i=lp;i<=rp;i++)
    26                 cur[l]=max(cur[l],lst[i]+sum[l-i]);
    27         }
    28         else
    29         {
    30             int mid=(l+r)/2; cur[mid]=0;
    31             int pt=-1,lp=max(nl,mid-sz),rp=min(nr,mid);
    32             for(int i=lp;i<=rp;i++)
    33             {
    34                 ll val=lst[i]+sum[mid-i];
    35                 if(val>=cur[mid])
    36                     cur[mid]=val,pt=i;
    37             }
    38             Solve(lst,cur,l,mid-1,nl,pt);
    39             Solve(lst,cur,mid+1,r,pt,nr);
    40         }
    41     }
    42 }
    43 int main()
    44 {
    45     scanf("%d%d",&n,&m);
    46     for(int i=1;i<=n;i++)
    47     {
    48         scanf("%d%d",&t1,&t2);
    49         ve[t1].push_back(t2),mx=max(mx,t1);
    50     }
    51     mx=min(mx,m); ll *dp=tr1,*pd=tr2;
    52     for(int i=1;i<=mx;i++)
    53         if(!ve[i].empty())
    54         {
    55             sz=ve[i].size();
    56             sort(ve[i].begin(),ve[i].end(),cmp);
    57             for(int j=1;j<=sz;j++)
    58                 sum[j]=sum[j-1]+ve[i][j-1];
    59             for(int j=0,p;j<i;j++)
    60             {
    61                 p=0; for(int k=j;k<=m;k+=i) tmp[++p]=dp[k];
    62                 Solve(tmp,tep,1,p,1,p);
    63                 p=0; for(int k=j;k<=m;k+=i) pd[k]=tep[++p];
    64             }
    65             for(int j=0;j<i;j++) pd[j]=dp[j]; swap(dp,pd);
    66         }
    67     for(int i=1;i<=m;i++)
    68         dp[i]=max(dp[i-1],dp[i]),printf("%lld ",dp[i]);
    69     return 0;
    70 }
    View Code

    线性结构的话如果之前的决策区间不会过期可以决策栈,否则决策队列

    类似单调队列的操作,就是注意弹队尾的时候可能最后一个决策区间是弹了一部分,要二分一下

    NOI 2009 诗人小G

     1 #include<cmath>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define lli long long
     6 #define double long double
     7 using namespace std;
     8 const int N=100005;
     9 const lli inf=1e18;
    10 struct a
    11 {
    12     int l,r,p;
    13 }que[N];
    14 int T,n,m,k,f,b,top,pre[N],stk[N]; 
    15 lli len[N]; double dp[N]; char str[N][32];
    16 double Qpow(double x,int k)
    17 {
    18     if(k==1) return x;
    19     double tmp=Qpow(x,k/2);
    20     return k%2?tmp*tmp*x:tmp*tmp;
    21 }
    22 double Calc(int a,int b)
    23 {
    24     return dp[b]+Qpow(fabs(len[a]-len[b]-m-1),k);
    25 }
    26 int main()
    27 { 
    28     scanf("%d",&T);
    29     while(T--)
    30     {
    31         scanf("%d%d%d",&n,&m,&k);
    32         for(int i=1;i<=n;i++)
    33         {
    34             scanf("%s",str[i]+1);
    35             len[i]=len[i-1]+strlen(str[i]+1)+1;
    36         }
    37         que[f=b=0]=(a){1,n,0};
    38         for(int i=1;i<=n;i++)
    39         {
    40             while(f<b&&que[f].r<i) f++;
    41             int pt=que[f].p; que[f].l++;
    42             dp[i]=Calc(i,pt),pre[i]=pt;
    43             while(f<b&&Calc(que[b].l,que[b].p)>=Calc(que[b].l,i)) b--;
    44             int lp=que[b].l,rp=que[b].r,ps=rp+1;
    45             while(lp<=rp)
    46             {
    47                 int mid=(lp+rp)/2;
    48                 if(Calc(mid,i)<=Calc(mid,que[b].p)) rp=mid-1,ps=mid;
    49                 else lp=mid+1;
    50             }
    51             (ps==que[b].l)?b--:que[b].r=ps-1;
    52             if(ps<=n) que[++b]=(a){ps,n,i};
    53         }
    54         if(dp[n]>inf) puts("Too hard to arrange");
    55         else
    56         {
    57             printf("%lld
    ",(lli)dp[n]),top=0;
    58             for(int i=n;i;i=pre[i]) stk[++top]=i; stk[++top]=0; 
    59             for(int i=top;i;i--)
    60                 for(int j=stk[i+1]+1;j<=stk[i];j++)
    61                 {
    62                     printf("%s",str[j]+1);
    63                     j==stk[i]?puts(""):putchar(' ');
    64                 }
    65         }
    66         puts("--------------------");
    67     }
    68     return 0;
    69 }
    View Code

    其实四边形不等式也可以归进决策单调性来

    这玩意用的时候可能打决策表瞪眼比较好

    如果有个DP实在不知道怎么优化可以胡猜它满足四边形不等式,再把边界放宽一点,乱搞技巧.JPG

    ⑤斜率优化

    从这里开始我们数形结合了

    感觉这东西非常让人(wo)头大

    首先,我觉得也是最让人头大的(可能我脑回路不太正常),什么他娘的叫斜率优化?

    这种DP的转移一般形如$dp[i]=max{dp[j]+a[i]*b[j]+c[j]+d[i]}$,$a,b,c,d$都是和下标有关的变量(当然那个max可以是min)

    显然和j无关的在这个转移里都不用管,我们把剩下的东西分类整合一下

    $(a[i]*b[j])+(dp[j]+c[j])$

    那我们先说为什么会扯到斜率

    如果你高一没有一直停课,你应该听你的高中数学老师讲过一个叫做线性规划的东西

    就像上面这种玩意,图是百度随便找的

    你把可行区域画出来,拿那条直线去切它,找截距的最值

    所以呢?这和斜率优化有什么关系?

    我们把每个位置的dp值看做一个二维平面上的点......

    怎么就看做点了呢?

    个人觉得最好理解的方法是直接倒腾那个式子,因为dp[j]+c[j]都只和j有关,我们就把它们统称f[j]

    我们假设从n转移比从m转移要优,那么就有

    $a[i]*b[n]+f[n]>a[i]*b[m]+f[m]$

    $f[n]-f[m]>a[i]*(b[m]-b[n])$

    $frac{f[n]-f[m]}{b[n]-b[m]}<-a[i]$

    所以上面那个式子成立的时候从n转移比从m转移优

    现在如果我们把f(即只和转移点有关的项)看做x坐标,b(即一开始既和转移点有关又与当前位置有关的项)看做y坐标,左边的东西就是一个斜率

    那对应的我们把右边的-a[i]也看做是斜率,所有转移就是一堆以-a[i]为斜率的直线,实际进行转移的是每条直线的截距

    我们说完了什么是斜率,然后怎么优化呢?

    暴力做转移就是拿那个斜率在每个点都试一试

    优化?想想那道高考题

    显然只要在边上的点试一试就好了,当然,我们都知道这些点组成的这个东西叫做— —

    凸包

    现在的问题就是— —

    1.维护好凸包 2.在凸包上找答案

    找答案是一个通用的过程,所以斜率优化优化来优化去就是在— —

    维护凸包

    (下面这段话假设了每次新来的横坐标和询问斜率都单调)

    我们找一个单调线性结构(参考决策单调性优化DP)存储凸包上的点,以单调队列为例,对于每个点

    1.按照推出来的那个式子更新队头

    2.更新答案

    3.把那些将来不可能成为凸包上的点的点从队尾踢掉

    问题又来了

    我刚才说“下面这段话假设了每次新来的横坐标和询问斜率都单调”

    那如果询问斜率不单调怎么办?

    也就是说队头不能pop,那我们总不能每次暴力扫凸包吧

    当然不,斜率不单调也是有一个最优点的,可以二分这个最优点

    那如果新来的横坐标不单调怎么办?

    也就是说做着做着突然发现前面又出来一个点

    按时间CDQ分治,每层里把左半边按横坐标排序建凸包,把右半边放上去跑,然后递归右半边

    可以看CEOI2017 Building Bridges

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define lli long long
     5 using namespace std;
     6 const int N=100005;
     7 int n,top,pos[N],stk[N];
     8 lli h[N],s[N],x[N],y[N],dp[N];
     9 void Maxi(lli &x,lli y){if(x<y) x=y;}
    10 void Mini(lli &x,lli y){if(x>y) x=y;}
    11 bool cmp(int a,int b)
    12 {
    13     return x[a]==x[b]?y[a]<y[b]:x[a]<x[b];
    14 }
    15 bool Slope(int a,int b,int c)
    16 {
    17     return (y[a]-y[c])*(x[b]-x[c])>=(y[b]-y[c])*(x[a]-x[c]);
    18 }
    19 lli Calc(int a,int b)
    20 {
    21     return -2*h[b]*x[a]+y[a];
    22 }
    23 void CDQ(int l,int r)
    24 {
    25     if(l==r)
    26         x[l]=h[l],y[l]=dp[l]-s[l]+h[l]*h[l]; 
    27     else
    28     {
    29         int mid=(l+r)>>1;
    30         CDQ(l,mid);
    31         sort(pos+l,pos+1+mid,cmp),top=0;
    32         for(int i=l;i<=mid;i++)
    33         {
    34             while(top>1&&Slope(pos[i],stk[top-1],stk[top])) top--;
    35             stk[++top]=pos[i];
    36         }
    37         for(int i=mid+1;i<=r;i++)
    38         {
    39             int ll=1,rr=top-1,re=top;
    40             while(ll<=rr)
    41             {
    42                 int midd=(ll+rr)>>1;
    43                 if(Calc(stk[midd],i)<Calc(stk[midd+1],i)) re=midd,rr=midd-1;
    44                 else ll=midd+1;
    45             }
    46             Mini(dp[i],Calc(stk[re],i)+s[i-1]+h[i]*h[i]);
    47         }
    48         CDQ(mid+1,r);
    49     }
    50 }
    51 int main()
    52 {
    53     scanf("%d",&n);
    54     for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
    55     for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
    56     for(int i=1;i<=n;i++) pos[i]=i,s[i]+=s[i-1];
    57     memset(dp,0x3f,sizeof dp),dp[1]=0;
    58     CDQ(1,n),printf("%lld",dp[n]);
    59     return 0;
    60 }
    View Code

    终于在省选前两周学完了斜率优化,wsl

    2.选取物品型的DP

    前面说的同余单调队列优化多重背包和决策单调性优化(特定的)01背包

    二进制压缩多重背包

    NOIP内容,不说了

    凸优化

    例题:八省联考2018 林克卡特树

    以选取的个数为为x轴,最优解为y轴。如果这是个上凸函数,这种DP有个套路的优化方法叫做凸优化:二分斜率,然后我们强制选取物品时额外付出斜率的代价。把原来的DP当成一个输入斜率输出切点的黑箱,相当于不限制数目地选取。最后得到一个最优情况下选出来的数目,根据这个数目调整二分上下界即可。

    树形背包

    虽然这样不好,但我还是要借一个东西来说一个沙茶原来不会的技巧

    — —九省联考2018 CoaT(指统计这种贡献的方法)

    多项式卷积优化

    经常与生成函数一起用

    对未来的承诺

    如果当前的选取决策影响将来的选取,可以在状态里对将来进行承诺

    比如IOI2005河流

    3.其他奇奇怪怪的DP 

    状压DP

    呃,没啥可说的?

    看到范围很小记得想这个就行

    数位DP

    套路

    看到了别忘了套路就行

    (以上两种可以去洛谷博客找一找)

    虚树DP

    估计出了我又不会

    至少记得怎么建虚树吧

    (看博客去)

    动态DP(DDP)

    看NOIP2018保卫王国的题解

    插头DP

    状压走人,再问不会

    斯坦纳树

    看博客.jpg

    所以最后这些就是让人去翻博客的=。=???

  • 相关阅读:
    C# 操作Excel之旁门左道
    C#数据类型、控件、数据类型、ADO.NET等命名规范
    tab 切换函数
    ASP.NET常用JS脚本整理
    实现MSMQ消息加密的安全实践
    关于jQuery中的offset()和position()
    SQL中使用update inner join和delete inner join
    setTimeout和setInterval
    设计漂亮的样式表是一门艺术(译) (转)
    使用jQuery制作手风琴效果(转)
  • 原文地址:https://www.cnblogs.com/ydnhaha/p/10485568.html
Copyright © 2020-2023  润新知