线性dp应该是dp中比较简单的一类,不过也有难的。(矩乘优化递推请出门右转)

  线性dp一般是用前面的状态去推后面的,也有用后面往前面推的,这时候把循环顺序倒一倒就行了。如果有的题又要从前往后推又要从后往前推...那它还叫线性dp吗?

  传球游戏:https://www.luogu.org/problemnew/show/P1057

  题意概述:一些人围成一个圈,每次可以把球传给左右两个人,求m步后回到第一个人手里的方案数。

  这题大概也可以矩乘?不过递推就可以了,$dp[i][j]$表示传了j步,现在在i手里的方案数。转移:

  $dp[i][j]=dp[i+1][j-1]+dp[i-1][j-1]$

  边界再处理一下就行了。

  

# include <cstdio>
# include <iostream> using namespace std; int n,m;
long long dp[][]; int main()
{
scanf("%d%d",&n,&m);
dp[][]=;
for (int i=;i<=m;i++)
{
dp[][i]=dp[][i-]+dp[n][i-];
dp[n][i]=dp[n-][i-]+dp[][i-];
for (int j=;j<n;j++)
dp[j][i]=dp[j-][i-]+dp[j+][i-];
}
printf("%d",dp[][m]);
return ;
}

传球游戏


     最长(上升/下降/不上升/不下降)子序列类

  题意概述:不用概述吧。

  写$blog$是为了备忘,但是我觉得$n^{2}$做法不可能忘,于是就不写了吧!

  $NlogN$的做法有两种,一种是用树状数组,一种是二分,就按照最长上升来讲,其他的也差不多啦;

  树状数组:一开始很难理解,后来看了一个很不错的课件才明白的,其实也不难。将数字的大小作为树状数组的下标,每次从0到$a_{i}-1$取最大值加一即为以$a_{i}$结尾的最长上升序列长度。

这里还有一个小问题:如果$a_{i}$比较大,时间复杂度就比较低了,可以考虑离散化一下,就又回到了$NlogN$。

  二分:用$d[i]$保存以长度为$i$的上升子序列中最小的一个结尾,这样构造下去,$d$数组是单调上升的,可以二分一下看看目前的这个数应该插入到哪里或者是直接扔掉,可以很容易的保证复杂度(因为最长上升子序列的长度不可能超过$n$)。

  看一个用二分写的题吧(树状数组也差不多...)

    

  导弹拦截:https://www.luogu.org/problemnew/show/P1020

  

# include <cstdio>
# include <iostream>
# include <cstring>
# define R register int using namespace std; int a[],d[];
int n=,j,len=,l,r,mid; int sfind(int x)
{
l=;
r=len;
while (l<r)
{
mid=(l+r)>>;
if(d[mid]>=x)
l=mid+;
else
r=mid;
}
return l;
} int zfind(int x)
{
l=;
r=len;
while (l<r)
{
mid=(l+r)>>;
if(d[mid]>=x)
r=mid;
else
l=mid+;
}
return l;
} int main()
{
while(scanf("%d",&a[++n])==); --n;
for (R i=;i<=n;i++)
{
if(a[i]<=d[len]) d[++len]=a[i];
else
{
j=sfind(a[i]);
d[j]=a[i];
}
}
printf("%d\n",len);
len=;
memset(d,-,sizeof(d));
for(R i=;i<=n;i++)
{
if(a[i]>d[len]) d[++len]=a[i];
else
{
j=zfind(a[i]);
d[j]=a[i];
}
}
printf("%d",len);
return ;
}

导弹拦截

  首先就是求一个最长不上升子序列,然后第二问可以通过一些奇妙的性质(Dilworth定理)转换为求最长上升子序列。这个定理好像很麻烦的样子...等我学到偏序再回来补这个。

  

  Milky-way学姐的测试:

   

  转换一下发现就是求最长下降子序列的长度。

  

# include <cstdio>
# include <iostream>
# include <cstring> using namespace std; const int maxn=;
int mid,l,r,j,T,n,k,ans,len;
long long a[maxn],d[maxn]; int Find(int x)
{
l=;
r=len;
while (l<r)
{
mid=(l+r)>>;
if(d[mid]>x)
l=mid+;
else
r=mid;
}
return l;
} int main()
{
scanf("%d",&T);
while (T)
{
scanf("%d%d",&n,&k);
for (int i=;i<=n;i++)
scanf("%lld",&a[i]);
memset(d,-,sizeof(d));
len=;
for (int i=;i<=n;i++)
{
if(a[i]<d[len]) d[++len]=a[i];
else
{
j=Find(a[i]);
d[j]=a[i];
}
}
if(len>=k) printf("No\n");
else printf("Yes\n");
T--;
}
return ;
} D

有趣的排列问题

    

  友好城市:https://www.luogu.org/problemnew/show/P2782

  转换一下又是最长不下降子序列。

  木棍加工:https://www.luogu.org/problemnew/show/P1233

  题意概述:有n根木棍需要加工,如果某个木棍比另一个木棍短,且比他窄,两者连着加工就只算一份时间,求最少时间。

  多么亲切而又熟悉的题目啊!首先可以按照长短(或者是宽窄)排个序,就从根本上解决了一半的问题,再求一个最长上升子序列就是答案咯。

  

# include <cstdio>
# include <iostream>
# include <algorithm>
# define R register int using namespace std; struct woo
{
int l,w;
}a[];
int ans=,n,rx,rf,dp[];
char rc; bool cmp(woo a,woo b)
{
return a.l>b.l;
} int read()
{
rx=,rf=,rc=getchar();
while (!isdigit(rc))
{
if(rc=='-') rf=-rf;
rc=getchar();
}
while (isdigit(rc))
{
rx=(rx<<)+(rx<<)+(rc^);
rc=getchar();
}
return rx*rf;
}
int main()
{
n=read();
for (R i=;i<=n;i++)
a[i].l=read(),a[i].w=read();
sort(a+,a++n,cmp);
for (R i=;i<=n;i++)
{
dp[i]=;
for (R j=;j<=i;j++)
if(a[i].w>a[j].w) dp[i]=max(dp[i],dp[j]+);
ans=max(ans,dp[i]);
}
printf("%d",ans);
return ;
}

木棍加工


   护卫队:https://www.luogu.org/problemnew/show/P1594

  题意概述:一队车队过桥,每辆车有其速度和质量,每次可以选择一组连续的,总重不超过限制的车一起过桥,速度以最低速度为准,求最短的总时间。

  一开始想的有点复杂$f[i][j]$表示前i个分j组的最短时间,后来发现对分组数并没有什么限制,所以直接扔掉第2维就好啦。在计算$f[i]$的时候,可以枚举j,只要i到j的总重不超过限重,就用来更新一下。这道题的总重要用前缀和优化,最小值可以用st表。还有$f[i]$的初始最大值可能会设置的不够大,这时候可以用$-1$表示,如果是$-1$就强制更新。

    

# include <cstdio>
# include <iostream>
# include <cmath>
# define R register int using namespace std; const int maxn=;
int n;
long long mw,l,w[],S[],dp[][];
double f[]; long long mi(int l,int r)
{
if(l>r) swap(l,r);
int k=log2(r-l+);
return min(dp[l][k],dp[r-(<<k)+][k]);
} int main()
{
scanf("%lld%lld%d",&mw,&l,&n);
for (R i=;i<=n;++i)
{
scanf("%lld%lld",&w[i],&dp[i][]);
S[i]=S[i-]+w[i];
}
for (R j=;(<<j)<=n;++j)
for (R i=;i+(<<j)-<=n;++i)
dp[i][j]=min(dp[i][j-],dp[i+(<<(j-))][j-]);
for (R i=;i<=n;i++)
f[i]=9223372036854775807ll;
f[]=;
for (R i=;i<=n;++i)
{
for (int j=i;j>=;j--)
{
if(S[i]-S[j-]>mw) break;
f[i]=min(f[i],f[j-]+(double)l/mi(i,j));
}
}
printf("%.1lf",f[n]*);
return ;
}

护卫队


  山区建小学:http://noi.openjudge.cn/ch0206/7624/

  还没做...


  低价购买:https://www.luogu.org/problemnew/show/P1108

  题意概述:最长下降子序列统计方案数,要求不能出现重复的序列。

  如果没有后半句要求,这道题就非常简单了,加上这个要求其实也并没有难很多。

  如果两个子序列是重复的,那么必然每个元素都是一一对应的。如果从前往后递推就可以不用考虑前面更多个元素,只要保证最后一个元素不相同就必然不会重复了。为了好处理,我们只考虑除了最后一位以外的去重,输出时再判断最后一位。注意一下这个递推式  $$f_i=\begin{Bmatrix}
f_j(dp_j+1>dp_i)\\f_i+f_j (dp_j+1==dp_i)

\end{Bmatrix}$$

所以说只要用来更新答案的j的a[j]两两不相同就一定保证不会重复,那会不会少呢?其实是会的,因为同一个元素结尾的子序列长度不一定相同啊。但是我们只关心最大值,而且靠后的元素能形成的子序列一定包含了所有前面的相同元素所能构成的子序列,所以倒着枚举,对于每个a[j]只取一次值。那怎么看每个a[j]是否用来更新过呢?用$set$...所以说这就成了一个特别高端的算法!红黑树优化dp!

  

 // luogu-judger-enable-o2
# include <cstdio>
# include <iostream>
# include <set> using namespace std; int n;
int a[];
int dp[];
long long f[];
set <int> s; int main()
{
scanf("%d",&n);
for (int i=;i<=n;++i)
scanf("%d",&a[i]);
for (int i=;i<=n;++i)
{
dp[i]=,f[i]=;
s.clear();
for (int j=i-;j>=;--j)
{
if(a[j]<=a[i]) continue;
if(dp[j]+>dp[i])
{
s.clear();
dp[i]=dp[j]+;
f[i]=f[j];
s.insert(a[j]);
}
else
{
if(dp[j]+==dp[i])
{
if(s.find(a[j])!=s.end()) continue;
f[i]+=f[j];
s.insert(a[j]);
}
}
}
}
int ans=;
long long num=;
s.clear();
for (int i=;i<=n;++i)
ans=max(ans,dp[i]);
for (int i=n;i>=;--i)
{
if(ans==dp[i])
{
if(s.find(a[i])!=s.end()) continue;
num+=f[i];
s.insert(a[i]);
}
}
printf("%d %lld",ans,num);
return ;
}

低价购买

  等以后再看到这题可以试一试$NlogN$的做法,然而我也不知道是否有这种做法。

  花店橱窗布置:https://www.luogu.org/problemnew/show/P1854

  题意概述:f朵花,v个瓶子,要求花要按照顺序插进瓶子里(不用连续),每朵花在每个瓶子里能创造的价值都是不一样的,求最大价值并输出方案。

  单调队列,滚动数组都可以试一试,不过这题$1<=f,v<=100$岂不是随便做。

  $f[i][j]$表示前i朵花插在前j个花瓶的最大价值。为了方便,我们不妨钦定第i朵花就插在j号瓶子里,这么一来转移就非常简单了:$f[i][j]=a[i][j]+max(f[i-1][k])(k<j)$,对于每一个dp状态再记录一个前驱方便输出方案,一开始一定要给f数组初始化为极小值,否则如果出现负数就会WA。

  

 # include <cstdio>
# include <iostream>
# include <cstring> using namespace std; int f,v;
int dp[][],a[][],p[][];
int sta[],Top=; int main()
{
scanf("%d%d",&f,&v);
for (int i=;i<=f;++i)
for (int j=;j<=v;++j)
scanf("%d",&a[i][j]);
memset(dp,,sizeof(dp));
dp[][]=;
for (int i=;i<=f;++i)
for (int j=;j<=v;++j)
{
for (int k=;k<j;++k)
{
if(dp[i-][k]>dp[i][j])
{
dp[i][j]=dp[i-][k];
p[i][j]=k;
}
}
dp[i][j]+=a[i][j];
}
int ans=,x;
for (int i=;i<=v;++i)
if(dp[f][i]>ans)
ans=max(ans,dp[f][i]),x=i;
printf("%d\n",ans);
while (x)
{
sta[++Top]=x;
x=p[f][x];
f--;
}
for (int i=Top;i>=;--i)
printf("%d ",sta[i]);
return ;
}

花店橱窗布置

  过河:https://www.luogu.org/problemnew/show/P1052

  题意概述:长度为l的序列上有m个特殊的点,从0出发,每次可以在$[i+s,i+t]$中选出一个点转移过去,求最小化跳到特殊点的数量。($l<=10^9,m<=100,s,t<=10$)

  题意非常有误导性,一开始还以为是每次必须跳到某一个特殊点上...其实还是比较简单的,我觉得都不用写了。但是l的范围非常大,用朴素的做法肯定不行,发现m,s,t的范围都不是很大(暗示我们这是一道最小生成树)可以手玩一下,如果两个石子之间的空隙非常大(这种情况是一定会出现的),因为这之间没有石子,所以有相当一部分的答案是不会变化的(好像这么说也不大对...不知道该怎么表述了),总之这么大的空隙是非常没有用的,我们可以直接把它缩起来,这个缩空间的界限非常模糊,也可以说都对,我一开始是直接%t,不知道是写挂了还是思路错了只得了10分,后来看了一个神奇的题解运用扩欧求出来可以把大的空间缩成t*(t-1),也有用1-10的lcm的。这里要注意一个问题,如果s=t,就只有一种走法了,建议特判,尤其是第一种缩点法不能应对这种情况。统计答案不用找到终点,其实只要从最后一个石子往后统计上10个左右就可以了。

  注意事项:如果要对拍一定要分清哪个是改过的哪个没改过,不要对拍用的是一份,交到OJ上的是另一份,因为这个原因浪费了3次提交qwq

  

 # include <cstdio>
# include <iostream>
# include <cstring>
# include <algorithm> using namespace std; int las,l;
int ans=,s,t,m,x;
int a[];
int dp[];
bool vis[]; int main()
{
scanf("%d%d%d%d",&l,&s,&t,&m);
memset(dp,,sizeof(dp));
dp[]=;
for (int i=;i<=m;++i)
scanf("%d",&a[i]);
sort(a+,a+m+);
if(s==t)
{
ans=;
for(int i=;i<=m;i++)
if(a[i]%s==)
ans++;
printf("%d\n",ans);
return ;
}
for (int i=;i<=m;++i)
{
x=a[i];
a[i]=a[i]-las;
las=x;
}
for (int i=;i<=m;++i)
{
if(a[i]>=t*(t-)) a[i]=t*(t-);
a[i]+=a[i-];
vis[a[i]]=;
}
for (int i=;i<=;++i)
{
if(vis[i]) dp[i]++;
for (int j=s;j<=t;++j)
dp[i+j]=min(dp[i+j],dp[i]);
}
for (int i=;i<=;++i)
ans=min(ans,dp[a[m]+i]);
printf("%d",ans);
return ;
}

过河

  

  严格$n$元树:https://www.luogu.org/problemnew/show/P4295

  题意概述:(这次我决定抄题面)如果一棵树的所有非叶节点都恰好有$n$个儿子,那么我们称它为严格$n$元树。如果该树中最底层的节点深度为$d$(根的深度为$0$),那么我们称它为一棵深度为$d$的严格$n$元树。

  一开始把式子弄得特别复杂,后来发现运用一点容斥的思想可以使式子变得简单许多.一个简单的想法是一棵深度为$d$的严格$n$元树肯定有$n$个深度为$x-1$的儿子,但是这样真的对吗?显然是错误的,因为只要它有一个儿子的深度到达$d-1$就足以使他成为一棵深度为$d$的树了.难道要运用组合数来判断每棵子树的深度情况吗...?还有一种思考方法:每个儿子的深度是$<=x-1$的,记录一个前缀和,保存深度$<=x$的树的数目的总和.每个点选取$n$个深度$<=d$的子树之后就会出现好多不合法的树,这些树的深度都是$<=d$的,而且也涵盖了所有的这种树,于是就有了一个简单的转移方程:

  $f_i=s_{i-1}^n-s_{i-1}+1$

  这题可以说是"想题十分钟,高精半小时"的典范了.  

  

 # include <cstdio>
# include <iostream>
# include <cstring>
# define R register int using namespace std; int n,d;
struct intt
{
int a[];
int len;
intt()
{
memset(a,,sizeof(a));
len=;
}
}f,s; intt mul (intt a,intt b)
{
intt c;
for (R i=;i<=a.len;++i)
for (R j=;j<=b.len;++j)
c.a[i+j-]+=a.a[i]*b.a[j];
c.len=a.len+b.len+;
for (R i=;i<=c.len;++i)
{
c.a[i+]+=c.a[i]/;
c.a[i]%=;
}
while (c.len&&c.a[ c.len ]==) c.len--;
return c;
} intt qui (intt a,int b)
{
intt s;
s.a[]=;
s.len=;
while (b)
{
if(b&) s=mul(s,a);
a=mul(a,a);
b=b>>;
}
return s;
} void add_one()
{
int l=;
f.a[]++;
while (f.a[l]>=)
{
f.a[l+]+=f.a[l]/;
f.a[l]%=;
l++;
}
f.len=max(f.len,l);
} intt add (intt a,intt b)
{
intt c;
c.len=max(a.len,b.len)+;
for (R i=;i<=c.len;++i)
{
c.a[i]+=a.a[i]+b.a[i];
c.a[i+]+=c.a[i]/;
c.a[i]%=;
}
while (c.len&&c.a[ c.len ]==) c.len--;
return c;
} intt sub (intt a,intt b)
{
for (R i=;i<=a.len;++i)
{
a.a[i]-=b.a[i];
if(a.a[i]<) a.a[i]+=,a.a[i+]--;
}
while (a.len&&a.a[ a.len ]==) a.len--;
return a;
}
int main()
{
scanf("%d%d",&n,&d);
f.a[]=,s.len=f.len=,s.a[]=;
for (R i=;i<=d;++i)
{
f=qui(s,n);
f=sub(f,s);
add_one();
s=add(s,f);
}
for (R i=f.len;i>=;--i) printf("%d",f.a[i]);
return ;
}

严格n元树

  牡牛和牝牛:https://www.lydsy.com/JudgeOnline/problem.php?id=3398

  题意概述:一共有$n$头牛排队,要求任意两只牡牛之间至少要有$K$只牝牛,求方案数;每头牛可以是牡牛也可以是牝牛,同一种牛内部不再区分.$n<=10^5$

  虽然是排列组合的课后习题,但是用$dp$显然更好做一点.$dp_i$表示第$i$头是牡牛的方案数,转移时加上$\sum_{t=0}^{i-k}$,统计答案时要全部统计.前缀和优化可以做到$O(N)$.似乎还有一种更简单的思路:$f_i$表示当前填到第$i$头的方案数,如果第$i$头选牡牛,那么前面$k$头等于说已经固定了,从$f_{i-k-1}$转移,如果选牝牛,那么之前的选什么都行,直接从$f_{i-1}$转移。

  

 # include <cstdio>
# include <iostream>
# define mod
# define R register int using namespace std; const int maxn=;
int n,k,dp[maxn],s[maxn]; int main()
{
scanf("%d%d",&n,&k);
for (R i=;i<=k;++i)
{
dp[i]=;
s[i]=dp[i];
if(i) s[i]+=s[i-];
if(s[i]>mod) s[i]-=mod;
}
for (R i=k+;i<=n;++i)
{
dp[i]=s[i-k-];
if(i) s[i]=dp[i]+s[i-];
else s[i]=dp[i];
if(s[i]>mod) s[i]-=mod;
}
printf("%d",s[n]%mod);
return ;
}

牡牛和牝牛

---shzr

线性dp的更多相关文章

  1. LightOJ1044 Palindrome Partitioning(区间DP+线性DP)

    问题问的是最少可以把一个字符串分成几段,使每段都是回文串. 一开始想直接区间DP,dp[i][j]表示子串[i,j]的答案,不过字符串长度1000,100W个状态,一个状态从多个状态转移来的,转移的时 ...

  2. Codeforces 176B (线性DP+字符串)

    题目链接: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=28214 题目大意:源串有如下变形:每次将串切为两半,位置颠倒形成 ...

  3. hdu1712 线性dp

    //Accepted 400 KB 109 ms //dp线性 //dp[i][j]=max(dp[i-1][k]+a[i][j-k]) //在前i门课上花j天得到的最大分数,等于max(在前i-1门 ...

  4. 动态规划——线性dp

    我们在解决一些线性区间上的最优化问题的时候,往往也能够利用到动态规划的思想,这种问题可以叫做线性dp.在这篇文章中,我们将讨论有关线性dp的一些问题. 在有关线性dp问题中,有着几个比较经典而基础的模 ...

  5. POJ 2479-Maximum sum(线性dp)

    Maximum sum Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 33918   Accepted: 10504 Des ...

  6. poj 1050 To the Max(线性dp)

    题目链接:http://poj.org/problem?id=1050 思路分析: 该题目为经典的最大子矩阵和问题,属于线性dp问题:最大子矩阵为最大连续子段和的推广情况,最大连续子段和为一维问题,而 ...

  7. nyoj44 子串和 线性DP

    线性DP经典题. dp[i]表示以i为结尾最大连续和,状态转移方程dp[i] = max (a[i] , dp[i - 1] + a[i]) AC代码: #include<cstdio> ...

  8. 『最大M子段和 线性DP』

    最大M子段和(51nod 1052) Description N个整数组成的序列a[1],a[2],a[3],-,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的.如果M &g ...

  9. 『最长等差数列 线性DP』

    最长等差数列(51nod 1055) Description N个不同的正整数,找出由这些数组成的最长的等差数列. 例如:1 3 5 6 8 9 10 12 13 14 等差子数列包括(仅包括两项的不 ...

  10. cf909C 线性dp+滚动数组好题!

    一开始一直以为是区间dp.. /* f下面必须有一个s 其余的s可以和任意f进行匹配 所以用线性dp来做 先预处理一下: fffssfsfs==>3 0 1 1 dp[i][j] 表示第i行缩进 ...

随机推荐

  1. spring 中 InitializingBean 接口使用理解

    前言:这两天在看 spring 与 quart 的集成,所以了解到 spring 是如何初始化 org.springframework.scheduling.quartz.SchedulerFacto ...

  2. JavaScript document和window属性及方法详解

    [document对象] 该对象是window和frames对象的一个属性,是显示于窗口或框架内的一个文档. 属性 alinkColor 活动链接的颜色(ALINK)  anchor 一个HTMI锚点 ...

  3. Https之SSL原理

    一.HTTPS和SSL HTTP(Hyper TEXT Transfer Protocol超文本传输协议)是目前互联网上应用最为广泛的一种网络协议,用于在Web浏览器和网站服务器之间传递信息,但是HT ...

  4. Spring Boot -01- 快速入门篇(图文教程)

    Spring Boot -01- 快速入门篇(图文教程) 今天开始不断整理 Spring Boot 2.0 版本学习笔记,大家可以在博客看到我的笔记,然后大家想看视频课程也可以到[慕课网]手机 app ...

  5. mysql 客户端

    MySQL是基于C/S模式的数据库管理系统.MySQL公司开发了众多的客户端软件来帮助用户管理MySQL软件,最著名的就是 MySQL Command Line Client 和 MySQL-Work ...

  6. @transient加在属性前的作用

    我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable ...

  7. zabbix系列之一——简要介绍

    参考来源:(官网) https://www.zabbix.com/documentation/3.4/manual/introduction/about 1what’s zabbix? index d ...

  8. Linux wget安装详解

    Wget是一个十分常用命令行下载工具,多数Linux发行版本都默认包含这个工具.如果没有安装可在 http://www.gnu.org/software/wget/wget.html下载最新版本,并使 ...

  9. cef开启摄像头和录音

    参考资料:https://github.com/cztomczak/phpdesktop/wiki/Chrome-settings#command_line_switches CefSharp中文帮助 ...

  10. 退出全屏监听ESC事件

    fullscreenchange事件 fullscreenchange:当窗口大小改变时触发 isFullscreen:全局变量 window.addEventListener("fulls ...