關於物品大小較小的01揹包
關於物品大小較小的01揹包
設題目給出的物品大小為\(w_i\),個數為\(n\),總和為\(N\)
前言
這個東西可能純粹是亂搞
發現WC2020 選課這個題直接被一個錯誤的寫法爆水
ps:這個題是大小範圍1~3,實際上正解複雜度很高
\(w_i\in\{1,2\}\)
這個範圍的01揹包,實際上可以
1.回撤貪心,從小到大考慮,每次要讓總和加一,則決策就是 選一個1 或者 選一個2,刪掉一個1
2.奇怪的dp? (先放著下面講)
\(w_i\in\{1,2,3\}\)
好像是並沒有很好的寫法的,在WC2020選課 這道題中,由於求出的揹包只需要訪問最後\(L\)個結果
配合上面的貪心,先求出\(w_i\in\{1,2\}\)
\[\ \]
作為亂搞選手,然後想要提一下這個奇怪的dp
大概可以表達為
1.對於每種權值的物品分別排序
2.每個dp位置記錄答案的同時,記錄方案在每種權值裡選擇了幾個
3.轉移就是考慮每種權值選擇剩下的最優的一個
實際上,這個dp在\(w_i\leq 2\)時正確性顯然,實際原理是與貪心一致的
而當\(w_i\leq 3\)時,由於轉移不完全,在實際隨機資料測試中發現
(ps:由於比較難搞暴力,所以只有測試\(n\leq 1000\)的)
1.可能存在一個位置的dp值沒有被轉移到
2.可能存在若干個位置(約0~1/60的比率,大部分情況都在1/200以下)的dp值錯誤
3.這個錯誤率在權值值域越小時錯誤率越低,在值域為10時幾乎不會錯(霧)
\[\ \]
這似乎可以解釋為什麼WC出題人沒有卡掉這個錯誤的dp,因為真的比較難卡?
\[\ \]
然後我和旁邊的一位 頂級亂搞選手lbc 一起亂搞這個演算法,發現
1.每次多列舉幾個物品
效果不佳
\[\ \]
2.記錄前\(k\)大不同的dp值(實際上,是指通過比較決策是否相同來判斷dp值是否相同)
當\(w_i\leq 3\)時,\(k\)達到3後幾乎不可能錯(近萬組沒鍋)
當\(w_i\leq 4\)時,\(k\)達到6後幾乎不可能錯 (實際還是出現過錯誤,要想完全正確比較難,但是如果調整到\(k=11\)時上千組可能才會有一個位置不一樣)
測試了\(w_i\leq 7\)左右的情況
發現想要完全正確很難,但是調整\(k\)的大小後,總是可以讓正確率非常高(然而這個\(k\)可能比較大,甚至需要記錄幾十個)
並且發現出現錯誤的位置幾乎只有沒有被dp到的那一個位置
ps:以上資料均對於\(n\leq 1000\),因為n大了暴力不會寫。。。
附一下評測的程式碼,希望這個問題能夠得到神仙的完美解決!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef pair <int,int> Pii;
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=1e3+10;
int n,m;
//int dp[N*10][12],s[N*10];
int A[12][N];
int a[N],b[N];
const int D=30;
struct Node{
int dp[11],s;
bool operator != (const Node __) const {
if(rand()%5==0) rep(i,1,10) if(dp[i]!=__.dp[i]) return 1;
return s!=__.s;
}
bool operator < (const Node __) const {
return s<__.s;
}
void Add(int x){
s+=A[x][dp[x]];
dp[x]++;
}
} dp[N*10][D];
void Part2(){
static int dp[N*10];
memset(dp,-63,sizeof dp),dp[0]=0;
rep(i,1,n) drep(j,m,a[i]) if(dp[j-a[i]]>=0) cmax(dp[j],dp[j-a[i]]+b[i]);
//rep(i,1,m) cmax(s[i],s[i-1]),cmax(dp[i],dp[i-1]);
//rep(i,0,m) cout<<dp[i]<<' '<<s[i]<<endl;
int sum=0,c=0;
rep(i,1,n) sum+=a[i];
rep(i,0,m) if(dp[i]>=0) {
if(dp[i]!=::dp[i][0].s) {
//assert(0);
c++;
cerr<<"#"<<i<<' '<<dp[i]<<' '<<::dp[i][0].s<<endl;
}
}
if(c) fprintf(stderr,"## %d / %d\n",c,sum);
//rep(i,0,m) if(dp[i]>=0) {
//if(s[i]!=dp[i])
//cerr<<"#"<<i<<' '<<s[i]<<' '<<dp[i]<<endl;
//}
}
int main(){
//freopen("test.in","r",stdin);
rep(kase,1,rd()) {
n=rd(),m=rd();
rep(i,0,m) rep(j,0,D-1) dp[i][j].s=-1;
dp[0][0].s=0;rep(i,1,10) dp[0][0].dp[i]=1;
rep(i,0,10) A[i][0]=0;
rep(i,1,n) {
a[i]=rd(),b[i]=rd();
A[a[i]][++A[a[i]][0]]=b[i];
}
rep(i,1,10) sort(A[i]+1,A[i]+A[i][0]+1,greater<int>());
//int ans=0;
rep(i,0,m) rep(j,0,D-1) if(~dp[i][j].s) {
//if(i && s[i-1]>s[i]) {
//s[i]=s[i-1];
//rep(j,1,10) dp[i][j]=dp[i-1][j];
//}
rep(d,1,min(10,m-i)) {
if(dp[i][j].dp[d]<=A[d][0]) {
Node t=dp[i][j]; t.Add(d);
rep(k,0,D-1) if(~t.s) {
if(dp[i+d][k]<t) {
if(!k || dp[i+d][k-1]!=t) swap(dp[i+d][k],t);
else break;
}
}
}
}
//ans^=s[i];
//cout<<"#"<<i<<' '<<s[i]<<' '<<dp[i][1]<<' '<<dp[i][2]<<' '<<dp[i][3]<<endl;
}
//rep(i,0,m) if(~dp[i][0].s && ~dp[i][1].s) cerr<<"#"<<i<<' '<<(dp[i][0]!=dp[i][1])<<endl;
//puts("");
Part2();
//printf("%d\n",ans);
}
}