坐标DP

最近打的题好多都是tj现在只能被动打tj真的好烦
先打一下最近打的坐标DP
1.矩阵取数:
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的nxm的矩阵,矩阵中的每个元素aij,均为非负整数。游戏规则如下:
1.每次取数时须从每行各取走一个元素,共n个。m次后取完矩阵所有元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分=被取走的元素值x2^i,其中i表示第i次取数(从1开始编号);
4. 游戏结束总得分为m次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入文件game.in包括n+1行: 第1行为两个用空格隔开的整数n和m。 第2-n+l行为nxm矩阵,其中每行有m个用单个空格隔开的非负整数。
输出文件game.out仅包含1行,为一个整数,即输入矩阵取数后的最大得分
题解:
首先:可以通过贪心法(累加满足贪心法)通过求解局部(即每行)的最大值累加得到全局最大值
然后,我们着手处理各行内的最大值:
显然:对于当前行取数越大越有利(贪心思想),不过由于2的累乘,较小数可以通过作为指数函数的常数通过指数爆炸达到更大值,所以贪心法假掉,考虑DP
以剩余数为阶段,以左右端点为状态,以取走当前待选数为决策
状态转移方程:f[i][j]=max(f[i][j],f[i1][j]+2(mj+i1)t[i],f[i][j+1]+2(mj+i1)t[j]);(i,j是左右端点)
再把最后的一个乘上2m次方,比较出1m中所有的f[i][i]的最大值,加入答案
最后将累加好的答案输出即可
PS:用高精度,可能会有时间上的不足
Code

矩阵取数游戏
#include <bits/stdc++.h>//矩阵取数
using namespace std;//坐标DP,需要高精度
const int P=222;//数据规模
const int mod=1e4;//压位高精度计算的万进制取模
int n,m,t[P];//定义数据
struct calc{//计算
    int p[505],len;//定义计算所用数据
    calc(){//重置操作的构造函数
        memset(p,0,sizeof(p));//重置数组
        len=0;//重置数组长度
    }
    void  print(){//结构体封装压位输出
        printf("%d",p[len]);//输出最高位无需补零特判掉
        for(int i=len-1;i>0;i--){//高位向下输出,需补零,但不用输出最高位
            if(p[i]==0){//某四位对应值全部是零
                printf("0000");//输出四个零即可
                continue;//跳过以后的操作(额外输出一个‘0’)
            }
            for(int k=10;k*p[i]<mod;k*=10){//万进制不够位数先补零(总算学会了)
                printf("0");//输出补位的零
            }
            printf("%d",p[i]);//从第一位非零数开始输出实际数字
        }
    }
}f[P][P],base[P],ans;//定义所需的结构体变量使主函数运行
calc operator +(const calc &a,const calc &b){//重载高精加
    calc c;//定义一个返回值变量,作为计算结果
    c.len=max(a.len,b.len);//确定数位
    int x=0;//确定进位
    for(int i=1;i<=c.len;i++){//高精加循环
        c.p[i]=a.p[i]+b.p[i]+x;//同位数相加,再加进位
        x=c.p[i]/mod;//向上进位(不满万向上进‘0’)
        c.p[i]%=mod;//得出进位后的值
    }
    if(x>0){//处理最高位的进位
        c.len++;//另开长度存最高位
        c.p[c.len]=x;//存进位
    }
    return c;//c作为运算结果返回
}
calc operator *(const calc &a,const int &b){//重载高精乘(低精)
    calc c;//返回值变量
    c.len=a.len;//确定长度
    int x=0;//定义进位
    for(int i=1;i<=c.len;i++){//同位相乘
        c.p[i]=a.p[i]*b+x;//每位与低精相乘,加上进位
        x=c.p[i]/mod;//向上进位
        c.p[i]%=mod;//得出进位后的实际值
    }
    while(x>0){//处理进位
        c.len++;//另开长度存最高位
        c.p[c.len]=x%mod;//存进位
        x/=mod;//向下除确定是否继续进位
    }
    return c;//作为结果返回
}
calc max(const calc &a,const calc &b){//比较函数取最大值
    if(a.len>b.len){//数位不相同:lena>lenb
        return a;//显然,a大于b,a作为比较结果返回
    }
    else if(a.len<b.len){//此时,lenb>lena
        return b;//同理,返回b
    }
    else{//数位相同
        for(int i=a.len;i;i--){//从最高位向下相比
            if(a.p[i]>b.p[i]){//a的某一位较大
                return a;//显然,a大于b,a作为比较结果返回
            }
            else if(a.p[i]<b.p[i]){//b的某一位较大
                return b;//同理,返回b
            }
        }
    }
    return a;//处理到此处未结束返回值说明a==b,返回a即可
}
void power(){//处理2的幂
    base[0].p[1]=1,base[0].len=1;//赋初值,调用高精乘低精函数,数组长初始化为1,由于相乘,高精数初始化为1
    for(int i=1;i<=m+2;i++){//预处理进程,由于最大需要2^m,先预处理到2^(m+2)
        base[i]=base[i-1]*2;//运用类似前缀和的思想完成O(m)的预处理,此处调用高精乘低精(重载)
    }
}
int main(){//函数体定义完(预处理好),开始正式处理数据
    scanf("%d%d",&n,&m);//读入
    power();//预处理2的(m+2)次幂,方便调用
    while(n--){//此题中对于整个矩阵与独立的单行来说,单行的最大值可推出整体最大值,满足贪心,在线处理数据得出最大值
        memset(f,0,sizeof(f));//初始化
        for(int i=1;i<=m;i++){//每行m个数,循环m次读入
            scanf("%d",&t[i]);//读入数据
        }
        for(int i=1;i<=m;i++){//枚举左端点
            for(int j=m;j>=1;j--){//枚举右端点
                f[i][j]=max(f[i][j],f[i-1][j]+base[m-j+i-1]*t[i-1]);//比较最优解1式
                f[i][j]=max(f[i][j],f[i][j+1]+base[m-j+i-1]*t[j+1]);//比较最优解2式
                /*状态转移方程:f[i][j]=max(f[i][j],f[i-1][j]+2*(m-j+i-1)*t[i],f[i][j+1]+2*(m-j+i-1)*t[j]);
                f[i][j]是未取时状态,1式和2式是将要取石子的状态*/
            }
        }
        calc maxi;//寻找当前最优状态加入答案
        for(int i=1;i<=m;i++){//查询m次
            maxi=max(maxi,f[i][i]+base[m]*t[i]);//以最后的f[i][i]为剩一个的状态,最后加的东西模拟“取走”的状态
        }
        ans=ans+maxi;//加入答案
    }
    ans.print();//输出
    return 0;//结束
}

2.方格取数/传纸条
此类问题是“找两条路径不交叉的路线使得权值最大”问题,那么,如何考虑使得算法实现合法的最优解?
这里说:贪心直接找出下一步可走的最大值在不考虑限制下是可行的,但是一旦考虑限制立刻假掉,因为找出来的解存在交叉路径而不合法
因此又回到了DP
显然走这一步的权值由上一步和这一步组成
而这一步的权值显然是个常数,无论走哪一种,这个常数不变,即对决策没有影响
因此这一步的决策,就是“向两个方向走的权值应取最大值”,
因此状态转移方程显而易见
当然阶段就是“已经走了k步”
所以状态转移方程:
f[k][i][j]=max(f[k][i][j],f[k1][i][j]);
f[k][i][j]=max(f[k][i][j],f[k1][i1][j]);
f[k][i][j]=max(f[k][i][j],f[k1][i][j1]);
f[k][i][j]=max(f[k][i][j],f[k1][i1][j1]);
就是这四条(当然为保证路径不交叉还要使i不等于j)
Code

方格取数
#include <bits/stdc++.h>
using namespace std;
const int p=200;
int f[p][p][p],a[p][p],m,n,t,ans;
void in(){
	scanf("%d%d",&m,&n);
	t=m+n;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
		}
	}
}
void work(){
	f[1][1][1]=a[1][1];
	for(int k=1;k<t;k++){
		for(int i=1;i<=m&&i<=k;i++){
			for(int j=1;j<=m&&j<=k;j++){
				 if (i == 1 && j == 1) continue;
				f[k][i][j]=max(f[k][i][j],f[k-1][i][j]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i][j-1]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j-1]);
				if(i==j){
					f[k][i][j]+=a[i][k-i+1];
				}
				else{
					f[k][i][j]+=a[i][k-i+1]+a[j][k-j+1];
				}
			}
		}
	}
	ans=f[t-1][m][m];
}
void out(){
	cout<<ans;
}
int main(){
	in();
	work();
	out();
	return 0;
} 
传纸条
#include <bits/stdc++.h>
using namespace std;
const int p=200;
int f[p][p][p],a[p][p],m,n,t,ans;
void in(){
	scanf("%d%d",&m,&n);
	t=m+n;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
		}
	}
}
void work(){
	f[1][1][1]=a[1][1];
	for(int k=1;k<t;k++){
		for(int i=1;i<=m&&i<=k;i++){
			for(int j=1;j<=m&&j<=k;j++){
				 if (i == 1 && j == 1) continue;
				f[k][i][j]=max(f[k][i][j],f[k-1][i][j]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i][j-1]);
				f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j-1]);
				if(i==j){
					f[k][i][j]+=a[i][k-i+1];
				}
				else{
					f[k][i][j]+=a[i][k-i+1]+a[j][k-j+1];
				}
			}
		}
	}
	ans=f[t-1][m][m];
}
void out(){
	cout<<ans;
}
int main(){
	in();
	work();
	out();
	return 0;
} 

3.花店橱窗摆放
其实这应该类似于区间DP。。。
不过都像。。。
dp[i][j]表示摆了i种花,且第i种花的位置在j的最大值,location[i][j]表示第i种花摆在j时上一种花摆在哪。
1.因为可能有负数,所以dp要初始化为负无穷,dp[0][0] = 0为边界
2.第i种花的位置必须要大于第i-1种花,所以j的范围要注意,要从i-1开始,到m-(n-i)结束
3.第三个循环的k表示上一种花的位置,这里k要升序循环,因为答案要求按照字典序

flower
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,a[101][101],dp[101][101],ans,location[101][101],x;
void locat(ll n,ll x)
{
    if(n==1)
        cout<<x<<" ";
    else
    {
        locat(n-1,location[n][x]);
        cout<<x<<" ";
    }
}
int main()
{
    memset(dp,128,sizeof(dp));//可能有负数,所以设为无穷大 
    ans = -1000000001;
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=m;j++)
            cin>>a[i][j];
    dp[0][0] = 0;//边界 
    for(int i = 1;i<=n;i++)
        for(int j = i;j<=m-(n-i);j++)//位置在j,这里j的范围要搞清 
            for(int k = i-1;k<=j-1;k++)//上一种花的位置,这里应该从小到大,因为要按照字典序 
                if(dp[i-1][k]+a[i][j]>dp[i][j])
                {
                    dp[i][j] = dp[i-1][k]+a[i][j];
                    location[i][j] = k;
                }             
    for(int i = n;i<=m;i++)
    {
        if(dp[n][i]>ans)
        {
            ans = dp[n][i];
            x = i;
        }
    }    
    cout<<ans<<endl;
    locat(n,x);
    return 0;
}

4.晴天小猪Hill
先讨论上下,再讨论同层的左右
同层考虑两遍防止解更新不及时
求最小值更新成正无穷

Hill
#include <bits/stdc++.h>
using namespace std;
const int p=1e4;
int a[p][p],f[p][p],n,ans;
inline int read(){
	int i=1,j=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-'){
			i=-1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		j=j*10+ch-'0';
		ch=getchar();
	}
	return i*j;
}
inline void in(){
	n=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			a[i][j]=read();
		}
	}
}
void work(){
	f[1][1]=a[1][1];
    for(int i=2;i<=n;i++)
    {
        for(int j=2;j<i;j++)
            f[i][j]=min(f[i-1][j],f[i-1][j-1])+a[i][j];
        f[i][1]=min(f[i-1][1],f[i-1][i-1])+a[i][1];
        f[i][i]=min(f[i-1][i-1],f[i-1][1])+a[i][i];
        for(int k=i-1;k>0;k--)
            f[i][k]=min(f[i][k],f[i][k+1]+a[i][k]);
        f[i][i]=min(f[i][i],f[i][1]+a[i][i]);
        for(int l=2;l<=i;l++)
            f[i][l]=min(f[i][l],f[i][l-1]+a[i][l]);
            f[i][1]=min(f[i][1],f[i][i]+a[i][1]);
        for(int k=i-1;k>0;k--)
            f[i][k]=min(f[i][k],f[i][k+1]+a[i][k]);
            f[i][i]=min(f[i][i],f[i][1]+a[i][i]);
        for(int l=2;l<=i;l++)
            f[i][l]=min(f[i][l],f[i][l-1]+a[i][l]);
                f[i][1]=min(f[i][1],f[i][i]+a[i][1]);
    }
	ans=f[n][1];
}
void out(){
	cout<<ans;
}
int main(){
	in();
	work();
	out();
	return 0;
}

免费馅饼
如果以第i个馅饼为最后一个接到的馅饼
f[i]表示接到了第i个馅饼后得分最大值
倒回去判断一下前面的馅饼能不能接到,如果接到更新值否则就扔了
原题需要数据结构优化一下还有离散化否则扛不住1e5或者1e9
Code

原题
#include <bits/stdc++.h>
using namespace std;
const int p=1e5+10;
struct node{
	int t;
	int p;
	int v;
	int l;
	int r; 
}a[p];
int b[p],w,n,t[p];
int lb(int x){
	return x&-x;
}
bool cmp1(node x,node y){
	return x.l>y.l;
} 
bool cmp2(node a,node b){
	return a.r<b.r;
}
void add(int x,int y){
	for(int i=x;i<p;i+=lb(i)){
		t[i]=max(t[i],y);
	}
}
int ques(int x){
	int ans=0;
	for(int i=x;i>0;i-=lb(i)){
		ans=max(ans,t[i]);
	}
	return ans;
}
void in(){
	scanf("%d%d",&w,&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&a[i].t,&a[i].p,&a[i].v);
		a[i].l=a[i].p-2*a[i].t;
		b[i]=a[i].r=a[i].p+2*a[i].t;
	}
}
void predeal(){
	sort(a+1,a+n+1,cmp2);
	sort(b+1,b+n+1);
	unique(b+1,b+n+1)-b;
	for(int i=1;i<=n;i++){
		a[i].r=lower_bound(b+1,b+n+1,a[i].r)-b;
	}
	sort(a+1,a+n+1,cmp1);
}
void work(){
	for(int i=1;i<=n;i++){
		add(a[i].r,ques(a[i].r)+a[i].v);
	}
}
void out(){
	printf("%d\n",ques(p-1));
}
int main(){
	in();
	predeal();
	work();
	out();
	return 0;
} 
弱化版
#include <bits/stdc++.h>           
const int N = 101 , T = 1113 ; using namespace std ; int w,h,score[T][N] , f[T][N],times; short int nxt[T][N]; void Init( ) {     scanf("%d%d",&w,&h);     int ti,wi,vi,ci;        while(~scanf("%d%d%d%d",&ti,&wi,&vi,&ci))     {         if((h-1)%vi==0||ti==0)         {             int t = (h-1) / vi + ti ;             times = max(times,t);             score[t][wi] += ci ;         }              } }  int dp(int ti,int wi) {     if(ti > times ) return 0 ;     if(~f[ti][wi]) return f[ti][wi] ;     int ret = 0 ; short int net = 0 ;     for(int i = -2; i <= 2 ; ++i)     {        if(wi + i < 1 || wi + i > w) continue;         int tmp = dp(ti+1,wi+i)+score[ti+1][wi+i];         if(tmp>ret) ret = tmp,net = i ;     }     f[ti][wi] = ret;nxt[ti][wi] = (short int)net;     return ret; }void dfs(int ti,int wi){     if(ti > times) return ;if(!f[ti][wi]) return ;  printf("%d\n",nxt[ti][wi]);     dfs(ti+1,wi+nxt[ti][wi]); } void Solve(){     memset(f,-1,sizeof(f)) ;     int ans = dp(0,(w+1)>>1) ;printf("%d\n",ans);     }int main( ) { Init();Solve();        return 0; }

三角蛋糕
分类讨论一下
顶点向下:上边紧挨第一个如果被啃坏返回1即可,因为上1被啃无法由上方左右转移
否则转移左右最小值+1
记搜特判:有值返回,遇n返回1,啃坏返回0
向上:同理,只不过特判需变化:如果三角形组成边返回1
然后向下和向上取最大值即可
Code

三角蛋糕
#include <cstdio>
#include <algorithm>
using namespace std;
const int p=1005;
int n,m,a[p][p],f[p][p],ans;
int main(){
	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++){
			if(a[i][j]){
				f[i][j]=1+min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]));
				ans=max(ans,f[i][j]);
			}
		}
	}
	printf("%d",ans);
	return 0;
} 
posted @   2K22  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示