1. 程式人生 > >洛谷P4241 採摘毒瘤

洛谷P4241 採摘毒瘤

傳送門

 

完了我連揹包都不會了……

考慮暴力,先列舉最小的數是哪個,設大小為$d_i$,個數為$k_i$,所有比它小的數的總和是$sum$,然後把所有比它小的全都裝進揹包,它以及比他大的做一個多重揹包,那麼設$dp[j]$表示在剩下的這些數裡取的總和為$j$時的方案數,那麼$$ans+=\sum_{j=m-sum-d_i+1}^{m-sum} dp[j]$$

上面的式子意思就是,將所有比它小的全放進去之後,列舉揹包剩餘的空間,統計所有方案數。因為不能讓把任何大於等於它的在統計之前就放進去,所以下界是$m-sum-d_i+1$

然後現在的問題就是怎麼統計了才能過。我們可以從大到小列舉,這樣的話每做到一個物品就只需要對它自己做多重揹包,不需要重新dp了

接下來的操作真的是學到了……在做多重揹包的時候把$j$按照模$d_i$的取值分為不同的組,然後時間複雜度就能優化到$O(nm)$

簡單來講的話,就是轉移的時候$dp[j]+=dp[j-d_i]+dp[j-d_i*2]...+dp[j-d_i*k_i]$,發現每一個$j$只會被與它模$d_i$同餘的轉移到,於是我們列舉這一個餘數,統計與它同餘的數的字首和,然後從小到大轉移並不斷維護字首和即可(真的很妙)

 1 //minamoto
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<algorithm>
 5
using namespace std; 6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 7 char buf[1<<21],*p1=buf,*p2=buf; 8 int read(){ 9 #define num ch-'0' 10 char ch;bool flag=0;int res; 11 while(!isdigit(ch=getc())) 12 (ch=='-')&&(flag=true
); 13 for(res=num;isdigit(ch=getc());res=res*10+num); 14 (flag)&&(res=-res); 15 #undef num 16 return res; 17 } 18 const int N=505,M=1e5+5,mod=19260817; 19 struct node{ 20 int k,d; 21 inline bool operator <(const node &b)const{return d<b.d;} 22 }a[N]; 23 int n,m,ans,tot,dp[2][M],t; 24 inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;} 25 inline int del(int x,int y){return x-y<0?x-y+mod:x-y;} 26 void ins(int k,int w){ 27 int sum,H; 28 for(int d=0;d<=w-1;++d){ 29 H=sum=0; 30 for(int j=0;j<=(m-d)/w;++j){ 31 sum=add(sum,dp[t^1][j*w+d]); 32 if(H<j-k) sum=del(sum,dp[t^1][(H++)*w+d]); 33 dp[t][j*w+d]=sum; 34 } 35 } 36 } 37 int main(){ 38 // freopen("testdata.in","r",stdin); 39 n=read(),m=read(); 40 for(int i=1;i<=n;++i) a[i].k=read(),a[i].d=read(),tot+=a[i].k*a[i].d; 41 if(tot<=m) return puts("1"),0; 42 sort(a+1,a+1+n); 43 dp[0][0]=1; 44 for(int i=n;i;--i){ 45 tot-=a[i].k*a[i].d,t^=1; 46 ins(a[i].k-1,a[i].d); 47 for(int j=max(m-tot-a[i].d+1,0);j<=m-tot;++j) ans=add(ans,dp[t][j]); 48 ins(a[i].k,a[i].d); 49 } 50 printf("%d\n",ans); 51 return 0; 52 }