zoukankan      html  css  js  c++  java
  • 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
    ",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] brace \ f[i][j]=max_{i≤k<j}lbrace max(f[i][k]*f[k+1][j],g[i][k]*g[k+1][j]) brace 操作为乘 ]

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

    [g[i][j]=min_{i≤k<j}lbrace g[i][k]+g[k+1][j] brace\ 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] brace 操作为乘 ]

    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颜色相同。

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

    所以,转移方程为:

    [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
    ",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_{yin son[x],v≥p≥0}lbrace {f[x,v-p]+f[y,p]} brace ]

    但是由于当前课程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以前写过就不再写了。

    Here

  • 相关阅读:
    【Yii2.0】1.5 Yii2.0新特性记录
    【PHP7.0】PHP7.0 小知识点摘录
    【PHP7.0】PHP7.0学习笔记目录
    【Yii2.0】1.4 Apache2.4.23基于主机名的虚拟主机配置
    【Yii2.0】2.2 Yii2.0 Basic代码中路由链接被转义的处理
    【Yii2.0】1.3 MySQL5.7.15修改root密码
    [Leetcode 106] 130 Surrounded Regions
    [Leetcode 105] 90 Subsets II
    [Leetcode 104] 131 Palindrome Partitioning
    [Leetcode 103] 37 Sudoku Solver
  • 原文地址:https://www.cnblogs.com/Bhllx/p/10959105.html
Copyright © 2011-2022 走看看