1. 程式人生 > 實用技巧 >關於物品大小較小的01揹包

關於物品大小較小的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\}\)

的,然後和3的暴力合併求出\(L\)個位置的結果,複雜度為\(O(NL)\)

\[\ \]

作為亂搞選手,然後想要提一下這個奇怪的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);
	}
}