2022蓝桥杯国赛B组-费用报销-(线性dp|状态dp)

F

题意:
就是给你n张票据,每个票据有个时间和价格,你可以选择一些,使得这些票据的日期任意两个差要>=k,然后让总价值最接近m,但是不能超过m。

思考:
当时比赛的时候我就定义的用到i这个票据最多能表示的数是多少。这样有个弊端,这样可以保证最大,但是可能会超过m,超过m的只能特判,所以这样不能保证最接近m。所以当这样感觉不太合适的时候,可以把总价值成为dp的状态,也就是枚举到i点的时候,为j状态是否可达。然后转移就行了。但是这样的复杂度是nnm,将近1e9。所以会超时,但是可以得大部分分。所以可以进行优化,优化的点就是这个日期只有365天,所以dp[i][j]为到第i天j状态是否可达。然后对于物品的枚举可以用个变量cnt来一直往后走,这样复杂度是n*m+365的。因为每个点只会走一次所以只会循环n次m。这样复杂度就够了。其实第一个暴力方法,只要利用好单调性这个性质,就能省去一维,和下面这种方法本质其实都是一样的。
另一种状态定义,定义dp[i][j]为用到i个,总价值不超过j的最大价值是多少。首先既然对数组的日期排序过了,所以再利用经典的线性dp的时候,当要看日期的时候就不用暴力循环一遍了,因为这个是有单调性的,所以用个指针一直指着就行了。然后更新的时候直接用idx更新就好了,因为越往后越大嘛,肯定直接用最大的idx就好了。但是第二维呢,当时我感觉这怎么能拿到最合适的呢?实际上,这就像个背包,dp[i][j] = max(dp[i][j],dp[idx][j-va[i.se]]+va[i].se就好了。就让不超过j-va[i].se他的来转移就够了。

代码:

经典的线性dp

#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define PII pair<int,int >
#define fi first
#define se second
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;

const int N = 1005;

int T,n,m,k;
PII va[N];
int res[N],sum[N];
int dp[1010][5010];

signed main()
{
	IOS;
	cin>>n>>m>>k;
	res[1] = 31,res[2] = 28,res[3] = 31,res[4] = 30;
	res[5] = 31,res[6] = 30,res[7] = 31,res[8] = 31;
	res[9] = 30,res[10] = 31,res[11] = 30,res[12] = 31;
	for(int i=1;i<=12;i++) sum[i] = sum[i-1]+res[i];
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		int now = sum[a-1]+b;
		va[i] = {now,c};
	}
	sort(va+1,va+1+n);
	int cnt = 1;
	dp[0][0] = 1;va[0].fi = -100;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++) dp[i][j] |= dp[i-1][j];
		for(int j=0;j<i;j++)
		{
			for(int t=va[i].se;t<=m;t++)
			{
				if(va[i].fi-va[j].fi>=k)
				dp[i][t] |= dp[j][t-va[i].se];
			}
		}
	}
	int maxn = 0;
	for(int i=m;i>=0;i--)
	{
		if(dp[n][i])
		maxn = max(maxn,i);
	}
	cout<<maxn;
	return 0;
}

利用好单调性:
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define PII pair<int,int >
#define fi first
#define se second
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;

const int N = 1005;

int T,n,m,k;
PII va[N];
int res[N],sum[N];
int dp[1010][5010];

signed main()
{
	IOS;
	cin>>n>>m>>k;
	res[1] = 31,res[2] = 28,res[3] = 31,res[4] = 30;
	res[5] = 31,res[6] = 30,res[7] = 31,res[8] = 31;
	res[9] = 30,res[10] = 31,res[11] = 30,res[12] = 31;
	for(int i=1;i<=12;i++) sum[i] = sum[i-1]+res[i];
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		int now = sum[a-1]+b;
		va[i] = {now,c};
	}
	sort(va+1,va+1+n);
	dp[0][0] = 1;va[0].fi = -100;
	int idx = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			dp[i][j] |= dp[i-1][j];
			while(idx<i&&va[i].fi-va[idx].fi>=k) idx++; 
			while(idx>0&&va[i].fi-va[idx].fi<k) idx--;
			if(j>=va[i].se) dp[i][j] |= dp[idx][j-va[i].se]; //从哪里来转移就省掉一维
		}
	}
	int maxn = 0;
	for(int i=m;i>=0;i--)
	{
		if(dp[n][i])
		maxn = max(maxn,i);
	}
	cout<<maxn;
	return 0;
}

优化后转化为状态dp
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define PII pair<int,int >
#define fi first
#define se second
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;

const int N = 1005;

int T,n,m,k;
PII va[N];
int res[N],sum[N];
int dp[1010][5010];

signed main()
{
	IOS;
	cin>>n>>m>>k;
	res[1] = 31,res[2] = 28,res[3] = 31,res[4] = 30;
	res[5] = 31,res[6] = 30,res[7] = 31,res[8] = 31;
	res[9] = 30,res[10] = 31,res[11] = 30,res[12] = 31;
	for(int i=1;i<=12;i++) sum[i] = sum[i-1]+res[i];
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		int now = sum[a-1]+b;
		va[i] = {now,c};
	}
	sort(va+1,va+1+n);
	int cnt = 1;
	dp[0][0] = 1;
	for(int i=1;i<=365;i++)
	{
		for(int j=0;j<=m;j++) dp[i][j] |= dp[i-1][j];
		while(cnt<=n&&va[cnt].fi==i)
		{
			for(int j=0;j<=m;j++)
			{
				if(i>=k&&j>=va[cnt].se) dp[i][j] |= dp[i-k][j-va[cnt].se];
				else if(i<k&&j==va[cnt].se) dp[i][j] |= 1;
			}
			cnt++;
		}
	}
	int maxn = 0;
	for(int i=m;i>=0;i--)
	{
		if(dp[365][i])
		maxn = max(maxn,i);
	}
	cout<<maxn;
	return 0;
}

另一种状态定义:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
		
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 1010,M = 5010;

int T,n,m,k;
PII va[N];
int dp[N][M];
int sum[N] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

signed main()
{
	IOS;
	cin>>n>>m>>k;
	for(int i=1;i<=12;i++) sum[i] += sum[i-1];
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		va[i] = {sum[a-1]+b,c};
	}
	sort(va+1,va+1+n);
	int idx = 0;
	va[0].fi = -100;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dp[i][j] = max(dp[i][j],dp[i-1][j]);
			while(idx<i&&va[i].fi-va[idx].fi>=k) idx++;
			while(idx>0&&va[i].fi-va[idx].fi<k) idx--;
			if(j>=va[i].se)
			dp[i][j] = max(dp[i][j],dp[idx][j-va[i].se]+va[i].se);
		}
	}
	cout<<dp[n][m];
	return 0;
}

总结:
多多思考哈,多想一想类似可以解决的方法和优化。一定要利用好各种性质啊,单调性之类的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
费用报销单打印HTML是指将费用报销单的内容在网页上进行排版和展示,并且可以通过浏览器直接打印出来。实现费用报销单打印HTML的步骤如下: 1. 创建HTML文件:使用HTML标记语言创建一个新的HTML文件,可以使用文本编辑器如Notepad++编写,文件扩展名为.html。 2. 设计打印内容:在HTML文件中设计费用报销单的打印内容。可以使用HTML标签如<table>、<tr>、<td>等标签来创建表格,显示费用明细和相关信息。还可以使用<title>、<h1>等标签来设置标题和段落。 3. 样式设置:使用CSS样式表对费用报销单HTML文件进行样式设置。可以使用<style>标签来定义样式,如字体、颜色、对齐等。通过定义打印样式表@media print,可以使打印时的页面排版更符合打印需求,如隐藏不必要的元素、调整布局等。 4. 添加打印功能:在HTML文件中添加打印功能。可以使用JavaScript代码添加打印按钮,并在点击按钮时调用window.print()方法实现打印功能。 5. 测试与调整:在浏览器中打开HTML文件,通过预览功能查看打印效果。根据实际情况调整HTML文件中的样式、布局等,以满足打印需求。 6. 打印费用报销单:在浏览器中点击打印按钮,选择打印机和打印设置,然后点击打印按钮完成费用报销单打印。 通过以上步骤,可以实现费用报销单打印HTML的功能。该方法不仅简便快捷,还可以根据需求进行灵活的定制和调整,方便用户进行费用报销单的打印和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值