目录
动态规划的优化
状态压缩
[COGS217][USACO Open05] Disease Management
N 头奶牛*有D 种疾病,选择性地给一些奶牛挤奶,使得挤出牛奶中的疾病总数不超过K 种,求最多能给多少奶牛挤奶。
1≤N≤100,1≤D≤15,1≤K≤D
#include<iostream>
#include<cstdio>
using namespace std;
int N,D,K;
int f[1<<15+5];
int ill[1005];bool ok[1<<15+5];
int main()
{
freopen("disease.in","r",stdin);
freopen("disease.out","w",stdout);
int i,j,x,y;scanf("%d%d%d",&N,&D,&K);
for(i=1;i<=N;i++)
{
scanf("%d",&x);
for(j=1;j<=x;j++)
{
scanf("%d",&y);
ill[i]+=1<<(y-1);
}
}
int tot=(1<<D)-1;
for(i=0;i<=tot;i++)
{
int icnt=0;
for(j=i;j;j>>=1)
if(j&1) icnt++;
if(icnt<=K) ok[i]=true;
}
for(i=1;i<=N;i++)
for(j=tot;j>=0;j--)
f[j|ill[i]]=max(f[j|ill[i]],f[j]+1);
int ans=0;
for(i=0;i<=tot;i++)
if(ok[i]) ans=max(ans,f[i]);
printf("%d\n",ans);
return 0;
}
数位动规
区间满足条件的整数个数
数位动规能解决一类形如求一个区间内满足条件的整数个数一类经典问题。
一般的做法是:记录
数位动规十分易懂且好写(所以说不经常出现),唯一的难点就在于前导零的处理,看三道题感受一下吧。
[HDU2098]不要 62
给定一个数字范围
[l,r] ,问满足不含有4 且不含有连续的两个数62 的数字的数量。
r≤106
这道题处理前导零可以在初始化 DP 数组时允许有前导零,然后在计算答案时对每一位分别统计,对一位只统计其整的部分(其中就包括了位数少的情况,因为之前算进去了),零的部分放到后面计算。
这样的做法虽然简单,但适用范围十分狭窄。
#include<iostream>
#include<cstdio>
#include<cstring>
int f[10][10];
inline void initDp()
{
int i,j,k;
f[0][0]=1;
for(i=1;i<=7;i++)
for(j=0;j<=9;j++)
for(k=0;k<=9;k++)
if(j!=4&&!(j==6&&k==2))
f[i][j]+=f[i-1][k];
}
int num[10];
int solve(int x)
{
memset(num,0,sizeof(num));
int res=0,len=0;
while(x) num[++len]=x%10,n/=10;
for(i=len;i>=1;i--)
{
for(j=0;j<num[i];j++)
if(!(num[i+1]==6&&j=2))
ans+=f[i][j];
if(num[i]==4||(num[i+1]==6&&num[i]=2)) break;
}
return res;
}
int main()
{
int i;
int n,m;
initDp();
while(scanf("%d%d",&n,&m)&&n&&m)
printf("%d\n",solve(m+1)-solve(n));
return 0;
}
[BZOJ1026][SCOI2009]windy数
给定一个区间
[l,r] ,求满足相邻两个数字只差最小为2 的数的个数。
r≤2×109
由于这道题我们不能像
斜率优化
[BZOJ1010][HNOI2008] 玩具装箱toy
由题意,易得如下状态转移方程:
令
则
证明决策单调性
假设在状态
i 处的k 决策优于j 决策,即
f[k]+(g[i]−g[k]−L)2≤f[j]+(g[i]−g[j]−L)2 则对于
i 后的所有状态t ,要证明决策单调性即
f[k]+(g[t]−g[k]−L)2f[k]+(g[i]+v−g[k]−L)2f[k]+(g[i]−g[k]−L)2+2v⋅(g[i]−g[k]−c)+v22v⋅(g[i]−g[k]−c)≤≤≤≤f[j]+(g[t]−g[j]−L)2f[j]+(g[i]+v−g[j]−L)2f[j]+(g[i]−g[j]−c)2+2v⋅(g[i]−g[j]−L)+v22v⋅(g[i]−g[j]−L)
即证明g[k]≥g[j] (显然)证明完毕
那么接下来就要求斜率方程
加入决策
满足
/************************************************************** Problem: 1010 User: zhangche0526 Language: C++ Result: Accepted Time:176 ms Memory:2464 kb ****************************************************************/
#include<iostream>
#include<cstdio>
typedef long long ll;
const int MAXN=5e4+5;
ll n,L;
ll f[MAXN],g[MAXN],que[MAXN];
inline double calSlop(int j,int k)
{return (f[k]-f[j]+(g[k]+L)*(g[k]+L)-(g[j]+L)*(g[j]+L))/(2.0*(g[k]-g[j]));}
int main()
{
int i,j;
scanf("%lld%lld",&n,&L);L++;
for(i=1;i<=n;i++) scanf("%lld",&g[i]);
for(i=2;i<=n;i++) g[i]+=g[i-1];
for(i=1;i<=n;i++) g[i]+=i;
int hd,tl;
hd=1,tl=0;que[++tl]=0;
for(i=1;i<=n;i++)
{
while(hd<tl&&calSlop(que[hd],que[hd+1])<=g[i]) hd++;
j=que[hd];
f[i]=f[j]+(g[i]-g[j]-L)*(g[i]-g[j]-L);
while(hd<tl&&calSlop(que[tl],i)<calSlop(que[tl-1],que[tl])) tl--;
que[++tl]=i;
}
printf("%lld\n",f[n]);
return 0;
}
矩阵快速幂优化
有时候我们会遇到一些状态转移方程是递推形式的动态规划问题,而数据范围很大时,我们使用朴素的递推方法会超时,这时我么你考虑用矩阵快速幂的方法将
[BZOJ1009][HNOI2008]GT考试
给出一个长度为
M 的十进制数字串,求在一个长度为N 的十进制数字串中有多少种不出现长度为M 的那个串(对K 取模),两个串中均允许出现前导零。
N≤109,M≤20,K≤103
这道题要是不看数据范围还感觉是数位动规,但
按顺序处理准考证号每一位,设
那么答案
- f[i][j]表示的每种方案不仅与其后j位有关,还应保证不含不吉利数
- 为避免重复,
f[i][j] 表示的每种方案都不含长度大于j且与不吉利数的前缀相同的后缀(否则就会出现:从1 到m 标号,不吉利数为123124 时,f[i][2] 计数的方案包含f[i][5] 计数的方案的情况)
状态转移:
比如:还是假设不吉利数为123124,那么 f[i][3]=f[i-1][2]+f[i-1][5],因为 f[i-1][2]末尾的*****12不能是**12312,所以需要f[i-1][5]补充
但若不吉利数为123123,那么 f[i][3]=f[i-1][2],因为 f[i][3]末尾的*****123不能是**123123
其中,
/**************************************************************
Problem: 1009
User: zhangche0526
Language: C++
Result: Accepted
Time:60 ms
Memory:1288 kb
****************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
const int MAXL=25;
int N,M,P;
struct Mt{int m[MAXL][MAXL];Mt(){memset(m,0,sizeof(m));}};
Mt operator *(Mt a,Mt b)
{
int i,j,k;
Mt res;
for(i=0;i<M;i++)
for(j=0;j<M;j++)
for(k=0;k<M;k++)
res.m[i][j]=(res.m[i][j]+a.m[i][k]*b.m[k][j])%P;
return res;
}
Mt qpow(Mt a,int n)
{
int i;
Mt res;
for(i=0;i<M;i++) res.m[i][i]=1;
for(i=1;i<=N;i<<=1,a=a*a)
if(i&n) res=res*a;
return res;
}
int next[MAXL];
char s[MAXL];
int main()
{
int i,j;
scanf("%d%d%d",&N,&M,&P);
scanf("%s",s+1);
for(i=2,j=0;i<=M;i++)
{
while(j&&s[j+1]!=s[i]) j=next[j];
if(s[j+1]==s[i]) j++;
next[i]=j;
}
Mt dp;
for(i=0;i<M;i++)
for(j=0;j<=9;j++)
{
int t=i;
while(t&&s[t+1]-'0'!=j)
t=next[t];
if(s[t+1]-'0'==j) t++;
if(t!=M) dp.m[t][i]=(dp.m[t][i]+1)%P;
}
Mt res=qpow(dp,N);
int ans=0;
for(i=0;i<M;i++) ans=(ans+res.m[i][0])%P;
printf("%d\n",ans);
return 0;
}