0x52~0x54

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\n",ans);
	}
    return 0;
}

0x53 区间DP

b.[√]Polygon

题目传送

sol:

首先任选一条边断掉之后,可以发现是一个比较明显的区间DP。

那么分别令\(f[l,r],g[l,r]\)表示把从l到r合并后的最大值和最小值。

对于最大值而言,由于当操作符为乘号时需要注意两个很小的负数相乘可能会更大,所以转移应该为:

\[f[i][j]=max_{i≤k<j}\lbrace f[i][k]+f[k+1][j]\rbrace \\ f[i][j]=max_{i≤k<j}\lbrace max(f[i][k]*f[k+1][j],g[i][k]*g[k+1][j])\rbrace\ 操作为乘 \]

对于最小值而言,由于当操作符为乘号时需要注意一正一负相乘可能会更小,所以转移应为:

\[g[i][j]=min_{i≤k<j}\lbrace g[i][k]+g[k+1][j]\rbrace\\ g[i][j]=min_{i≤k<j}\lbrace g[i][k]*g[k+1][j],g[i][k]*f[k+1][j],f[i][k]*g[k+1][j]\rbrace\ 操作为乘 \]

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\n",ans);
	for(i=1;i<=n;++i)
		if(f[i][i+n-1]==ans) printf("%lld ",i);
	return putchar('\n'),0;
}

c.[√]金字塔

题目传送

sol:

首先要考虑到区间DP,令\(f[l,r]\)表示从l到r的序列能够组成的树的方案数。

那么容易想到把子树分成两部分然后相乘得到结果。

但是这样会算重复,因为把一种划分方案前后调转顺序是有可能得到另一种划分方案的,而实际上这二者一致。

怎样保证不会出现重复的呢?

考虑到 如果存在一颗子树发生了变化,那么这棵树必然是一颗新的树。

所以,不妨把l~r分成l+1~k-1作为一颗子树,k~r作为树的剩余部分(一个根+若干子树),其中k与l颜色相同。

这种情况下,枚举出的那棵子树不断变大,不会重复,把二者相乘即可。

所以,转移方程为:

\[f[l,r]=f[l+1,r-1]+\sum_{l+2≤k≤r-2,k和l同色}{f[l+1,k-1]*f[k,r]}\ (l和r同色) \]

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\n",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门课)

所以转移方程应为:

\[f[x,v]=max_{y\in son[x],v≥p≥0}\lbrace {f[x,v-p]+f[y,p]} \rbrace \]

但是由于当前课程x(除0号节点外)是必选的,所以应该有:\(f[x,v]=f[x][v-1]+a[x],v\in(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\n",f[0][m]);
    return 0;
}

e.[√]Accumulation Degree

题目传送

sol:

简单的sol以前写过就不再写了。

Here

posted @ 2019-06-01 11:59  薄荷凉了夏  阅读(497)  评论(0编辑  收藏  举报