背包_(:з」∠)_ 写的很乱

啊!!!!!!!!!!!!

ABCD终于都过了,我这几天简直什么都没干!!!!!!!!

啊!!!!我要开始跑步洗衣服弹吉他了!!!!

 

是这样的

背包的知识我先不写了

这个C有什么坑呢

【一定要知道你在干什么】

【而不是盲目的这里改改,那里改改,哎……】

所以说了嘛,要冷静。要冷静。要冷静。

出去溜达一圈,最好的是去洗个澡。

 

地址 https://vjudge.net/contest/239853

核心代码其实一点也不麻烦

其实也就只是dp

然后,这里要求的是恰好要装满的时候怎么办。所以最开始初始化成-inf

inf 444......

(一般,正好装满的时候都是这样的,如果你想取得最大值初始化成-inf,最小值是inf)

(因为,比如90块钱的东西花30元去买,dp[31]  dp[1]+value)

30这里对应的dp[0]=0 取到的是max了(-inf和0+value是后面的大)

31呢 dp[31]  dp[1]+value之后虽然是后面的大   但是加了一点零碎  是负一堆乱七八糟

(然后,这些都是无效的,只有从0那里开始才有效。)

最后判断的时候判是不是小于0 什么的就好了_(:з」∠)_

_(:з」∠)_.... 我已经又乱了 别叫我了

 

 

 

如果是取得能装满的最小值,那么用inf,min的话

dp[0]=0 dp[30]

dp[31] dp[1]+value  还是取到dp[31]本身(还是取到inf  那个无穷大)

最后不可达到的都是inf

 

for (int i = 1; i <= n; i++) {
			for (int j = vall; j >= a[i]; j--) {
				sum[j] = max(sum[j], sum[j - a[i]] + a[i]);
				if ((max(sum[j], sum[j - a[i]] + a[i]) == sum[j - a[i]] + a[i]))
					//如果这一刻唱歌了
					//那就看,你唱的那个数量和原来的数量哪个大呀
				{
					if (s[j] > s[j - a[i]] + 1);//如果唱了还不如没唱,那就不唱不更新这个 了
					else if(s[j]<s[j-a[i]]+1)//如果是唱了的更大,那就唱呗
					s[j] = s[j - a[i]] + 1;
					
				}
				else if (max(sum[j], sum[j - a[i]] + a[i]) == sum[j])
					//如果这一刻没唱歌,那就不唱咯,还是不变
					s[j] = s[j];
				
				//cout << i << " " << s[i] << " " << sum[i] << endl;
			}
		}

 

这个题完全不麻烦,但是唱歌这里,它是想按照唱歌最大的进行输出的。

更新的时候稍微注意一下。

 

怎么更新有两种可能的。

 

还有就是,可能并不是倒着数能达到的最大,可能在前面,所以我最后跑了一遍,略微记录一下经过的所有道路。recordi再计算。

sum是对应最大次数的时候唱歌的时长  这里可能不是满着的 但是直接加上678就可以了 因为只能这样 默认你是接着开始唱的

 

可能你唱最后一首jingejinqu并不是最后一秒, 也就是取到从前面所能达到的最大值。



#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f
int k; int n; int vall;
int  a[55];
//int sum[55];
int sum[100005];
int s[100005];
//}node[100005];
int main() {
	int kase = 0;
	cin >> k; while (k--) {
		cin >> n >> vall;
		vall--;
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
		}
		sort(a + 1, a + 1 + n);
		//*********************这不能够!!!
		for(int i=1;i<=vall;i++){
			sum[i] = -inf;
			s[i] = -inf;
		}
		sum[0] = 0;
		s[0] = 0;
		for (int i = 1; i <= n; i++) {
			for (int j = vall; j >= a[i]; j--) {
				sum[j] = max(sum[j], sum[j - a[i]] + a[i]);
				if ((max(sum[j], sum[j - a[i]] + a[i]) == sum[j - a[i]] + a[i]))
					//如果这一刻唱歌了
					//那就看,你唱的那个数量和原来的数量哪个大呀
				{
					if (s[j] > s[j - a[i]] + 1);//如果唱了还不如没唱,那就不唱不更新这个 了
					else if(s[j]<s[j-a[i]]+1)//如果是唱了的更大,那就唱呗
					s[j] = s[j - a[i]] + 1;
					
				}
				else if (max(sum[j], sum[j - a[i]] + a[i]) == sum[j])
					//如果这一刻没唱歌,那就不唱咯,还是不变
					s[j] = s[j];
				
				//cout << i << " " << s[i] << " " << sum[i] << endl;
			}
		}
		//int maxx = sum[1];
		int record = 0;
		int recordi = 0;
		for (int i = 1; i <= vall; i++) {
				//cout << i << " " << s[i] << " " << sum[i]+678 << endl;
			//}
			if (s[i] > record || (s[i] == record && sum[i] > record))
			{
				record = s[i];
				recordi = i;
			}
		}
		//int record = vall;
		cout << "Case " << ++kase << ": " << s[recordi] + 1 << " ";//
		cout << sum[recordi] + 678<<endl;
		//cout<<" "<< sum[recordi] + 678 - (vall - recordi) << endl;
		
		}
	return 0;
}

 

依赖背包的B

依赖背包

最低不能低于盒子
最低不能低于盒子
最低不能低于盒子
 
把j代进去试一下为什么我们错了

 

首先要知道什么呢...  我们买了箱子是没有价值的,所以先不要考虑那么多
(对于这个题的样例的解释)
如果我们买了200块钱的箱子,我们就只有600块去买别的东西了,这600块到底能怎么拿,是balabalabal……

(所以,你真的理解吗?代换到01背包那个时候我们给出的状态,01背包的时候9为10,10为11 ,我当时说你有九个肚子的时候可以吃十个东西,有十个肚子的时候啊哈增加了,可以吃十一个东西了)

现在,也就是你买了200块的东西,捆绑消费,你又不得不买,所以你手里只剩下600块去买其他的吃的了。
我最开始想的方程是,你有800块可以买,你只能花在200-800里面,因为有200是买了箱子的捆绑销售。
所以大概,大概我的做法对于一个或许可行,但是

emmm,dp这种东西,保证每一次都最优的。每一次停下来的时候,所能取到的都是有史以来最佳的方案(是不是很神奇!)
所以一定要可以连起来。

按照正确的想法,我们应该先把那200扔掉。扔掉之后处理的话,就当你被绑票了之后,身上只剩个600块再去发配处理。

你贪心不足还是想要买个被绑票的机会。。。 因为只有绑票了才能吃到绑匪界的粮食,那么你只能用这600块再去挥霍了。

至于怎么挥霍的呢?
每次你只能选择去一家绑匪被绑。
状态更新到,在这一家买所有的物品拿到最优的方案。
比如你最后,用这600元拿到了130元的食物。
存着的是600处为130, 再往前,存着的是300处50(比如这样呢)

现在你恢复自由身。
第一次肯定是买了箱子划算……
(600,130, )(300,50)
继续,你又有了800元,现在你买了一次300元的绑架计划。于是你只有500块可以自己挥霍了

(……还是不太懂……)

=======分割线=======
好你完了

是这样的,首先,可以写成二维的,二维的意思就是说存一下上个状态和下个状态咯
也可以是一维的,一维嘛,每次动态规划里,都取到了的是最优_(:з」∠)_
每次都取到最优,然后继承下来的,所以这个代码使用了memcpy
如果不用memcpy,其实存一下上个状态(就是没装当前盒子的状态,此题里为sum)和下个状态(装了当前盒子的状态,此题里为box)
最后取得最大。动态规划每个都最优的…… 一路下来。

其实只要两次01背包就好了,第一次01背包是假背包,比如800的花了300去买,那么只能装0-500的地方,最少的是50那么就从50-500开始装。
第二次01背包是把它看成一个大背包。。。  大箱子扔进去填充了,所以只能装到350-800的,这也是为什么写成sum[j]和box[j-box]的原因。
这个最后的350其实是上文里面的50
这个最后的800其实是上文里面的500
这样,就把箱子当做一个大东西存了起来。
背包装的时候还是老套路,所以从vvall到bbox装。
这里,0-50装不下,其实也就是300-350装不下,这里空了出来。
其他地方存着的状态,其实就是装了300箱子+300箱子其他物品的状态。
这个sum数组会延续到下一次的使用中。
就是最开始正着用,塞进去,拿出来再反过来。
我们假设第二个物品400块,其中第一个东西就200快(有70的价值),根据常理我们知道不能买了第一个再买第二个的。

在第一次01背包中:
更新的是0-400的状态。
对于这个第一个物品,更新的是200-400的状态。
注意350-400已经被上一次更新过了,所以前面的200-350是空的,那么就比较【空的所以第一次没装,第二次装,第二次装的时候能达到的状态-  200-350是70】
(如果不是空的,也继续比较第一次的状态和第二次的状态,,装起来哪个大嘛)

350-400 相比呢,box[150]~box[200] +70
和装了第一个的相比
这里350-400胜出了,所以这里想都不要想

第二轮大的,再加400
这里200-350就是600-350,就是没装以前的,只装了现在的,只有70
比较前面的

这第二次01背包是个重新分配的大比较,每次装各种东西的比较都会用到。

 

 

 

D 旅行商问题

WA的竟然是 int max

/* 想求什么?
简单的二维偏序关系
重要的是先设置成inf,一定要开始这个更新!...
先找到所有里面,数组a最小的
如果a就是最小的,那就好了,a附带的b也一起记下来
(也就是说,如果a是独一无二的,那没事,只更新了一次,那就是a带着自己的b唯一更新的一次)
如果有多次,那么也没关系 
不满足前面的,满足后面的(minn=a[i],然后字典序还更小)那就更开心了,换成字典序更小的那个。
就算后面又遇到了新的
(意思是,1和2相等,并且更新了,但是3才是最小的)
这个时候满足了(minn>a[i]),非常开心的更新成了a[i]....
先找到a里面最小的
如果有多个最小的,就找到这多个里面b最小的.. 
来跟我念 
int mina=inf,int minb=inf;
for(.. ) 数据范围(像不像两个眼睛-.-)
//... 到了uva116这个题里面范围就是那三个咯...  只有这三个

if(mina>a[i]||(mina==a[i]&&minb>b[i])){
//相等情况下 b[i]更优
mina=a[i];
minb=b[i];
}

//注意一下:
等于号是两个等于,不要犯小错误。

【关于这个题】
其实整体上来说还算好就是一个动态规划问题……
刚才又想了一下,过来的时候每个都取最小的,
就像你每次要买那么多东西, 有一个巨无敌贵的,只要你买的时候每次都是在三个里面比较完成的,寂寞就追不上你。
哪怕最后是 1 2 3 4 5 6
第一次找完,每次是在三个里面找最小的,那么就会变成1 2 3 4 5 1 
有同学会问了,这样不就是1 2 3 4 1 1 ,... 渐渐的只要6列就没了么?
不怕,我们是按列来的,这个c是一路过来的所有的总和,我们还要加上自己a的值,这样原来的1 2 3 4 1 1 大小就会变化。
我们确保了它的来源都是最小的。要是有一个剧贵无比的在第一个,硬是要从这里走的话也只能买下巨贵无比的东西才能在此路走过呀。(不过,它会在下一次优化中消失掉)
所以状态转移方程并不复杂

int minn; int maxx;
                if (k == 1) minn = n; else minn = k - 1;
                if (k == n)int maxx = 1; else maxx = k + 1;
                c[k][i] = a[k][i] + min(min(c[k][i + 1], c[minn][i + 1]), c[maxx][i + 1]);
因为可以从上面走掉,所以多写了一点东西,不是简单的k+1  k-1此类。

还有就是要处理字典序问题,在三个数字一样的时候是不用考虑的,如果有一样的保证最后走过的, 是字典序最小的(上面写了)

 

最后,要找一下所有的里面,(最终值,列都变成1了之后)总和小的那一列
这里还是有个小tips,就是如果有相同的话,选序号最小的那些……嗯。。。
注意一下next要赋初值,否则会在1 1 的时候出问题。
 

#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
using namespace std;
typedef long long ll;
int c[35][105];
int a[35][105];
int s[35][105];
int ss[35][105];//ss是记录他后面是什么....!!!
int m;int n;
#define inf 0x3f3f3f;
int finddd(int maxx) {
	for (int i = 1; i <= n; i++) {
		if (c[i][1] == maxx)
			return i;
	}
}
int main() {
	while (cin >> n >> m) {
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)cin >> a[i][j];
		}
		for (int i = 1; i <= n; i++)
		{
			c[i][m] = a[i][m];
			s[i][m] = i;}
		for (int i = m - 1; i >= 1; i--) {
			for (int k = 1; k <= n; k++) {
				int minn; int maxx;
				if (k == 1) minn = n; else minn = k - 1;
				if (k == n) maxx = 1; else maxx = k + 1;
				c[k][i] = a[k][i] + min(min(c[k][i + 1], c[minn][i + 1]), c[maxx][i + 1]);
				//cout << c[minn][i+1]<<" "<<c[k][i + 1] << " " << c[maxx][i + 1];
				//cout << k<<" "<<c[k][i] << endl;
				int lla[3]; int llb[3];
				lla[0] = c[k][i + 1]; llb[0] = k;
				lla[1] = c[minn][i + 1]; llb[1] = minn;
				lla[2] = c[maxx][i + 1]; llb[2] = maxx;
				int mina =inf; int minb = inf;
				for (int i = 0; i <= 2; i++) {
					if (mina > lla[i] ||( (mina==lla[i]) && minb > llb[i]))
					{
						mina = lla[i]; minb = llb[i];
					}
				}
				ss[k][i] =minb;//意思就是我的下一个是k
				//cout << minb << endl;
			}
		}
		int maxx = c[1][1]; int record = 105;
		for (int i = 1; i <= n; i++) {
			if (c[i][1] <= maxx) {
				maxx = c[i][1];
			}
		}
		record= finddd(maxx);
		
		int next=record;
		for (int i = 1; i <m; i++) {//(应该少输一个)
			cout << record<<" ";
			next = ss[record][i];//next 是下一个应该找到的行..  吧
			record = next;
		}cout << next<<endl;
		cout << maxx << endl;
	}
	return 0;
}

0-1背包
老问题了
这个图(其实也就是,对应的这个方程呀)
(1)从下面往上面生成的
(其实也就是,每一个东西分次往里面加加加)
保证每个过来的时候,都是最好的(看代码吧...)
加东西 不加东西 加东西 不加东西
(2) 如果装进去体积就超了
这个情况已经被, j>=price[i]限制住了...
i是第几个物品,n是物品总数
然后for(int i=1;i<=n;i++)
这样j的话每个状态也试一下,就是
for(int j=m;j>=price[i];j--)
sum[j]=max(sum[j],sum[j-price[i]]+value[i]);
 

完全背包是正的 因为要加很多次 对后面的状态都有影响了

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值