• 区间dp专题练习


    区间dp专题练习

    题意

    1.Equal Sum Partitions

    ? 这嘛东西,\(n^2\)自己写去

    \[\ \]

    \[\ \]

    2.You Are the One

    感觉自己智力被吊打

    \(dp[i][j]\)表示 , 对于当前的一个空栈 , \(i\)\(j\)这一段都出栈的最小花费

    显然是长得一副区间(诡)dp(异)的样子 , 如何转移呢?(建议自己想想吧)

    对于一个\(dp[i][j]\),因为这个\(i\)必须是最先入栈的 , 所以我们可以枚举它的出栈时间\(k\) , 那么总贡献就是

    \[dp[i+1][k]+(k-i)*a[i]+sum[k+1..j] \cdot (k-i+1) \]

    即先出栈的\(i+1...k\)的贡献和+i自己的贡献+\(k+1..j\)这一段的贡献和它们被推迟\(k-i+1\)位产生的贡献

    rep(i,1,n) rep(j,i+1,n) dp[i][j]=1e9;
    rep(i,1,n) dp[i][i]=a[i];
    drep(i,n,1) 
        rep(j,i,n) 
    	    rep(k,i,j) 
    		    dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+(k-i)*a[i]+(k-i+1)*(s[j]-s[k]));
    

    \[\ \]

    \[\ \]

    3.Groups

    where is my head?

    每个人的决策其实就是把一群人分成一组,所以可以直接一个个区间接上去

    细节:满足条件的人大于\(R-L+1\)时要取\(min\)

    区间dp?

    rep(i,1,n) a[i]=rd(),b[i]=rd();
    rep(i,0,n) rep(j,0,n) c[i][j]=0;
    rep(i,0,n) dp[i]=0;
    rep(i,1,n) c[a[i]][n-b[i]]++;
    rep(i,0,n-1) rep(j,i+1,n) dp[j]=max(dp[j],dp[i]+min(j-i,c[i][j]));
    printf("%d\n",dp[n]);
    

    4.Sky Soldiers

    又是假区间dp

    const int N=1010;
    const double eps=1e-6;
    
    int n,m;
    map <int,double> M;
    typedef map<int,double> ::iterator iter;
    int p[N];
    double x[N];
    int cnt;
    double dp[N][51];//前i个,放j个站
    double sum[N][N];
    
    
    int main(){
        while(~scanf("%d%d",&n,&m)&&n&&m) {
            M.clear();
            rep(i,1,n) {
                rep(j,1,rd()) {
                    int p=rd();
                    double x;scanf("%lf",&x);
                    M[p]+=x;
                }
            }
            cnt=0;
            for(iter it=M.begin();it!=M.end();++it) {
                p[++cnt]=it->first;
                x[cnt]=it->second;
            }//暴力离散
            double ans=1e11;
            rep(i,0,cnt) rep(j,0,m) dp[i][j]=1e11;
            rep(i,1,cnt) rep(j,1,cnt) sum[i][j]=sum[i][j-1]+x[j]*abs(p[i]-p[j]);//预处理便于算贡献
            rep(i,1,cnt) dp[i][1]=sum[i][i];
            rep(i,1,cnt) {
                rep(j,1,m-1) if(dp[i][j]<1e10) {
                    rep(k,i+1,cnt) {
                        int mid=(p[i]+p[k])>>1;
                        mid=lower_bound(p+1,p+cnt+1,mid)-p;
                        double s=min(sum[i][mid]-sum[i][i]+sum[k][k]-sum[k][mid],sum[i][mid-1]-sum[i][i]+sum[k][k]-sum[k][mid-1]);//贡献
                        dp[k][j+1]=min(dp[k][j+1],dp[i][j]+s);
                    }
                }
            }
            rep(i,1,cnt) ans=min(ans,dp[i][m]+sum[i][cnt]-sum[i][i]);
            printf("%.2lf\n",ans);
        }
    }
    

    \[\ \]

    \[\ \]

    5.Play Game

    假区间dp again

    \(dp[l1][r1][l2][r2]\)表示两个序列分别还剩下\(l1,r1\),\(l2,r2\)的答案 , 很基础但是有细节

    int n,m;
    int a[N],b[N];
    int dp[N][N][N][N];
    int sum1[N],sum2[N];
    
    int main(){
    	rep(kase,1,rd()) {
    		n=rd();
    		rep(i,1,n) sum1[i]=a[i]=rd(),sum1[i]+=sum1[i-1];
    		rep(i,1,n) sum2[i]=b[i]=rd(),sum2[i]+=sum2[i-1];
    		memset(dp,0,sizeof dp);
    		drep(l1,n+1,1) rep(r1,l1-1,n) drep(l2,n+1,1) rep(r2,l2-1,n) {
    			if(l1>r1&&l2>r2) continue;
    			int s=1e9;
    			if(l1<=r1) s=min(s,min(dp[l1+1][r1][l2][r2],dp[l1][r1-1][l2][r2]));
    			if(l2<=r2) s=min(s,min(dp[l1][r1][l2+1][r2],dp[l1][r1][l2][r2-1]));
    			dp[l1][r1][l2][r2]=sum1[r1]-sum1[l1-1]+sum2[r2]-sum2[l2-1]-s;
    		}
    		printf("%d\n",dp[1][n][1][n]);
    	}
    }
    
    

    \[\ \]

    \[\ \]

    6.Two Rabbits

    这个鬼题是真的惊了

    首先肯定要接两倍数组断成链

    然后我们分析题目性质,对于任意路径,互为重合逆子序列时最优

    即跳跃路径为原序列的一个回文子序列时一定更优

    若两只兔子的路径错开,那么一定可以向两边继续延伸

    细节:一个点时回文长度为1,当跳跃总长小于n时答案要加一

    int n,m;
    int a[N];
    int dp[N][N];
    
    int main(){
        srand(618);
        while(~scanf("%d",&n)&&n){
            rep(i,1,n) a[i]=a[i+n]=rd();
            int ans=0;
            memset(dp,0,sizeof dp);
            rep(l,1,n) drep(i,n*2-l+1,1) {
                int j=i+l-1;
                if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2-(i==j);
                else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
                ans=max(ans,dp[i][j]+(l<n));
            }
            printf("%d\n",ans);
        }
    }
    

    \[\ \]

    \[\ \]

    7.Dire Wolf

    这个题长得还是比较区间dp的

    表示自己的转移错得离谱都过掉了。。。

    正确的转移方法和T2有点类似,即枚举最后一个杀死的狼,加上前后直接转移

    int n,m;
    int a[N],b[N];
    int dp[N][N];
    
    int main(){
    	rep(kase,1,rd()){
    		rep(i,1,n=rd()) a[i]=rd();
    		rep(i,1,n) b[i]=rd();b[n+1]=a[n+1]=0;
    		//memset(dp,10,sizeof dp);
    		drep(i,n,1) rep(j,i,n) {
    			dp[i][j]=1e9;
    			if(i==j) dp[i][j]=a[i]+b[i-1]+b[i+1];
    			else rep(k,i,j) dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
              //第k只狼最后杀死,贡献分开算
    		}
    		printf("Case #%d: %d\n",kase,dp[1][n]);
    	}
    }
    
    

    \[\ \]

    \[\ \]

    8.Dylans loves sequence

    这个题令我不想说话

    
    int n,m;
    int a[N],b[N],cnt;
    int s[N],ans[N][N];
    void Add(int p,int x){
    	while(p<=n) s[p]+=x,p+=p&-p;
    }
    int Que(int p){
    	int res=0;
    	while(p) res+=s[p],p-=p&-p;
    	return res;
    }
    
    int main(){
    	n=rd(),m=rd();
    	rep(i,1,n) a[i]=b[i]=rd();
    	sort(b+1,b+n+1),cnt=unique(b+1,b+n+1)-b-1;
    	rep(i,1,n) a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
    	rep(l,1,n) {
    		memset(s,0,sizeof s);
    		rep(r,l,n) {
    			ans[l][r]=ans[l][r-1]+r-l-Que(a[r]);
    			Add(a[r],1);
    		}
    	}
    	rep(i,1,m) {
    		int x=rd(),y=rd();
    		printf("%d\n",ans[x][y]);
    	}
    }
    
    
    

    9.Expression

    首先要理解题意

    所以符号是任意安排顺序,每个符号相当于合并两个数

    所以总方案数应该是\((n-1)!\)

    转移是也要注意,应该乘上左右两个区间符号交叉出现的价值

    将区间\([i,k];[k+1,j]\)合并至\([i,j]\)时要乘上的贡献是

    \[\frac{(j-i-1)!}{(k-i)!*(j-k-1)!} \]

    (因为固定了第\(k\)个符号最后,所以是\(j-i-1\))

    int n,m;
    int a[N],opt[N];
    ll g[N][N];
    ll fac[N]={1},Inv[N]={1,1};
    
    int main(){
    	rep(i,1,N-1) fac[i]=fac[i-1]*i%P;
    	rep(i,2,N-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
    	rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P;
    	while(~scanf("%d",&n)) {
    		rep(i,1,n) a[i]=rd();
    		rep(i,1,n-1) {
    			char ch;
    			do ch=getchar();
    			while(ch!='+'&&ch!='-'&&ch!='*');
    			if(ch=='+') opt[i]=1;
    			else if(ch=='-') opt[i]=2;
    			else opt[i]=3;
    		}
    		memset(g,0,sizeof g);
    		drep(i,n,1) rep(j,i,n) {
    			if(i==j) g[i][i]=a[i];//边界条件
    			rep(k,i,j-1) {
                    ll t=Inv[k-i]%P*Inv[j-k-1]%P*fac[j-i-1]%P;
    				if(opt[k]==1) {
    					g[i][j]=(g[i][j]+(fac[k-i]*g[k+1][j]%P+fac[j-k-1]*g[i][k]%P)*t)%P;
    				} else if(opt[k]==2) {
    					g[i][j]=(g[i][j]+(-1ll*fac[k-i]*g[k+1][j]%P+1ll*fac[j-k-1]*g[i][k]%P)*t)%P;
    				} else {
    					g[i][j]=(g[i][j]+g[i][k]*g[k+1][j]%P*t)%P;
    				}
    			}
    		}
    		printf("%lld\n",(g[1][n]%P+P)%P);//可能有负数
    	}
    }
    
    

    \[\ \]

    \[\ \]

    10.QSC and Master

    众所周知我是一个擅长乱搞的人,所以这个奇怪的题我也是乱搞搞过去的

    它长得并不是一个标准的区间dp

    两种决策:

    直接从左右端点向两边扩展 ;

    将这段区间切开

    注意由于求得是整个序列的答案所以最后还要合并一下

    int n,m;
    int a[N],b[N];
    
    ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
    ll dp[N][N];
    
    int main(){
        rep(kase,1,rd()){
            n=rd();
            rep(i,1,n) a[i]=rd();
            rep(i,1,n) b[i]=rd();
            memset(dp,-63,sizeof dp);
            drep(i,n,1) rep(j,i+1,n) {
                if(j-i==1) {
                    if(gcd(a[i],a[j])!=1) dp[i][j]=b[i]+b[j];
                    continue;
                }
                if(gcd(a[i],a[j])!=1) dp[i][j]=max(dp[i][j],dp[i+1][j-1]+b[i]+b[j]);//决策一
                rep(k,i,j) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);//决策二
            }
            ll ans=0;
            rep(i,1,n) {
                dp[1][i]=max(dp[1][i],0ll);
                rep(j,1,i) dp[1][i]=max(dp[1][i],max(0ll,dp[1][j])+max(0ll,dp[j+1][i]));
            }//合并答案
            rep(i,1,n) rep(j,i,n) ans=max(ans,dp[i][j]);
            printf("%lld\n",ans);
        }
    }
    

    \[\ \]

    \[\ \]

    11.Kirinriki

    跑错的尺取

    const int N=5100,P=1e9+7;
    
    
    int n,m;
    char s[N];
    int dp[N];
    
    int main(){
    	rep(kase,1,rd()){
    		m=rd();
    		scanf("%s",s+1);n=strlen(s+1);
    		int ans=0;
    		rep(i,3,n*2-1) {
    			reg int cnt=0,p=1;
    			drep(j,i/2-(!(i&1)),1) {
    				if(i-j>n) break;
    				cnt++;
    				dp[cnt]=dp[cnt-1]+abs(s[j]-s[i-j]);
    				while(dp[cnt]-dp[p-1]>m) p++;
    				ans=max(ans,cnt-p+1);
    			}
    		}
    		printf("%d\n",ans);
    	}
    }
    
    

    \[\ \]

    \[\ \]

    12.Zuma

    这个题没有素质!它卡常!

    \(dp[i][j][0...4]\)表示

    0:消完了

    1:还剩1个0

    2:还剩2个0

    3:还剩1个1

    4:还剩2个1

    转移极其繁琐

    卡常技巧:相邻相同的可以压成一个块

    (希望我没有想错吧)

    const int N=410;
    #define chk(a,b) (a>b&&(a=b))//卡常
    int n,m;
    char s[N];
    int a[N],b[N],c[N],dp[N][N][5];
    //0 --> 0
    //1 --> one 0
    //2 --> two 0
    //3 --> one 1
    //4 --> two 1
    int main(){
    	int T; scanf("%d",&T);
    	rep(kase,1,T){
    		scanf("%s",s+1);
    		n=strlen(s+1);
    		rep(i,1,n) a[i]=s[i]^'0';
    		int _n=0;
    		rep(i,1,n) if(i==1||a[i]!=a[i-1]) {
    			_n++;
    			b[_n]=1; c[_n]=a[i];
    		} else b[_n]++,b[_n]=b[_n];//块合并
    		n=_n;
    		rep(i,1,n) rep(j,1,n) rep(o,0,4) dp[i][j][o]=1e9;
    		rep(i,1,n) {
    			dp[i][i][c[i]*2+b[i]]=0;
    			dp[i][i][0]=3-b[i];
    		}//边界条件初始化
    		drep(i,n,1) rep(j,i+1,n) {
    			rep(k,i,j-1){
    				reg int *x=dp[i][k],*y=dp[k+1][j];//指针卡常
    				chk(dp[i][j][0],x[0]+y[0]);
    				chk(dp[i][j][0],x[1]+y[2]);
    				chk(dp[i][j][0],x[2]+y[2]);
    				chk(dp[i][j][0],x[2]+y[1]);
    				chk(dp[i][j][0],x[3]+y[4]);
    				chk(dp[i][j][0],x[4]+y[3]);
    				chk(dp[i][j][0],x[4]+y[4]);
    
    				chk(dp[i][j][1],x[0]+y[1]);
    				chk(dp[i][j][1],x[1]+y[0]);
    				chk(dp[i][j][2],x[1]+y[1]);
    				chk(dp[i][j][2],x[0]+y[2]);
    				chk(dp[i][j][2],x[2]+y[0]);
    
    				chk(dp[i][j][3],x[0]+y[3]);
    				chk(dp[i][j][3],x[3]+y[0]);
    				chk(dp[i][j][4],x[3]+y[3]);
    				chk(dp[i][j][4],x[0]+y[4]);
    				chk(dp[i][j][4],x[4]+y[0]);
    			}
    			chk(dp[i][j][0],dp[i][j][1]+2);
    			chk(dp[i][j][0],dp[i][j][2]+1);
    			chk(dp[i][j][0],dp[i][j][3]+2);
    			chk(dp[i][j][0],dp[i][j][4]+1);
    		}
    		printf("Case #%d: %d\n",kase,dp[1][n][0]);
    	}
    }
    
    

    (题解写到一半突然dalao来质问我,发现这种做法好像是错的。。。,但还不清楚是不是)

    如果发现有错误请写评论

  • 相关阅读:
    Flask中路由系统、Flask的参数及app的配置
    linux之master和minion
    linux之docker学习
    项目的发布(nginx、uwsgi、django、virtualenv、supervisor)
    Linux下安装和使用nginx
    linux下主从同步和redis的用法
    论图像识别的预处理技术
    图像技术分析 图像编辑器核心技术
    C++ Primer 第九章 顺序容器
    图像灰度化公式 颜色空间用途说明
  • 原文地址:https://www.cnblogs.com/chasedeath/p/11331120.html
Copyright © 2020-2023  润新知