DP
最长上升子序列(LIS)——O(nlogn)
定义
最长上升子序列是:
1:只包含ai的子序列
2:满足j<i并且aj<ai
3:子序列长度最长
朴素算法O(n^2) dp+二分(nlogn)
dp数组维护的是以pos位置结尾最小可能的值,如果要打印最长上升子序列路径,开辟新数组path,倒着找合法序列,正序寻找就会出现
1 2 4 6(2 8 6 7)这种不合法序列。虽然满足所有ai <aj 但是存在i>j
注意:严格lis:lower_bound,非严格lis:upper_bound
vector<int>lis;
for(int j=i;j<n+i;j++)
{
auto it=upper_bound(all(lis),a[j]);
if(it==lis.end())
lis.pb(a[j]);
else
lis[it-lis.begin()]=a[j];
}
LCS(最长公共序列)——O(n^2)
枚举一下就穿上裤子就走人
for(int i=1; i<=len1; ++i)
{
for(int j=1; j<=len2; ++j)
{
if(s[i-1]==t[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
cout<<dp[len1][len2]<<endl;
区间DP——O(n3)
一般问题
把相邻符合条件的合并,来获得最优解
概念
区间类型动态规划是线性动态规划的拓展,它在分阶段划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。(例:f[i][j]=f[i][k]+f[k+1][j])
区间类动态规划的特点:
合并:即将两个或多个部分进行整合。
特征:能将问题分解成为两两合并的形式。
求解:对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优值。
DP(n^3) DP+四边形不等式优化(n^2)
一般数据500还能跑跑,如果1e3基本凉了,要优化到O(n^2),要用到四边形不等式优化。暂时不会,再等等吧
cin>>n;
memset(dp,inf);///初始化dp数组
for(int i=1; i<=n; ++i)
cin>>a[i],pre[i]=pre[i-1]+a[i],dp[i][i]=0;
for(int len=1; len<n; ++len) ///枚举区间长度
{
for(int i=1; i+len<=n; ++i) ///枚举区间起点
{
int j=i+len;///根据起点得到合法终点
for(int k=i; k<j; ++k) ///枚举最优分割点
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
dp[i][j]+=pre[j]-pre[i-1];
}
}
cout<<dp[1][n]<<endl;
树形DP——O(m)
算法核心
出发点:点对至少需要两个点。
枚举每条边的u和v,u是v父节点,v向下的最大直径+cost+u向上的最大直径
向下的最大直径,就是看最大跟次大,但是每次先加入当前点值,保证每次直径存在尾点权
向上的最大直径,u结点先更新点权跟向上最大简单路径,再更新v的最大直径点权和
子树计数
问题:
换根理解
u到v之前,把v向上的最长路径更新一下。
取决于u向下的第一长路径是否是v
- 如果是,dp[v][0]=max(dp[u][0],dp[u][2])+1;
- 如果不是,dp[v][0]=max(dp[u][0],dp[u][1])+1;
每个结点换根,只需要考虑dp[v][0]是否更新,换根并不是字面意思换根,只是把dp[v][0]除了第一次dfs模型向下路径,其他路径的最长更新dp[v][0]
vector<int>G[N];
int a[N],dp[N],ans;
void dfs(int u,int fat)
{
dp[u]=a[u];
for(auto v:G[u])
{
if(v!=fat)
{
dfs(v,u);
dp[u]+=max(0,dp[v]);
}
}
ans=max(ans,dp[u]);
}
换根-任意一点能到达的最远距离
const int N=5e5+5;
vector<pair<int,int> >G[N];
int dp[N][3],pot[N];
void dfs(int u,int fat)
{
for(auto it:G[u])
{
int v=it.F,cost=it.S;
if(v==fat)
continue;
dfs(v,u);
if(dp[u][0]<=dp[v][0]+cost)
{
dp[u][1]=dp[u][0];
dp[u][0]=dp[v][0]+cost;
pot[u]=v;
}
else if(dp[u][1]<dp[v][0]+cost)
dp[u][1]=dp[v][0]+cost;
}
}
void dfs1(int u,int fat)
{
for(auto it:G[u])
{
int v=it.F,cost=it.S;
if(v==fat)
continue;
if(pot[u]==v)
dp[v][2]=cost+max(dp[u][1],dp[u][2]);
else
dp[v][2]=cost+max(dp[u][0],dp[u][2]);
dfs1(v,u);
}
}
状压DP——O(3n)
15 or 16可以枚举子集,以上就不行了
枚举子集
例如:1011
子集1010 1001 1000 0011 0010 0001
然后xor ,就可以得到子集的补集,合并取最小即可
枚举子集
for(int i=1;i<(1<<n);i++)
{
dp[i]=get(i);
for(int j=i;j;j=(j-1)&i)
dp[i]=min(dp[i],dp[j]+dp[j^i]);
}
cout<<dp[ (1<<n)-1 ]<<endl;
概率DP
概率顺着推,期望逆着推
高维前缀和(sosdp)—— O(nlogn)
子集前缀和
for(int i=0;i<22;i++)
for(int j=0;j<(1<<22);j++)
if(j&(1<<i))
dp[j]+=dp[j^(1<<i)];
数位DP
状态考虑完全,都加入的dp数组中,参考YKW模板
注意:有的时候,把limit加入dp中,每次清空dp更快,有的时候,取消limit,一次清空更快。
int dp[20][2][2][unknow],a[20];
int dfs(int pos,bool limit,bool lead,int state)
{
if(pos==0)///边界条件
return true or false;
if(limit==0&&dp[pos][limit][lead][state]!=-1)
return dp[pos][limit][lead][state];
int maxn=(limit?a[pos]:9);
int ans=0;
for(int i=0;i<=maxn;i++)
{
if(lead&&i==0)
ans+=dfs(pos-1,limit&&i==maxn,true,state);
else///确定状态的改变
ans+=dfs(pos-1,limit&&i==maxn,false,state);
}
if(limit==0)
dp[pos][limit][lead][state]=ans;
return ans;
}
int query(int x)
{
///这里不 容易TLE——memset(dp,-1);
int pos=0;
while(x)
a[++pos]=x%10,x/=10;
return dfs(pos,true,true,0);///开始有限制,有前导0
}
///输入样例之前清空dp