动态规划笔记
连续段DP
#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
#define N 2005
using namespace std;
int dp[N][N];
signed main(){
int n,s,t;
scanf("%lld%lld%lld",&n,&s,&t);
dp[1][1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==s||i==t)dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
else dp[i][j]=(dp[i-1][j-1]*(j-(int)(i>s)-(int)(i>t))+dp[i-1][j+1]*j)%mod;
}
}
printf("%lld\n",dp[n][1]);
return 0;
}
将问题转化为一个排列,然后从小往大插入数,状态为当前形成j个连续段的方案数,根据题目要求进行转移
四边形不等式优化
P10861 [HBCPC2024] MACARON Likes Happy Endings
对于solve函数维护一个求解区间和一个决策区间,对求解区间进行分治,然后遍历决策区间求出mid的决策点继续分治
#include<bits/stdc++.h>
#define N 100005
#define M (1<<20)+10
using namespace std;
int n,m,d,x[N],L=1,R=0,cntl[M],cntr[M];
long long now=0,dp[N][25];
void deL(int u){
now-=cntr[x[u-1]^d];
cntl[x[u-1]]--;
cntr[x[u]]--;
return;
}
void adL(int u){
cntr[x[u]]++;
now+=cntr[x[u-1]^d];
cntl[x[u-1]]++;
return;
}
void deR(int u){
now-=cntl[x[u]^d];
cntl[x[u-1]]--;
cntr[x[u]]--;
return;
}
void adR(int u){
cntl[x[u-1]]++;
now+=cntl[x[u]^d];
cntr[x[u]]++;
return;
}
long long query(int l,int r){
while(L<l)deL(L++);
while(L>l)adL(--L);
while(R>r)deR(R--);
while(R<r)adR(++R);
return now;
}
void solve(int l,int r,int fl,int fr,int id){
if(l>r||fl>fr)return;
int mid=(l+r)/2,maxn=0;
dp[mid][id]=1e12;
for(int i=fl;i<=fr;i++){
long long v=dp[i-1][id-1]+query(i,mid);
if(v<dp[mid][id]){
dp[mid][id]=v;
maxn=i;
}
}
solve(l,mid-1,fl,maxn,id);
solve(mid+1,r,maxn,fr,id);
return;
}
signed main(){
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=n;i++)scanf("%d",&x[i]);
for(int i=1;i<=n;i++)x[i]=(x[i-1]^x[i]);
for(int i=1;i<=n;i++)dp[i][0]=1e12;
for(int i=1;i<=m;i++)solve(1,n,1,n,i);
long long ans=1e12;
for(int i=1;i<=m;i++)ans=min(ans,dp[n][i]);
printf("%lld\n",ans);
return 0;
}
概率DP
在没有明显顺序时,通过处理出各个点期望之间的关系式,通过高斯消元跑出答案。
#include<bits/stdc++.h>
#define N 505
#define M 125005
using namespace std;
struct Ty{
int u,v;
double w;
bool operator <(const Ty &a)const{return w>a.w;}
}w[M];
vector<int>y[N];
double x[N][N],dis[N],deg[N];
signed main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&w[i].u,&w[i].v);
y[w[i].u].push_back(w[i].v);
y[w[i].v].push_back(w[i].u);
deg[w[i].u]+=1.0;
deg[w[i].v]+=1.0;
}
for(int i=1;i<n;i++){
for(int j=0;j<y[i].size();j++)x[i][y[i][j]]+=1.0/deg[y[i][j]];
x[i][n]=0.0;
x[i][i]=-1.0;
if(i==1)x[i][n]-=1.0;
}
for(int i=1;i<n;i++){
int r=i;
for(int j=i;j<n;j++)if(fabs(x[j][i])>fabs(x[r][i]))r=j;
if(r!=i)swap(x[i],x[r]);
double div=x[i][i];
for(int j=i;j<=n;j++)x[i][j]/=div;
for(int j=i+1;j<n;j++){
div=x[j][i];
for(int k=i;k<=n;k++)x[j][k]-=div*x[i][k];
}
}
for(int i=n-1;i>=1;i--){
dis[i]=x[i][n];
for(int j=i+1;j<n;j++)dis[i]-=dis[j]*x[i][j];
}
for(int i=1;i<=m;i++)w[i].w=dis[w[i].u]/deg[w[i].u]+dis[w[i].v]/deg[w[i].v];
sort(w+1,w+m+1);
double ans=0.0;
for(int i=1;i<=m;i++)ans=ans+i*1.0*w[i].w;
printf("%.3lf\n",ans);
return 0;
}
斜率优化DP
将决策点转化为和j有关的点,维护凸壳,然后使用二分或者单调队列找到最优点,并计算截距,从而得到答案
#include<bits/stdc++.h>
#define N 25005
using namespace std;
int y[N],fl=1,fr=0,n,m,x[N];
long long dp[N][30],ans=0;
inline int lowbit(int u){return u&-u;}
void update(int u,int val){
for(int i=u;i<=n;i+=lowbit(i))y[i]+=val;
return;
}
int query(int u){
int now=0;
for(int i=u;i;i-=lowbit(i))now+=y[i];
return now;
}
void del(int u){
update(x[u],-1);
ans-=fr-fl+1-query(x[u]);
return;
}
void adl(int u){
update(x[u],1);
ans+=fr-fl+1-query(x[u]);
return;
}
void der(int u){
update(x[u],-1);
ans-=query(x[u]);
return;
}
void adr(int u){
ans+=query(x[u]);
update(x[u],1);
return;
}
long long query(int l,int r){
while(fl<l)del(fl++);
while(fl>l)adl(--fl);
while(fr<r)adr(++fr);
while(fr>r)der(fr--);
return ans;
}
void solve(int l,int r,int L,int R,int id){
if(r<l||R<L)return;
int mid=(l+r)/2;
dp[mid][id]=2e10;
int minn=0;
for(int i=L;i<=min(mid-1,R);i++){
long long v=dp[i][id-1]+query(i+1,mid);
if(v<dp[mid][id]){
dp[mid][id]=v;
minn=i;
}
}
solve(l,mid-1,L,minn,id);
solve(mid+1,r,minn,R,id);
return;
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&x[i]);
for(int i=1;i<=n;i++){
adr(++fr);
dp[i][1]=ans;
}
for(int i=2;i<=m;i++)solve(1,n,1,n,i);
long long now=2e10;
for(int i=1;i<=m;i++)now=min(now,dp[n][i]);
printf("%lld\n",now);
return 0;
}
树形DP
题目中的A不用管,记Y点为白点,然后题目可以转化为白点不相邻,然后除了根节点加了b个白点,然后总共有c/2个非叶子节点
考虑设计状态为 \(dp_{i,j,k}\) 表示以 \(i\) 为根的子树,选了 \(c/2\) 个非叶子白点,\(k\) 表示 \(i\) 的状态。
那么如果限制 \(j\le siz_i\),那么总转移复杂度就是 \(O(n^2)\) 的,可以直接做,但是注意对于边界情况 \(i\) 为叶子节点时,\(dp_{i,1,0}=dp_{i,1,1}=-inf\) 即不存在情况。
#include<bits/stdc++.h>
#define N 10005
using namespace std;
int dp[N][N][2],siz[N],fa[N],ls[N],rs[N],n;
void dfs(int u){
siz[u]=1;
if(!ls[u]){
dp[u][0][1]=1;
dp[u][0][0]=0;
return;
}
dfs(ls[u]);
siz[u]+=siz[ls[u]];
dfs(rs[u]);
siz[u]+=siz[rs[u]];
for(int i=0;i<=siz[rs[u]];i++){
for(int j=0;j<=siz[ls[u]];j++){
dp[u][i+j][0]=max(dp[u][i+j][0],max(dp[ls[u]][j][0],dp[ls[u]][j][1])+max(dp[rs[u]][i][0],dp[rs[u]][i][1]));
dp[u][i+j+1][1]=max(dp[u][i+j+1][1],dp[ls[u]][j][0]+dp[rs[u]][i][0]);
}
}
return;
}
signed main(){
int T;
scanf("%d",&T);
while(T--){
int a,b,c;
scanf("%d%d%d%d",&n,&a,&b,&c);
for(int i=1;i<=n;i++)for(int j=0;j<=n;j++)dp[i][j][0]=dp[i][j][1]=-1e8;
for(int i=1;i<=n;i++)ls[i]=rs[i]=siz[i]=fa[i]=0;
for(int i=2;i<=n;i++){
scanf("%d",&fa[i]);
if(!ls[fa[i]])ls[fa[i]]=i;
else rs[fa[i]]=i;
}
if(c%2||b-c/2+1<0){
printf("No\n");
continue;
}
dfs(1);
if(dp[1][c/2][0]>=b-c/2&&b-c/2>=0||dp[1][c/2][1]>=b-c/2+1)printf("Yes\n");
else printf("No\n");
}
return 0;
}

浙公网安备 33010602011771号