zoukankan      html  css  js  c++  java
  • 集训之各种dp

    1.线性

    「BZOJ1609」麻烦的聚餐

    分别求一遍连续非下降/上升子序列长度,用总长减去,取最小值即可,主要(O(n^2))优化

    Code

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int maxn=3e5+5;
    typedef long long ll;
    int n,f[maxn],a[maxn],top,ans;
    int main(){
    //	freopen("1.in","r",stdin);
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    	f[++top]=a[1];
    	for(int i=2;i<=n;++i){
    		if(a[i]>=f[top]){
    			f[++top]=a[i];
    			continue;
    		}
    		int x=upper_bound(f+1,f+1+top,a[i])-f;
    		f[x]=a[i];
    	}
    	ans=n-top;
    	f[top=1]=a[n];
    	for(int i=n-1;i>=1;--i){
    		if(a[i]>=f[top]){
    			f[++top]=a[i];
    			continue;
    		}
    		int x=upper_bound(f+1,f+1+top,a[i])-f;
    		f[x]=a[i];
    	}
    	printf("%d
    ",min(ans,n-top));
    	return 0;
    }
    
    

    「P2066」机器分配

    (f[i][j])表示i个公司分j台机器所得的最大利润

    转移方程:(f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]),1<=i<=n,1<=j<=m,0<=k<=j)

    Code

    
    
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int maxn=20;
    int f[maxn][maxn],a[maxn][maxn],n,m,ans,xx,shu[maxn];
    void B(int x,int y){
      if (x==0) return;
      for (int k=0;k<=y;k++){
      	if (ans==f[x-1][k]+a[x][y-k]){
            ans=f[x-1][k];
      		B(x-1,k);
    		printf("%d %d
    ",x,y-k);
            break;
         }
      }
    }
    int main(){
    //	freopen("1.in","r",stdin);
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			scanf("%d",&a[i][j]);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j){
    			for(int k=0;k<=j;++k){
    			f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);
    		}
    	}
    	printf("%d
    ",f[n][m]);
    	ans=f[n][m];
    	B(n,m);
    	return 0;
    }
    

    2.树形

    没有上司的舞会

    相邻节点不能在一起,0表示不参加,1表示参加。
    主要要搞清0/1分别由谁转移,详见代码,不赘述。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int maxn=6000+5;
    typedef long long ll;
    struct Edge{int next,to;}e[maxn*2];
    int n,a[maxn],f[maxn][2],cnt,x,y,head[maxn];
    void Add(int x,int y){
    	e[++cnt].to=y;
    	e[cnt].next=head[x];
    	head[x]=cnt;
    }
    void dfs(int u,int fa){
    	f[u][1]=a[u];
    	int xx=0;
    	for(int i=head[u];i;i=e[i].next){
    		int v=e[i].to;
    		if(v==fa)continue;
    		dfs(v,u);
    		f[u][1]=max(f[u][1],f[v][0]+a[u]);
    		xx+=max(f[v][1],f[v][0]);
    	}
    	f[u][0]=xx;
    }
    int main(){
    //	freopen("1.in","r",stdin);
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    	for(int i=1;i<n;++i)scanf("%d%d",&x,&y),Add(x,y),Add(y,x);
    	scanf("%d%d",&x,&y);
    	dfs(1,0);
    	printf("%d
    ",max(f[1][1],f[1][0]));
    	return 0;
    }
    

    小胖守皇宫

    题目就是说,每相邻的两个节点必须有一个人守着,并有一定的花费,给你个节点花费,求花费最小。
    0表示儿子守着,1是自己守,2是父亲守。
    那个tot变量有些迷,待更新吧,树形的和后面的状压还得持续更新

    Code

    
    
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int maxn=1500+5,Inf=0x3f3f3f3f;
    typedef long long ll;
    struct Edge{int next,to;}e[maxn*2];
    int n,a[maxn],f[maxn][3],cnt,x,m,k,head[maxn];
    void Add(int x,int y){
    	e[++cnt].to=y;
    	e[cnt].next=head[x];
    	head[x]=cnt;
    }
    void dfs(int u,int fa){
    	f[u][1]=a[u];
    	int tot=Inf;
    	for(int i=head[u];i;i=e[i].next){
    		int v=e[i].to;
    		if(v==fa)continue;
    		dfs(v,u);
    		f[u][1]+=min(f[v][0],min(f[v][1],f[v][2]));
    		f[u][0]+=min(f[v][1],f[v][0]);
    		f[u][2]+=min(f[v][1],f[v][0]);
    		tot=min(tot,f[v][1]-f[v][0]);
    	}
    	if(tot>0)f[u][0]+=tot;
    }
    int main(){
    	//freopen("1.in","r",stdin);
    	scanf("%d",&n);
    	int aa;
    	for(int i=1;i<=n;++i){
    		scanf("%d%d%d",&k,&aa,&m);
    		a[k]=aa;
    		while(m--){
    			scanf("%d",&x);
    			Add(k,x);Add(x,k);
    		}
    	}
    	if(n==1)printf("%d
    ",a[1]);
    	else{
    		dfs(1,0);
    		printf("%d
    ",min(f[1][0],f[1][1]));
    	}
    	return 0;
    }
    
    

    3.区间

    整数划分

    和乘积最大那道题很像,不过更复杂一点,要输出。
    f[i][j]表示前i位划分为m部分的最大乘积。(就这个部分关键要想到)
    预处理出任意i~j位的数a[i][j]
    转移:(f[i][j]=max(f[i][j],f[k][j-1]*a[k+1][i]),j-1<=k<=i-1)
    边界:f[0][0]=1(因为是相乘,所以为1不能为0)
    转移时记录前i位划分为j段的最后一个“*”前面那位数,递归输出。

    Code

    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    typedef long long ll;
    const int maxn=23;
    ll a[maxn][maxn],f[maxn][maxn];
    int t,hua[maxn][maxn],m,n,len;
    char s[maxn];
    void P(int x,int y){
    	if(y==0)return;
    	P(hua[x][y],y-1);
    	for(int i=hua[x][y]+1;i<=x;++i)printf("%c",s[i]);
    	printf(" ");
    }
    void C(){
    	memset(f,0,sizeof f);
    	memset(a,0,sizeof a);
    	memset(hua,0,sizeof hua);
    	memset(s,0,sizeof s);
    }
    void R(){
    	scanf(" %s %d",s+1,&m);
    	n=strlen(s+1),m=min(m,n);
    	for(int i=1;i<=n;++i)
    		for(int j=i;j<=n;++j) a[i][j]=a[i][j-1]*10LL+s[j]-'0';
    	memset(f,-1,sizeof f);
    	f[0][0]=1;
    }
    void Solve(){
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=min(i,m);++j)
    			for(int k=j-1;k<=i-1;++k)
    				if(f[i][j]<f[k][j-1]*a[k+1][i]) f[i][j]=f[k][j-1]*a[k+1][i],hua[i][j]=k;
    }
    void Pr(){
    	printf("%lld
    ",f[n][m]);
    	P(n,m);
    	printf("
    ");
    }
    int main(){//可怜的主函数
    	//freopen("1.in","r",stdin);
    	scanf("%d",&t);
    	while(t--){
    		C();R();
    		Solve();
    		Pr();
    	}
    	return 0;
    }
    

    矩阵连乘

    题意是什么鬼我看不懂......
    解释一下:
    可以这么理解:对于三个连续的矩阵,分两步求,再将这两步结果相加。

    A:2 * 3 B:3 * 4 C:4 * 5

    (A * B) * C=(2 * 3 * 4)+(2 * 4 * 5)

    A * ( B * C)=(3 * 4 * 5)+(2 * 3 * 5)

    f[i][j]表示第i个到第j个的矩阵乘积最小运算量

    转移:(f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]),i<=k<j)

    边界:(f[i][j]=Inf,f[i][i]=0,1<=i<=n)

    Code

    
    
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    const int maxn=105,Inf=0x7f7f7f7f;
    int n,f[maxn][maxn],a[maxn];
    int main(){
    //	freopen("1.in","r",stdin);
    	memset(f,Inf,sizeof f);
    	scanf("%d",&n);
    	for(int i=0;i<=n;++i)scanf("%d
    ",&a[i]);
    	for(int i=1;i<=n;++i)f[i][i]=0;
    	for(int l=2;l<=n;++l)
    		for(int i=1,j;i+l-1<=n;++i){
    			j=i+l-1;
    			for(int k=i;k<j;++k) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]);
    		}
    	printf("%d
    ",f[1][n]);
    	return 0;
    }
    
    

    凸多边形的三角剖分

    也是区间的套路,由简单衍生出来

    先顺时针给各个顶点编上号,(f[i][j])表示连接i号顶点和j号顶点,所形成的下面的一个多边形的最小剖分值。

    转移:(f[i][j]=min(f[i][k]+f[k][j]+a[i] * a[k] * a[j]),i+1<=k<=j-1)

    边界:初始化(f[i][i+2]=a[i] * a[i+1] * a[i+2],(1<=i<=n-2))

    Code

    
    
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=50+5;
    int n;
    ll a[maxn],f[maxn][maxn];
    int main(){
    	//freopen("1.in","r",stdin);
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)scanf("%lld
    ",&a[i]);
    	for(int i=1;i+2<=n;++i)f[i][i+2]=a[i]*a[i+1]*a[i+2];
    	for(int d=3;d<=n;++d){
    		for(int i=1;i+d-1<=n;++i){
    			int j=i+d-1;
    			for(int k=i+1;k<j;++k){
    				if(f[i][j])f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[k]*a[i]*a[j]);
    				else f[i][j]=f[i][k]+f[k][j]+a[k]*a[i]*a[j];
    			}
    		}
    	}
    	printf("%lld
    ",f[1][n]);
    	return 0;
    }
    
    
    

    多边形

    李煜东的《算法进阶指南》P284~286
    我就不再解释啦

    Code

    
    
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <iostream>
    #include <cstring>
    using namespace std;
    const int maxn=50+5,Inf=1e9+10;
    int n,data[maxn<<1];
    int f[maxn<<1][maxn<<1][3];
    int ff[maxn<<1];
    char c[maxn<<1]; 
    int q,w,e,r;
    int ans_point=(-1)*Inf;
    void Read(){
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>c[i]>>data[i];
            c[i+n]=c[i];
            data[i+n]=data[i];
        }
    }
    void Solve(){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=(n<<1);++j){
                for(int k=1;k<=(n<<1);++k){
                    f[j][k][1]=(-1)*Inf;
                    f[j][k][2]=Inf;
                }
            }
            for(int j=1;j<=(n<<1);++j){
                f[j][j][1]=f[j][j][2]=data[j];
                if(c[j+1]=='t')  f[j][j+1][1]=f[j][j+1][2]=data[j]+data[j+1];
                else f[j][j+1][1]=f[j][j+1][2]=data[j]*data[j+1];
            }
            for(int l=2;l<=n;++l){
                for(int j=i;j+l<=i+n;++j){
                    int k=j+l-1;
                    for(int x=j;x<k;++x){
                        if(c[x+1]=='t'){
                            f[j][k][1]=max(f[j][k][1],f[j][x][1]+f[x+1][k][1]);
                            f[j][k][2]=min(f[j][k][2],f[j][x][2]+f[x+1][k][2]);
                        }else{
                            q=f[j][x][1]*f[x+1][k][1];
                            w=f[j][x][2]*f[x+1][k][2];
                            e=f[j][x][1]*f[x+1][k][2];
                            r=f[j][x][2]*f[x+1][k][1];
                            f[j][k][1]=max(f[j][k][1],max(max(q,w),max(e,r)));
                            f[j][k][2]=min(f[j][k][2],min(min(q,w),min(e,r)));
                        }
                    }
                }
            }
            ff[i]=f[i][i+n-1][1];
        }
        for(int i=1;i<=n;++i){
            ans_point=max(ans_point,ff[i]);
        }
        printf("%d
    ",ans_point);
        for(int i=1;i<=n;++i){
            if(ans_point==ff[i]) printf("%d ",i);
        }
    }
    int main(){
    //	freopen("1.in","r",stdin);
        Read();
        Solve();
        return 0;
    }
    
    

    低价回文

    (soda)的博客吧
    我的Code

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    const int maxn=2e3+5;
    int f[maxn][maxn],n,m,v[30];
    char s[maxn],ss;
    int main(){
    //	freopen("1.in","r",stdin);
    	scanf("%d%d %s",&n,&m,s+1);
    	for(int i=1,x,y;i<=m;++i)scanf(" %c%d%d",&ss,&x,&y),v[ss-'a']=min(x,y);
    	for(int i=m;i>=1;--i)
    		for(int j=i+1;j<=m;++j){
    			if(s[i]==s[j])f[i][j]=f[i+1][j-1];
    			else f[i][j]=min(f[i+1][j]+v[s[i]-'a'],f[i][j-1]+v[s[j]-'a']);
    		}
    	printf("%d
    ",f[1][m]);
    	return 0;
    }
    

    4.状压

    终于到了状压dp了,说实话我的状压dp真的不好,前几天集训时遇到状压的题我都不会,前两天没有时间,今天不能再拖了,总结一下。

    互不侵犯

    (N * N)的棋盘里面放(K)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共(8)个格子。

    给你N和K的值,求方案数

    (1<=N<=9,0<=K<=N * N).
    我们用(f[i][S][k])表示前i行放k个国王,且第i行状态为S时的方案数。状态0是不放国王,1是放国王。
    边界是(f[0][0][0]=1),这样才能第一层转移到底层时为1(也可以直接将第一整行全预处理出来,方案数都为1)
    转移是比较简单的,不需要常数了:(f[i][S][k]+=f[i-1][s][k-cnt]),(本行由上一行转移过来)这个k是S中1的个数,不一定是总数K,s是上一行的状态,与S不能冲突,(不能互相攻击)。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn=1<<9;
    int n,m;
    ll f[10][maxn][82];
    int lowbit(int x){return x&-x;}
    int Q(int x){//求x的二进制中1的个数
    	int cnt=0;
    	for(int i=x;i;i-=lowbit(i))cnt++;
    	return cnt;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	f[0][0][0]=1;
    	int maxs=1<<n;
    	for(int i=1;i<=n;++i){
    		for(int S=0;S<maxs;++S){
    			int cnt=Q(S);
    			if(S&(S<<1))continue;
    			for(int s=0;s<maxs;++s){//s是上一行的状态枚举,与内层的k的枚举可以互换,但这样更省时间,因为特判会省去一些冗余循环
    				if(S&s||(S<<1)&s||(S>>1)&s)continue;
    				for(int k=cnt;k<=m;++k) f[i][S][k]+=f[i-1][s][k-cnt];
    			}
    		}
    	}
    	ll ans=0;
    	for(int S=0;S<maxs;++S)ans+=f[n][S][m];//最后结果是最后一行放k个国王的各种状态所对应的方案数之和
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    炮兵阵地

    这个题目和上个差不多,除了不能相互攻击外,还有些地方不能放,最后问你的是最多的个数而不是方案数了。
    (f[i][S][s])是前(i)行中,第(i)行状态是(S)(i-1)行状态是(s)时的个数。
    转移是枚举本行状态(S),算出该状态下本行的个数(cnt),用上一行的(f[i-1][s][ss])再加上(cnt),得到(i)行状态为(S),上一行状态为(s)时的最大个数。
    即:(f[[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt)).
    其中(ss)是&i-2&行的状态,也需要枚举。另外本题有个优化,因为炮兵左右攻击范围挺大(2格),所以,尽管一行有M<=10个格子,但真正合法的状态数不超过60个,我们可以提前将1行的合法的状态编上号预处理出来,时间/空间复杂度都可减少,空间可减少大概八九十倍的样子。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int n,m,tot;
    const int maxn=1<<9;
    int a[101],st[70],f[101][70][70];
    char s[15];
    bool J(int x){
    	if(x&(x<<1)||x&(x<<2))return 0;
    	return 1;
    }
    int Q(int x){
    	int cnt=0;
    	for(int i=st[x];i;i-=(i&-i))cnt++;
    	return cnt;
    }
    int main(){
    	scanf("%d%d", &n, &m);
        for(int i=1; i<=n; i++){
            scanf(" %s",s);
            for(int j=0; j<m; j++){
                if(s[j] == 'H') a[i]|=(1<<j);
            }
        }
        int maxs=1<<m;
    	for(int i=0;i<maxs;++i)if(J(i))st[++tot]=i;
    	for(int i=1;i<=tot;++i)if(!(st[i]&a[1]))f[1][i][1]=Q(i);
    	for(int i=2;i<=n;++i){
    		for(int S=1;S<=tot;++S){
    			if(st[S]&a[i])continue;
    			int cnt=Q(S);
    			for(int s=1;s<=tot;++s){
    				if((st[S]&st[s])||(st[s]&a[i-1]))continue;
    				for(int ss=1;ss<=tot;++ss){
    					if((st[ss]&a[i-2])||(st[ss]&st[s])||(st[S]&st[ss]))continue;
    					f[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt);
    				}
    			}
    		}
    	}
    	int Max=0;
    	for(int i=1;i<=tot;++i)
    	for(int j=1;j<=tot;++j){
    		if((st[i]&a[n])||(st[j]&a[n-1])||(st[i]&st[j]))continue;
    		Max=max(Max,f[n][i][j]);
    	}
    	printf("%d
    ",Max);
    	return 0;
    }
    

    旅游景点 Tourist Attractions

    愤怒的小鸟

    动物园

    待更新吧......

  • 相关阅读:
    一、JQuery选择器
    二、HelloMaven-第一个Maven项目
    一、maven的简介和环境搭建
    Junit源码
    五、spring和Hibernate整合
    JS 冒泡排序从学到优化
    JS小案例(基础好烦恼少)----持续更新
    JS+PHP实现用户输入数字后取得最大的值并显示为第几个
    将博客搬至CSDN
    HTML表单相关
  • 原文地址:https://www.cnblogs.com/Lour688/p/13189649.html
Copyright © 2011-2022 走看看