zoukankan      html  css  js  c++  java
  • 区间和序列上的dp

    区间上的dp状态设计最基本的形式:

    (F[i])表示以i结尾的最优值或方案数。
    (F[i][k])表示以i结尾附加信息为k的最优值或方案数。
    当然可以有多维附加信息。
    转移的话往往是枚举上一个断点。
    (F[i]=max { F[j]+ w(j+1,i) | j是一个满足转移条件的断点})
    另一个很常见的是:$ f[i][j]$前i个位置分成j段/选出j个的最优值。
    这是最简单的一类序列上的dp

    bzoj1003

    有m个码头和e条航线,每天航线有成本。有连续n天需要从1号码头到m号码头运输货物。每个码头会在某些天数区间内不许经过。每更换一次运输路线,要付出k的成本。
    求这n天的最小总成本。
    m<=20, n<=100
    SOLUTION:
    其实就是分成很多段,每一段选同一个运输路线,然后得到一个最优的划分方案,使得成本最小。
    f[i]表示前i天的运输最小成本。
    (f[i]=min{ f[j]+k+w(j+1,i)*(i-j) | j<i })
    其中w(x,y)表示最短的在第x天到第y天都能用的路线长度;

    处理方法:

    首先枚举所有的x,y,然后利用最短路算法(这里dijkstra)算出x~y这些天都可以满足的1到m的最短路径;(计算方法:首先数组$use[i][j] $记录第i个点在第j天是否可以使用,在dijkstra时传入x和y,提前预处理,用数组used[i]记录在第x天到第y天是否可以使用码头i,在更新时如果遇到不能使用的点,直接continue掉即可)

    复杂度O(N^2 * m * log(m)) 然后数据范围很小嘛所以可以过√

    CODE:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #include<queue>
    #define pr pair<int,int>
    #define mk make_pair
    
    using namespace std;
    
    inline int read(){
    	int ans=0;
    	char last=' ',ch=getchar();
    	while(ch>'9'||ch<'0') last=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    	if(last=='-') ans=-ans;
    	return ans;
    }
    
    const int inf=2147483647;
    struct node{
    	int to,dis,nxt;
    }e[500];
    int ecnt,head[25];
    void add(int from,int to,int dis){
    	++ecnt;
    	e[ecnt].to=to;
    	e[ecnt].dis=dis;
    	e[ecnt].nxt=head[from];
    	head[from]=ecnt;
    }
    int n,m,k,E;
    int d;
    long long wei[110][110];
    bool use[25][110];
    void hs(int p,int f,int t){//记录第p个码头在第f天到第t天不能用
    	for(int i=f;i<=t;i++)
    		use[p][i]=1;
    }
    bool vis[25],used[25];
    int dis[25];
    priority_queue<pr,vector<pr>,greater<pr> > q;
    long long dijkstra(int x,int y){
    	while(!q.empty()) q.pop();
    	memset(used,0,sizeof(used));
    	memset(vis,0,sizeof(vis));
    	q.push(mk(0,1));
    	for(int i=1;i<=m;i++)
    		dis[i]=inf;
    	for(int i=x;i<=y;i++)
    		for(int j=1;j<=m;j++)
    			if(use[j][i]) used[j]=1;//预处理x~y天不能用的码头,标记为1;
    	dis[1]=0;
    
    	while(!q.empty()){
    		int u=q.top().second;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u]=1;
    		for(int i=head[u],v,w;i;i=e[i].nxt){
    			v=e[i].to;w=e[i].dis;
    			if(used[v]) continue;//遇到不能用的码头,continue;
    			if(dis[v]>dis[u]+w){
    				dis[v]=dis[u]+w;
    				q.push(mk(dis[v],v));
    			}
    		}
    	}
    	return dis[m];
    }
    
    long long f[110];
    
    void dp(){//计算前n天从1=>m码头的最短花费
        //显然最短花费是
    	for(int i=1;i<=n;i++){
    		f[i]=(long long)wei[1][i]*i;
    		for(int j=1;j<i;j++){
    			f[i]=min(f[i],f[j]+k+wei[j+1][i]*(i-j));
    		}
    	}
    }
    
    int main(){
    	n=read();m=read();
    	k=read();E=read();
    	for(int i=1,u,v,w;i<=E;i++){
    		u=read();
    		v=read();
    		w=read();
    		add(u,v,w);
    		add(v,u,w);
    	}
    	d=read();
    	for(int i=1,p,a,b;i<=d;i++){
    		p=read();
    		a=read();
    		b=read();
    		hs(p,a,b);
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			wei[i][j]=dijkstra(i,j);
    	dp();
    	cout<<f[n]<<endl;
    	return 0;
    }
    

    bzoj1296 粉刷匠

    有n条木板要被粉刷,每条木板分为m个格子,每个格子需要被刷成蓝色或红色。

    每次粉刷可以在一条木板上给连续的一段格子刷上相同的颜色。每个格子最多被刷一次。

    问若只能刷k次,最多正确粉刷多少格子。

    n,m<=50, k<=2500

    如果只有一条木板,那么设(g[i][j])表示前i个格子刷j次的最多正确格子

    然后枚举一个k,表示前k个格子刷了j-1次,第k+1到第i个格子刷一次的情况,这样依次枚举求出(g[i][j])的最大值

    [g[i][j]=max{ g[k][j-1]+w(k+1,i) | k<i } ]

    w(x,y)为第x到第y个格子的最多同色格子数,哪个颜色出现的多刷哪个,直接记一个前缀和即可。???对怎么处理w(x,y)表示疑惑???

    大概是这个意思叭:

    for(int i=1;i<=m;i++){
        if(color[i]=='0') {
            Sumr[i]=Sumr[i-1]+1;
        	Sumb[i]=Sumb[i-1];
        }
        else {
            Sumb[i]=Sumb[i-1]+1;
            Sumr[i]=Sumr[i-1];
        }
    }
    w[i][j]=max(Sumb[j]-Sumb[i-1],Sumr[j]-Sumr[i-1]);
    

    有多条木板,设(f[i][j])表示前i个木板刷j次的最大答案。

    (f[i][j]=Max{ f[i-1][k]+g_i[m][j-k] | k<=j })

    也就是需要先处理出每一块木板的g数组;


    然后叭,其实这是一个有TLE的代码,但(o_2)优化,你值得拥有!

    基本上都是上面↑讲到的式子叭,但是有几个要注意的点:

    1. 处理g数组时,枚举k要从0开始枚举;
    2. (g[i][j][k])在k!=0时,初值为1;k=0时初值为0;
    #include<bits/stdc++.h>
    
    using namespace std;
    
    inline int read(){
    	int ans=0;
    	char last=' ',ch=getchar();
    	while(ch>'9'||ch<'0') last=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    	if(last=='-') ans=-ans;
    	return ans;
    }
    
    int n,m,t;
    int g[55][55][2505];
    char color[60];
    int Sumr[55],Sumb[55];
    int f[55][2505];
    
    void hs(int n){
    	for(int i=1;i<=m;i++)
    		for(int j=1;j<=t;j++){
    			g[n][i][j]=1;
    			for(int k=0;k<i;k++)
    				g[n][i][j]=max(g[n][i][j],g[n][k][j-1]+max(Sumr[i]-Sumr[k],Sumb[i]-Sumb[k]));
    		}
    }
    
    void clear(){
    	for(int i=0;i<=m;i++) Sumr[i]=Sumb[i]=0;
    }
    
    int main(){
    	n=read();m=read();t=read();
    	for(int i=1;i<=n;i++) {
    		scanf("%s",color+1);
    		clear();
    		for(int j=1;j<=m;j++){
    			if(color[j]=='1') {
    				Sumr[j]=Sumr[j-1];
    				Sumb[j]=Sumb[j-1]+1;
    			}
    			else {
    				Sumr[j]=Sumr[j-1]+1;
    				Sumb[j]=Sumb[j-1];
    			}
    		}
    		hs(i);
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=0;j<=t;j++)
    			for(int k=0;k<=j;k++)
    				f[i][j]=max(f[i][j],f[i-1][k]+g[i][m][j-k]);
    				
    	cout<<f[n][t]<<endl;
    	return 0;
    }
    

    括号序列模型及解法

    给定一个长度为n的仅包含左右括号和问号的字符串,将问号变成左括号或右括号使得该括号序列合法,求方案总数。
    例如(())与()()都是合法的括号序列。
    n<=3000。

    然后反正没找到例题叭,所以没有办法验证对错了,只能先写写看;

    然鹅并没有写对啊,太难过了

    只能先复制一下zhhx的solution了(溜

    (dp[i][j])表示当前到第i个字符,现在还有j个左括号。

    那么分三种情况考虑。

    1. 若第i+1个字符是左括号,则能转移到(dp[i+1][j+1])
    2. 若第i+1个字符是右括号,则能转移到(dp[i+1][j-1])
    3. 若第i+1个字符是问号,则能转移到(dp[i+1][j-1])(dp[i+1][j+1])

    最终(dp[n][0])就是方案总数啦。
    时间复杂度为(O(n^2))

    首先感谢小蒟蒻皮皮鱼的友情代码(真的自己写枯了,果然还是太弱了

    思维上确实不如神仙皮皮鱼

    神仙皮皮鱼把这道题出成了.jpg

    U86873 小Y的精灵国机房之旅

    #include<bits/stdc++.h>
    
    using namespace std;
    
    inline int read() {
    	int ans=0;
    	char last=' ',ch=getchar();
    	while(ch>'9'||ch<'0') last=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    	if(last=='-') ans=-ans;
    	return ans;
    }
    const int mxn=10010;
    const int mod=1000000007;
    int n;
    char c[mxn];
    int f[3][10010];
    
    int main(){
    	n=read();
    	scanf("%s",c+1);
    	f[0][0]=1;
    	for(int i=1;i<=n;i++) {
    		for(int j=i;j>=0;j--) {
    			f[i&1][j]=0;
    			if(c[i]=='Y') {
    				if(j>0)
    					f[i&1][j]=f[(i-1)&1][j-1]%mod;
    			}
    			if(c[i]=='H') {
    				f[i&1][j]=f[(i-1)&1][j+1]%mod;
    			}
    			if(c[i]=='C') { 
    				f[i&1][j]=f[(i-1)&1][j+1]%mod;
    				if(j>0) 
    					f[i&1][j]=(f[i&1][j]+f[(i-1)&1][j-1])%mod;
    			}
    		}
    	}
    	printf("%d",f[n&1][0]%mod);
    	return 0;
    }
    

    BZOJ3709

    在一款电脑游戏中,你需要打败n只怪物(从1到n编号)。为了打败第i只怪物,你需要消耗d[i]点生命值,但怪物死后会掉落血药,使你恢复a[i]点生命值。任何时候你的生命值都不能降到0(或0以下)。

    请问是否存在一种打怪顺序,使得你可以打完这n只怪物而不死掉。

    N<=10^5

    这个题是不是蜜汁眼熟?

    没错!它就是lyd之前讲贪心讲到的那个题:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #define ll long long
    
    using namespace std;
    
    inline int read(){
        int ans=0;
        char last=' ',ch=getchar();
        while(ch>'9'||ch<'0') last=ch,ch=getchar();
        while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
        if(last=='-') ans=-ans;
        return ans;
    }
    
    int n,x,y,upc,downc;
    ll z;
    struct node{
        int a,d,id;
    }up[100005],down[100005];
    
    bool cmp1(node i,node j){
        return i.d<j.d;
    }
    
    bool cmp2(node i,node j){
        return i.a>j.a;
    }
    
    int main(){
        n=read();scanf("%lld",&z);
        for(int i=1;i<=n;i++){
            x=read();y=read();
            if(x>y) down[++downc].a=y,down[downc].d=x,down[downc].id=i;
            else up[++upc].a=y,up[upc].d=x,up[upc].id=i;
        }
        sort(up+1,up+upc+1,cmp1);
        sort(down+1,down+downc+1,cmp2);
        for(int i=1;i<=upc;i++){
            z-=up[i].d;
            if(z<=0){
                printf("NIE");
                            return 0;
            }
            z+=up[i].a;
        }
        for(int i=1;i<=downc;i++){
            z-=down[i].d;
            if(z<=0) {
                printf("NIE");
                            return 0;
            }
            z+=down[i].a;
        }
        printf("TAK
    ");
        for(int i=1;i<=upc;i++)printf("%d ",up[i].id);
        for(int i=1;i<=downc;i++) printf("%d ",down[i].id);
        return 0;
    }
    

    bzoj4922

    给出一些括号序列,要求选择一些括号序列拼接成一个合法的括号序列,使得总长最大。

    1<=n<=300,表示括号序列的个数

    括号序列的长度len不超过300.

    这是一道调了3天的题(我太难了)

    首先对于已经配对的括号,我们不必再去考虑,因此可以先将其删掉:

    举个例子:

    ))()(),显然对于子串"()()",我们可以不考虑它。

    那么最后消成的,一定是这样的序列:))…)((…((,左端为右括号,右端为左括号

    因此我们可以先将这一部分处理掉:

    //用x来记录没有消掉的右括号的数量,y记录没有消掉的左括号的数量
    /*结构体struct node{
    	int l,r,len;
    	//l表示这个序列的左端右括号数
    	//r表示这个序列的右端左括号数
    	//len表示这段序列的长度
    }a[mx]*/
    for(int i=1,len,x,y;i<=n;i++){
            scanf("%s",s+1);
            x=y=0;
            len=strlen(s+1);
            sum+=len;//记录整个字符串共有多长
            a[i].len=len;
            for(int i=1;i<=len;i++) {
                if(s[i]=='(') y++;
                else y?y--:x++;
            }
            a[i].l=x;
            a[i].r=y;
        }
    

    然后回到了一个很熟悉的题目:

    按照这道题的贪心思路,我们把左括号看成+1,右括号看成-1,首先肯定是要考虑左边右括号小于右边左括号的,因此将左括号数量>右括号数量的排在前面,右括号数量>左括号数量的排在后面;

    然后对于每一部分,我们又应该怎么排列呢?

    对于左括号数量>右括号数量的,我们按照右括号数量,右括号越少,排的位置越靠前。

    对于左括号数量<右括号数量的,我们按照左括号数量,左括号越多,排的位置越靠前。

    bool cmp(node x,node y){
        if((x.l<x.r)&&(y.l<y.r)) return x.l<y.l;//如果比较的两个字符串都是左括号数>右括号数,比较右括号数量,右括号少的在前;
        if((x.l<x.r)&&(y.l<y.r)) return x.l<x.r;//如果比较的两个字符串中有一个满足左括号数>右括号数,另一个不满足(两个都满足的情况会在上面return掉),那么我们看一下x是否满足右括号数<左括号数,如果满足,显然x在前y在后,反之。
        return x.r>y.r;//最后剩下的是左括号数<右括号数的,也就按照左括号数从大到小排序;
    }
    
    int main(){
        ……
        sort(a+1,a+n+1,cmp);
    	……
        return 0;
    }
    

    按照这样排序之后,我们进行dp:

    (f[i][j])表示前i个字符串,左右括号之和为j(左+1右-1)时的长度最长是多少w?

    然后考虑转移:

    对于(f[i][j])如果不选第i个子串:(f[i][j]<=f[i-1][j])

    如果选择第i个子串:(f[i][j]<=f[i-1][j-a[i].r+a[i].l]+a[i].len)

    因此,(f[i][j])最终结果是在两者中取max:(f[i][j]=max(f[i-1][j],f[i-1][j-a[i].r+a[i].l]+a[i].len);)

    然后注意考虑(j-a[i].r+a[i].l>=0)

    (CODE:)

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #define ll long long
    
    using namespace std;
    
    inline int read(){
        int ans=0;
        char last=' ',ch=getchar();
        while(ch>'9'||ch<'0') last=ch,ch=getchar();
        while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
        if(last=='-') ans=-ans;
        return ans;
    }
    
    int n,B,A,ans;
    char s[310];
    struct node{
        int l/*left )*/,r/*right (*/,len;
    }a[310];
    
    int f[310][90001];
    
    bool cmp(node x,node y){
       if((x.l<x.r)&&(y.l<y.r)) return x.l<y.l;
       if((x.l<x.r)||(y.l<y.r)) return x.l<x.r;
       return x.r>y.r;
    }
    
    int main(){
        n=read();
        //x:right brackets
        //y:left brackets
        int sum=0;
        for(int i=1,len,x,y;i<=n;i++){
            scanf("%s",s+1);
            x=y=0;
            len=strlen(s+1);
            sum+=len;
            a[i].len=len;
            for(int i=1;i<=len;i++) {
                if(s[i]=='(') y++;
                else y?y--:x++;
            }
            a[i].l=x;
            a[i].r=y;
        }
        
        sort(a+1,a+n+1,cmp);
        memset(f,200,sizeof(f));
        f[0][0]=0;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=sum;j++){
                f[i][j]=f[i-1][j];
                if(i==n&&a[i].r!=0&&j==0) continue;
                if(j+a[i].l-a[i].r>=0)
                f[i][j]=max(f[i][j],f[i-1][j+a[i].l-a[i].r]+a[i].len);
            }
        }
        printf("%d",f[n][0]);
        return 0;
    }
    
  • 相关阅读:
    HashMap源码分析和应用实例的介绍
    Map不同具体实现类的比较和应用场景的分析
    Set集合架构和常用实现类的源码分析以及实例应用
    深入理解JVM(7)——类加载器
    深入理解JVM(5)——HotSpot垃圾收集器详解
    PoolManager 简单使用
    HDU4786_Fibonacci Tree
    UVA11653_Buses
    UVA11625_Lines of Containers
    HDU3507_Print Article
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11454484.html
Copyright © 2011-2022 走看看