省选前的动态规划大杂烩

这是一个不知道为什么反正就是想写成汇总的东西,整合一些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\n",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\n",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\n",(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

 

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

 

转载于:https://www.cnblogs.com/ydnhaha/p/10485568.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值