dp 杂题选做
区间dp
考虑设 \(f[i][j]\) 表示区间 \([l,r]\) 合并过的值
枚举断点 当 \(f[l][k]==f[k+1][r]\) 且都有值的时候 可以合并出 \(f[l][k]+1\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=305;
const int M=1e5+5;
const int inf=0x7fffffff;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,a[N],f[N][N],ans;
signed main(){
n=read();
for(int i=1;i<=n;i++)f[i][i]=read();
for(int i=1;i<=n;i++)ans=max(ans,f[i][i]);
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int k=l;k<r;k++){
if(f[l][k]==f[k+1][r]&&f[l][k]){
f[l][r]=max(f[l][r],f[l][k]+1);
ans=max(ans,f[l][r]);
}
}
}
}
writel(ans);
return 0;
}
设 \(f[l][r][0/1]\) 表示排成 \(a[l-r]\) 队形的方案 且上一个排进来的是左/右
那么只能从 \([l+1,r]\) 或 \([l,r-1]\) 转移(因为只会加到左/右)
判一下左右与当前点的大小关系即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long
#define int ll
const int N=1e3+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=19650827;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,a[N],f[2][N][N];
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)f[0][i][i]=0,f[1][i][i]=1;
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
f[0][l][r]=(f[0][l][r]+(a[l+1]>a[l])*f[0][l+1][r]+(a[r]>a[l])*f[1][l+1][r])%mod;
f[1][l][r]=(f[1][l][r]+(a[l]<a[r])*f[0][l][r-1]+(a[r-1]<a[r])*f[1][l][r-1])%mod;
}
}
writel((f[0][1][n]+f[1][1][n])%mod);
return 0;
}
背包
细节有点多。
发现跳/不跳/跳几次 类似背包的选/不选
设 \(f[i][j]\) 代表调到第 \(i\) 列 高度为 \(j\) 的最小步数
那么类似01背包的:
\[f[i][j]=\min(f[i][j],f[i-1][j-up[i-1]]+1)
\]
\[f[i][j]=\min(f[i][j],f[i-1][j+down[i-1]])
\]
类似完全背包的:
\[f[i][j]=\min(f[i][j],f[i][j-up[i-1]]+1)
\]
对于跳的时候顶到上方的情况 特殊处理一下
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long
const int N=1e4+5;
const int M=1e3+5;
const int inf=0x3f3f3f3f;
const int mod=19650827;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,m,k,f[N][M],up[N],down[N],top[N],flr[N],p,l,h,cnt,mi;
signed main(){
n=read();m=read();k=read();
for(int i=0;i<=n-1;i++)up[i]=read(),down[i]=read();
for(int i=1;i<=n;i++)top[i]=m+1,flr[i]=0;
while(k--){
p=read();l=read();h=read();
top[p]=h,flr[p]=l;
}
memset(f,0x3f,sizeof f);
for(int i=0;i<=m;i++)f[0][i]=0;
for(int i=1;i<=n;i++){
for(int j=up[i-1];j<=m;j++)f[i][j]=min(f[i-1][j-up[i-1]]+1,f[i][j-up[i-1]]+1);
for(int j=m+1;j<=m+up[i-1];j++)f[i][m]=min(f[i][m],min(f[i-1][j-up[i-1]]+1,f[i][j-up[i-1]]+1));
for(int j=1;j+down[i-1]<=m;j++)f[i][j]=min(f[i][j],f[i-1][j+down[i-1]]);
for(int j=1;j<=flr[i];j++)f[i][j]=inf;
for(int j=top[i];j<=m;j++)f[i][j]=inf;
mi=inf;
for(int j=1;j<=m;j++)mi=min(mi,f[i][j]);
if(mi==inf){puts("0");writel(cnt);return 0;}
cnt+=(bool)(top[i]^(m+1));
}
puts("1");writel(mi);
return 0;
}
计数dp
P6146 [USACO20FEB] Help Yourself G
设 \(f_i\) 表示取到前 \(i\) 条线段的答案
考虑两种情况:
- 不选:答案不变 \(f_{i-1}\)
- 选:与前面连上 答案还是 \(f_{i-1}\) 没连上的情况一定是 满足所有线段右端点都在这条线段左边 的所有子集 为 \(2^x\)
\(x\) 前缀和求一下即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
const int N=1e6+5;
const int M=1e5+5;
const int mod=1e9+7;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,f[N],sum[N],ans;
struct node{
int l,r;
friend bool operator<(node a,node b){return a.l<b.l;}
}a[N];
inl int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]={read(),read()};
for(int i=1;i<=n;i++)sum[a[i].r]++;
for(int i=1;i<=(n<<1);i++)sum[i]+=sum[i-1];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)f[i]=((f[i-1]<<1)+qpow(2,sum[a[i].l-1]))%mod;
writel(f[n]);
return 0;
}
数位dp
题意:给出一个区间 \(L\) ~ \(R\) ,求 \(L\) 到 \(R\) 区间内每个数的数字和,如123这个数的数字和为1+2+3=6。
实际上就是在求 \(L\) 到 \(R\) 区间内 \(1\) ~ \(9\) 出现次数 * 每个数字的值 \([l,r]\) 答案可以用 \([1,r]-[1,l-1]\) 求出
数位dp即可
(这题可以不管前导0 因为0对答案无贡献)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=1e9+7;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,l,r,f[22][2][22][12],num[12],ans;
int dfs(int pos,int lim,int sum,int x){
if(!pos)return sum;
if(~f[pos][lim][sum][x])return f[pos][lim][sum][x];
int ans=0;
for(int i=0;i<=9;i++){
if(lim&&i>num[pos])break;
ans=(ans+dfs(pos-1,lim&&(i==num[pos]),sum+(i==x),x))%mod;
}
return f[pos][lim][sum][x]=ans;
}
inl int solve(int x,int k){
int pos=0;
memset(f,-1,sizeof(f));
while(x){
num[++pos]=x%10;
x/=10;
}
return dfs(pos,1,0,k);
}
signed main(){
t=read();
while(t--){
l=read();r=read(),ans=0;
for(int i=1;i<=9;i++)
ans=(ans+((solve(r,i)-solve(l-1,i)+mod)%mod)*i%mod)%mod;
writel(ans);
}
return 0;
}
考虑在状态里存一个pst 表示上一位的数是啥
剩下的就一样了 ans记得初始化
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=1e9+7;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,l,r,f[12][2][2][22],num[12],ans;
int dfs(int pos,int lim,int zero,int pst){
if(!pos)return !zero;
if(~f[pos][lim][zero][pst])return f[pos][lim][zero][pst];
int ans=0;
for(int i=0;i<=9;i++){
if(lim&&i>num[pos])break;
if(!zero&&abs(i-pst)<2)continue;
ans+=dfs(pos-1,lim&&(i==num[pos]),zero&&!i,i);
}
return f[pos][lim][zero][pst]=ans;
}
inl int solve(int x){
int pos=0;
while(x){
num[++pos]=x%10;
x/=10;
}
memset(f,-1,sizeof f);
return dfs(pos,1,1,20);
}
signed main(){
l=read();r=read();
writel(solve(r)-solve(l-1));
return 0;
}
设 \(f_i\) 表示 \(sum(i)\) 的数量 那么等价与 求 \(\prod^{n}_{i=1}i^{f_i}\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=10000007;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,f[55][2][55][55],num[55],ans=1;
inl int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int dfs(int pos,int lim,int sum,int x){
if(!pos)return sum==x;
if(~f[pos][lim][sum][x])return f[pos][lim][sum][x];
int ans=0;
for(int i=0;i<=1;i++){
if(lim&&i>num[pos])break;
ans+=dfs(pos-1,lim&&(i==num[pos]),sum+i,x);
}
return f[pos][lim][sum][x]=ans;
}
inl int solve(int x){
int pos=0;
while(x){
num[++pos]=x&1;
x>>=1;
}
memset(f,-1,sizeof(f));
for(int i=1;i<=pos;i++)
ans=ans*qpow(i,dfs(pos,1,0,i))%mod;
return ans;
}
signed main(){
n=read();
writel(solve(n));
return 0;
}