1. 程式人生 > >[USACO13NOV]沒有找零No Change [TPLY]

[USACO13NOV]沒有找零No Change [TPLY]

pri class 消費 消費者 二分答案 括號 inf 沒有 ref

[USACO13NOV]沒有找零No Change

題目鏈接 https://www.luogu.org/problemnew/show/3092

做題背景

FJ不是一個合格的消費者,不知法懂法用法,不會拿起法律的武器維護消費者權益

做法

本題涉及的知識點:狀態壓縮,動態規劃,二分答案。
註意物品是依次購買

首先是狀態壓縮:
壓什麽?我們註意到k<=16,那麽就是壓硬幣使用集合

然後是動態規劃:
怎麽設狀態?設dp[i]表示我們選硬幣集合i(狀壓)從一號物品開始最多能買的物品數。
怎麽轉移?從0開始枚舉每一個可能的硬幣集合i(狀壓)
找到每一個存在於使用集合i中的硬幣
(例如在集合11001中1,2,5是存在於集合i中的)
然後從不包含該硬幣的集合

中轉移過來
(即11001從01001,10001,11000中轉移過來)

再是二分答案:哪裏需要二分答案?在轉移過程中.我們需要找到利用硬幣最多能買到的物品數,枚舉太耗時間,前綴和二分答案就可以加快速度了。

幾點註意:
轉移開long long
位運算優先級有點頭疼,能打括號打括號
Ans設為-1,不能設為0因為可能存在正好一點不剩的最優解

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

#define RG register
#define rg register int #define rc register char #define ll long long #define il inline #define INF 2147483647 #define SZ 100001 using namespace std; il int gi() { rg x=0,o=0;rc ch=getchar(); while(ch!=‘-‘&&(ch<‘0‘||‘9‘<ch))ch=getchar(); if(ch==‘-‘) o=1,ch=getchar(); while
(‘0‘<=ch&&ch<=‘9‘) x=(x<<1)+(x<<3)+ch-‘0‘,ch=getchar(); return o?-x:x; } int k,n; ll tot,Ans,a[SZ],sum[SZ],dp[65537],c[17]; // 查詢一個數二進制中1的個數 il ll num(rg x) { RG ll cnt=0; for(rg i=0;i<k;++i) if((x>>i)&1) cnt+=c[i+1]; return cnt; } //手寫upperbound函數 il int Upper(ll *array,int size,ll key) { rg hd=1,len=size; while(len>0) { rg mid=hd+(len>>1); if(array[mid]>key) len>>=1; else hd=mid+1,len=len-(len>>1)-1; } return hd; } // upper_bound int main() { k=gi(),n=gi(); for(rg i=1;i<=k;++i) c[i]=gi(),tot+=c[i]; for(rg i=1;i<=n;++i) a[i]=gi(),sum[i]=sum[i-1]+a[i]; for(rg i=0;i<=(1<<k)-1;++i) for(rg j=0;j<k;++j) if((i>>j)&1) { ll tmp=(i^(1<<j)); tmp=Upper(sum,n,sum[dp[tmp]]+c[j+1]); dp[i]=max(dp[i],tmp-1); } Ans=-1; for(rg i=0;i<=(1<<k)-1;++i) if(dp[i]==n) Ans=max(Ans,tot-num(i)); printf("%lld",Ans); return 0; }

[USACO13NOV]沒有找零No Change [TPLY]