• USACO2013 Nov. Gold T3,一道集合DP


    网上关于集合动态规划的题目好像不多?

    关于集合DP的题目基本没见到过多少...但是USACO 2013 Nov. Gold T3居然直接考了个裸的......

    于是就怒学一发...


    集合DP,意思是我们定义状态的时候使用集合.

    一般我们定状态,比如LIS(最长上升子序列),

         d[i]: 以第i个元素结尾的最长上升序列长度.

    再比如0/1背包,

         d[i][j]: 考虑前i件物品,用掉了容量j所能达到的最大价值

    或者 d[i][j]: 考虑前i件物品,背包容量上限为j,所能达到的最大价值


    适用范围:

    1.集合DP是指数级算法,一般有n<=20,将n!的复杂度优化至2^n;

    2.集合DP想要求出一个依赖于集合中元素顺序的结果,但是判定方案是否可行或者状态转移不需要得到原集合中元素顺序.



    集合dp呢就是"把集合当下标".具体的看下边....


    例题在此:  http://www.usaco.org/index.php?page=viewproblem2&cpid=348

    题目大意是,给你n<=16个硬币,每个硬币有一个面值c[i],要按照给定先后顺序购买m<=100000个物品,所有物品的价格a[i]由输入给出,不能找零,求最多能留下多少钱(价值).

    n<=16是一个很明显的提示.这告诉我们2^n的算法能做.


    考虑最优的购买方案.

    我们肯定是从n个硬币中选出若干个,然后按照某种排列依次购买物品.

    直接枚举硬币的选取情况(某个硬币选或不选),有2^n中可能.

    然后我们试着快速地判断这个选取硬币的方案是不是最优的.

    怎么评判方案的优劣呢? 显然,剩余的硬币价值之和越大,方案就越好.

    对于每种选取硬币的方案S,如果我们能够快速判断它是否能购买完所有物品,那么就能在O(1)时间算出S能带来的最大剩余价值.

    于是,我们的任务变成,快速判断S能否购买完所有物品.

    于是集合DP登场.

    还是枚举S.

    注意到他给出的一个小小的条件我们没有用到:物品必须按顺序买,一旦买了物品k,那么物品k-1,物品k-2...物品1都得买.

    于是乎...

    状态: d[S]: S最多能够买到哪件物品.(S所能取得的最大k)

    转移: d[S]=max( d[S\p]+用硬币p在之后的中按顺序购买,最多能买到的物品数 );

    读起来比较绕.

    我们用S\p购买到了第k=d[S\p]个物品,那么我们接下来就一定要用硬币p购买之后的物品k+1,物品k+2..直到不能买为止.

    显然,如果把所有物品买完了,说明S是可行的.

    最后一个小问题,边界.

    边界: d[S]=0,当且仅当S为空(不使用任何硬币).

    整个DP就完成了.....

    有一个小优化.用硬币p购买物品k+1,物品k+2...直到h-1,h.我们想要快速找出这个h,毕竟m=100000,扫一遍的话速度够呛.

    这个直接二分h的位置即可.


    接下来是照例帖代码....

    直接用map做的记忆化搜索.

    最大数据点跑620ms. USACO评测机真心快....

    #include <cstdio>
    #include <iostream>
    #include <cstdlib>
    #include <algorithm>
    #include <map>
    #include <cstring>
    #include <fstream>
    
    #include <cmath>
    
    #include <vector>
    #include <queue>
    #include <stack>
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double db;
    
    using namespace std;
    
    ifstream fin("nochange.in");
    ofstream fout("nochange.out");
    
    int n,m;
    
    int a[101000];
    int __presum[101000];
    int*presum=__presum+1;
    int c[20];
    
    class Set
    {
    	public:
    	
    	bool t[17];
    	Set(){}
    	Set(bool h)
    	{
    		for(int i=0;i<n;i++)
    		t[i]=h;
    	}
    	bool empty()
    	{
    		for(int i=0;i<n;i++)
    		if(t[i]) return false;
    		return true;
    	}
    	
    	bool&operator[](int f) { return t[f]; }
    	
    	Set operator-(int k)
    	{
    		Set c(*this);
    		c[k]=false;
    		return c;
    	}
    	
    	bool operator<(const Set x) const
    	{
    		for(int i=0;i<n;i++)
    		if(x.t[i]!=t[i]) return t[i]<x.t[i];
    		
    		return false;
    	}
    	
    };
    
    int res=-1;
    
    map<Set,int> g;
    
    int DFS(Set x)
    {
    	if(x.empty())
    		return 0;
    	
    	if(g.find(x)!=g.end()) return g[x];
    	
    	int mx=0;
    	for(int i=0;i<n;i++)
    	if(x[i]!=0)
    	{
    		int k=DFS(x-i);
    		mx=max(mx,(int)(upper_bound(presum,presum+m,presum[k-1]+c[i])-presum));
    	
    		if(mx==m)
    		{
    			int ut=0;
    			for(int i=0;i<n;i++)
    			if(x[i]==0) ut+=c[i];
    			res=max(ut,res);
    		}
    	}
    	
    	return g[x]=mx;
    }
    
    int main()
    {
    	fin>>n>>m;
    	
    	for(int i=0;i<n;i++)
    	fin>>c[i];
    	
    	for(int i=0;i<m;i++)
    	fin>>a[i];
    	
    	presum[-1]=0;
    	for(int i=0;i<m;i++)
    	presum[i]=presum[i-1]+a[i];
    	
    	DFS(Set(true));
    	
    	fout<<res<<endl;
    	
    	return 0;
    }
    


    位运算优化的代码.最大的数据点跑了60ms.

    #include <cstdio>
    #include <iostream>
    #include <cstdlib>
    #include <algorithm>
    #include <map>
    #include <cstring>
    #include <fstream>
    
    #include <cmath>
    
    #include <vector>
    #include <queue>
    #include <stack>
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double db;
    
    using namespace std;
    
    ifstream fin("nochange.in");
    ofstream fout("nochange.out");
    
    int n,m;
    
    int a[101000];
    int __presum[101000];
    int*presum=__presum+1;
    int c[20];
    
    int res=-1;
    
    int g[145000];
    
    int DFS(int x)
    {
    	if(x==0)
    		return 0;
    	
    	if(g[x]!=-1) return g[x];
    	
    	int mx=0;
    	int p=1;
    	for(int i=0;i<n;i++,p<<=1)
    	if((x&p)!=0)
    	{
    		int k=DFS(x-p);
    		mx=max(mx,(int)(upper_bound(presum,presum+m,presum[k-1]+c[i])-presum));
    	
    		if(mx==m)
    		{
    			int ut=0;
    			int h=1;
    			for(int i=0;i<n;i++,h<<=1)
    			if((x&h)==0) ut+=c[i];
    			res=max(ut,res);
    		}
    	}
    	
    	return g[x]=mx;
    }
    
    
    int main()
    {
    	fin>>n>>m;
    	
    	for(int i=0;i<n;i++)
    	fin>>c[i];
    	
    	for(int i=0;i<m;i++)
    	fin>>a[i];
    	
    	presum[-1]=0;
    	for(int i=0;i<m;i++)
    	presum[i]=presum[i-1]+a[i];
    	
    	int p=0;
    	for(int i=0;i<n;i++) p=p<<1|1;
    	
    	for(int i=0;i<=p;i++) g[i]=-1;
    	
    	DFS(p);
    	
    	fout<<res<<endl;
    	
    	return 0;
    }
    

    另外USACO2014 Dec. Gold T1 也是集合DP,必须压位,跑了200ms.记得我调了好久才莫名其妙地干掉唯一一个WA点...





  • 相关阅读:
    Lua 语言学习
    Unity EasyTouch官方案例学习
    Unity Dotween官方案例学习
    斗地主出牌规则
    C# XML文件读取
    斗地主项目总结
    C# 委托和事件
    C# 观察者模式
    消防法及相关法律法规(二)
    消防法及相关法律法规(一)
  • 原文地址:https://www.cnblogs.com/DragoonKiller/p/4295955.html
Copyright © 2020-2023  润新知