USACO Shopping Offers 解题报告

这道题刚开始用搜索做的,即遍历每种特惠的可能次数,最后取最省的组合。虽然用到了剪枝(当当前的费用已经超过了当前最省的组合,就不必接着考虑后面的特惠了),但是仍然效率特别低,在遇到后面的测试点的时候程序就无止境地跑下去了。

后来看到网上说用动态规划,因为最多只有5种商品,每种商品最多买5个,所以状态总数特别少,只有6^5=7776。所以用动态规划把表填了就好。

简单总结:

1.每次做题对状态总数要有个大致的估计,像最近做的这几道题,如果状态空间很小(运算重量级万或运算轻量级十万以下),直接枚举就好(会通过动态规划等方法用到之前的状态)。

2.好的算法和差的算法的运行效率有天壤之别。之前的搜索的方法面对稍大的数据,就永远等不出结果了。而动态规划的方法仍然是blazingly fast.

代码如下:

/*
ID: thestor1
LANG: C++
TASK: shopping
*/
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cassert>
#include <string>
#include <algorithm>
#include <stack>
#include <set>
#include <queue>
#include <map>

using namespace std;

const int P = 5;
const int S = 99;

int main()
{
	FILE *fin  = fopen ("shopping.in", "r");
	FILE *fout = fopen ("shopping.out", "w");
	//freopen("log.txt", "w", stdout);

	int s;
	fscanf(fin, "%d", &s);

	map<int, int> cid;
	int sos[S][6] = {0};
	int id = 0;
	for(int i = 0; i < s; ++i)
	{
		int n;
		fscanf(fin, "%d", &n);
		for(int j = 0; j < n; ++j)
		{
			int c;
			fscanf(fin, "%d", &c);
			if(cid.find(c) == cid.end())
			{
				cid[c] = id;
				id++;
			}
			fscanf(fin, "%d", &sos[i][cid[c]]);
		}
		fscanf(fin, "%d", &sos[i][5]);
	}

	int b;
	fscanf(fin, "%d", &b);
	int require[P] = {0};
	int price[P] = {0};
	for(int i = 0; i < b; ++i)
	{
		int c;
		fscanf(fin, "%d", &c);
		if(cid.find(c) == cid.end())
		{
			cid[c] = id;
			id++;
		}
		fscanf(fin, "%d", &require[cid[c]]);
		fscanf(fin, "%d", &price[cid[c]]);
	}
	assert(id <= 5);

	int f[6][6][6][6][6];
	int num[5] = {0};
	for(num[0] = 0; num[0] <= require[0]; ++num[0])
	{
		for(num[1] = 0; num[1] <= require[1]; ++num[1])
		{
			for(num[2] = 0; num[2] <= require[2]; ++num[2])
			{
				for(num[3] = 0; num[3] <= require[3]; ++num[3])
				{
					for(num[4] = 0; num[4] <= require[4]; ++num[4])
					{
						if(!num[0] && !num[1] && !num[2] && !num[3] && !num[4])
						{
							f[0][0][0][0][0] = 0;
							continue;
						}
						f[num[0]][num[1]][num[2]][num[3]][num[4]] = -1;
						for(int i = 0; i < 5; ++i)
						{
							int nnum[5];
							for(int j = 0; j < 5; ++j)
							{
								nnum[j] = num[j];
							}
							if(num[i] > 0)
							{
								nnum[i] = num[i] - 1;
								if(f[num[0]][num[1]][num[2]][num[3]][num[4]] < 0 
									|| f[nnum[0]][nnum[1]][nnum[2]][nnum[3]][nnum[4]] + price[i] 
								< f[num[0]][num[1]][num[2]][num[3]][num[4]])
								{
									f[num[0]][num[1]][num[2]][num[3]][num[4]] = 
										f[nnum[0]][nnum[1]][nnum[2]][nnum[3]][nnum[4]] + price[i];
								}	
							}
						}
						for(int i = 0; i < s; ++i)
						{
							bool possible = true;
							for(int j = 0; j < 5; ++j)
							{
								if(sos[i][j] > num[j])
								{
									possible = false;
									break;
								}
							}
							if(!possible)
							{
								continue;
							}
							int nnum[5];
							for(int j = 0; j < 5; ++j)
							{
								nnum[j] = num[j] - sos[i][j];
							}
							assert(f[num[0]][num[1]][num[2]][num[3]][num[4]] > 0);
							if(f[nnum[0]][nnum[1]][nnum[2]][nnum[3]][nnum[4]] + sos[i][5] 
							< f[num[0]][num[1]][num[2]][num[3]][num[4]])
							{
								f[num[0]][num[1]][num[2]][num[3]][num[4]] = 
									f[nnum[0]][nnum[1]][nnum[2]][nnum[3]][nnum[4]] + sos[i][5];
							}
						}
					}
				}
			}
		}
	}
	fprintf(fout, "%d\n", f[require[0]][require[1]][require[2]][require[3]][require[4]]);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值