0x52~0x54
0x52 背包问题
a.[√] coins
sol:
这是一道多重背包模板题,但是常规的二进制优化过不了。单调队列优化是可以的。
这里需要一个更加简单的方法。
注意到本题只要关心是否存在,所以可以考虑设(f[x])表示x能否被表示出来。
那么对于硬币i,考虑如果存在(f[x-a[i]]=1),那么(f[x]=1)。
但是由于有硬币数量的限定,所以可以用一个(cnt[x])来记录构成x只是需要多少枚硬币i。
只要不超过限定,就可以继续转移。
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
const int N=101;
const int M=1e5+1;
int n,m,ans,a[N],c[N],f[M],cnt[M];
int main()
{
while(scanf("%d%d",&n,&m)!=EOF&&n&&m) {
RG int i,j;
for(i=1;i<=n;++i) scanf("%d",&a[i]);
for(i=1;i<=n;++i) scanf("%d",&c[i]);
memset(f,0,sizeof(f));
for(i=1,ans=0,f[0]=1;i<=n;++i) {
memset(cnt,0,sizeof(cnt));
for(j=a[i];j<=m;++j) {
if(!f[j]&&f[j-a[i]]&&cnt[j-a[i]]<c[i])
f[j]=1,cnt[j]=cnt[j-a[i]]+1,++ans;
}
}
printf("%d
",ans);
}
return 0;
}
0x53 区间DP
b.[√]Polygon
sol:
首先任选一条边断掉之后,可以发现是一个比较明显的区间DP。
那么分别令(f[l,r],g[l,r])表示把从l到r合并后的最大值和最小值。
对于最大值而言,由于当操作符为乘号时需要注意两个很小的负数相乘可能会更大,所以转移应该为:
对于最小值而言,由于当操作符为乘号时需要注意一正一负相乘可能会更小,所以转移应为:
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define int long long
#define DB double
using namespace std;
const int N=111;
const int inf=0x3f3f3f3f;
char inp[3];
int n,ans,a[N],op[N],f[N][N],g[N][N];
signed main()
{
RG int i,j,k;
scanf("%lld",&n);
for(i=1;i<=n<<1;++i) {
if(i&1) {
scanf("%s",inp);
if(inp[0]=='t') op[i+1>>1]=1;
else op[i+1>>1]=2;
}
else scanf("%lld",&a[i>>1]);
}
for(i=1;i<=n;++i) a[n+i]=a[i],op[n+i]=op[i];
memset(f,0xcf,sizeof(f));
memset(g,0x3f,sizeof(g));
for(i=1;i<=n<<1;++i) f[i][i]=g[i][i]=a[i];
for(i=n<<1;i>=1;--i)
for(j=i+1;j<=i+n-1&&j<=n<<1;++j)
for(k=i;k<j;++k) {
if(op[k+1]==1) {
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]);
}
else {
f[i][j]=max(f[i][j],max(f[i][k]*f[k+1][j],g[i][k]*g[k+1][j]));
g[i][j]=min(g[i][j],min(g[i][k]*g[k+1][j],min(g[i][k]*f[k+1][j],f[i][k]*g[k+1][j])));
}
}
for(i=1,ans=-inf;i<=n;++i)
ans=max(ans,f[i][i+n-1]);
printf("%lld
",ans);
for(i=1;i<=n;++i)
if(f[i][i+n-1]==ans) printf("%lld ",i);
return putchar('
'),0;
}
c.[√]金字塔
sol:
首先要考虑到区间DP,令(f[l,r])表示从l到r的序列能够组成的树的方案数。
那么容易想到把子树分成两部分然后相乘得到结果。
但是这样会算重复,因为把一种划分方案前后调转顺序是有可能得到另一种划分方案的,而实际上这二者一致。
怎样保证不会出现重复的呢?
考虑到 如果存在一颗子树发生了变化,那么这棵树必然是一颗新的树。
所以,不妨把l~r分成l+1~k-1作为一颗子树,k~r作为树的剩余部分(一个根+若干子树),其中k与l颜色相同。
这种情况下,枚举出的那棵子树不断变大,不会重复,把二者相乘即可。
所以,转移方程为:
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int mod=1e9;
char s[303];
LL n,f[303][303];
LL solve(int l,int r) {
if(l>r||s[l]!=s[r]) return 0;
if(l==r) return f[l][r]=1;
if(f[l][r]!=-1) return f[l][r];
f[l][r]=solve(l+1,r-1);
RG int i;
for(i=l+2;i<=r-2;++i)
if(s[l]==s[i]) f[l][r]=(f[l][r]+solve(l+1,i-1)*solve(i,r)%mod)%mod;
return f[l][r];
}
int main()
{
scanf("%s",s+1);
n=1ll*strlen(s+1);
memset(f,-1,sizeof(f));
printf("%lld
",solve(1,n));
return 0;
}
0x54 树形DP
d.[√]选课
sol:
注意到所有的课程构成森林,不方便转移。
所以不妨另设一个0号节点,使其成为一颗有根树。
那么令(f[x,v])表示到了x号课程,总共选v个的最优学分。
考虑一次枚举x的每一个儿子y,那么当前的最优值必然由y子树中的一部分和之前扫过的子树中的一部分构成。
可以发现,实际上这两个值都可以说是已知的,一部分是(f[y,p]),另一部分是(f[x,v-p])。(y子树中选p门课)
所以转移方程应为:
但是由于当前课程x(除0号节点外)是必选的,所以应该有:(f[x,v]=f[x][v-1]+a[x],vin(0,m]) 。
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=303;
int n,m,tot,a[N],f[N][N],head[N];
struct EDGE{int next,to;}e[N<<1];
IL void make(int x,int y) {e[++tot]=(EDGE){head[x],y},head[x]=tot;}
void DP(int x) {
RG int i,j,k,y;
for(i=head[x];i;i=e[i].next) {
DP(y=e[i].to);
for(j=m;j>=0;--j) //必须倒序
for(k=0;k<=j;++k) //此处似乎正倒序都可以
f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
}
if(!x) return;
for(i=m;i>0;--i) f[x][i]=f[x][i-1]+a[x];
}
int main()
{
RG int i,x;
n=gi(),m=gi();
for(i=1;i<=n;++i) x=gi(),a[i]=gi(),make(x,i);
DP(0);
printf("%d
",f[0][m]);
return 0;
}
e.[√]Accumulation Degree
sol:
简单的sol以前写过就不再写了。