zoukankan      html  css  js  c++  java
  • $P3092 [USACO13NOV]$没有找零

    #$Description$ [题面](https://www.luogu.org/problem/P3092) 有$n(n<=1e5)$个商品和$k(k<=16)$个硬币,给定每个硬币的面额和商品价格,要求按照顺序购买商品,每次可以拿出一个硬币,购买从上次结算到现在的所有物品(前提是可以支付得起),不会找零,求最优方案使得最终剩余的面额和尽可能大 #$Solution$ 一道看起来比较简单~~但实际上就是很简单~~的题,中午硬是没想出来,下午才有了思路,过掉了这道题 真是一道降智好题 比较显然的是需要对硬币使用情况状态压缩 一开始的想法是设$dp[i][j]$表示硬币使用情况是$i$时,当前考虑到$j$,剩余$dp[i][j]$,也记不太清了,总之有很多思路,但是多半没法转移或者会$MLE$。结果我下午才发现我是个傻子,注意到题目中一条性质是不能找零,所以我们知道买完所有商品时硬币的使用情况,也就知道剩余的面额和了,所以不需要用$dp[i][j]$表示剩余面额。

    那么换种思路,因为题目中提到按照顺序买,所以设(dp[i])为选择状态为(i)时买到的右边界,转移过程大概为枚举状态,枚举哪颗硬币,再枚举买完后的右边界,检验是否合法,取(max)即可。但这样做(O(nSk)),妥妥(TLE),考虑预处理每个位置每颗硬币最远买到什么地方,直接(O(Sk))转移即可。而预处理的过程可以(O(nk))解决,总复杂度可以接受。

    为什么对于一种硬币,尽可能往后买的贪心是对的呢,还是基于题目的性质,不能找零,当这个硬币用掉不管买多少答案都会减去这个硬币的面值,不如让他买的尽可能多,这样才有可能让后面的硬币省下来。

    (Code)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define re register
    #define maxn 100010
    using namespace std;
    inline int read()
    {
    	int x=0,f=1; char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int k,n,w[20],c[maxn],sumc[maxn],pos[maxn][20],MAX; 
    int dp[maxn],val[maxn],sums,ans;
    void work(int x)
    {
    	int r=1,l=1;
    	while(r<=n)
    	{
    		while(sumc[r]-sumc[l-1]>w[x]) //这里注意写while,因为有可能加入一个后好几个区间都超过了面额 
    		{
    			pos[l][x]=r-1;
    			l++;
    		}
    		r++;
    	}
    	while(sumc[r]-sumc[l-1]>w[x]) 
    	{
    		pos[l][x]=r-1;
    		l++;
    	}//调整 
    	for(re int i=l;i<=n;++i) pos[i][x]=n;//注意把后面没有处理的区间解决掉 
    	
    }
    void pre()
    {
    	for(re int i=0;i<=MAX;++i)
    	{
    		int now=i,cnt=1;
    		while(now)
    		{
    			if(now&1) val[i]+=w[cnt];
    			now>>=1,cnt++;
    		}
    	}
    }
    int main()
    {
        k=read(),n=read();
        MAX=(1<<k)-1;
        for(re int i=1;i<=k;++i) w[i]=read(),sums+=w[i];
        for(re int i=1;i<=n;++i) c[i]=read(),sumc[i]=sumc[i-1]+c[i];
        for(re int i=1;i<=k;++i) 
    	work(i);
        pre();
        for(re int s=0;s<=MAX;++s)
        	for(re int i=1;i<=k;++i)
        	{
        		if(s&(1<<i-1)) continue;
        		dp[s|(1<<i-1)]=max(dp[s|(1<<i-1)],pos[dp[s]+1][i]);
    		}
    	ans=0x3f3f3f3f;
    	for(re int s=0;s<=MAX;++s)
    	{
    		if(dp[s]==n) ans=min(ans,val[s]);
    	}
    	if(ans==0x3f3f3f3f) printf("-1
    ");
        else printf("%d
    ",sums-ans);
    	return 0;
    }
    
    
  • 相关阅读:
    Thread.sleep(0)的意义& 多线程详解
    .NET AOP的实现
    UML详解
    asp.net事件委托易理解实例
    2个或多个datable类似于sql inner join 合并查询
    web.cofing(新手必看)
    JS操作URL
    .net对象转Datable
    NPOI读写Excel
    RSA加密
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/11825974.html
Copyright © 2011-2022 走看看