1. 程式人生 > 實用技巧 >codeforces 1428G Lucky Numbers (貪心+dp)

codeforces 1428G Lucky Numbers (貪心+dp)

題目連結:https://codeforces.com/problemset/problem/1428/G1

仔細分析發現,貪心地選取 0,3,6,9 組成的數字一定是最優的,
而且這樣的貪心策略,最多會有一個數字,不是全部由 0,3,6,9組成的

考慮按位分組dp,每組都有體積為:3,6,9 * 位權,價值為的物品
於是問題轉變成了:先分組完全揹包,每組物品中最多選 K 個,最後將每組合並,每組物品最多選 1 個
但時間複雜度為\(O(NMK)\),直接起飛
考慮優化:
對於完全揹包最多取 K 個的限制,考慮將 K 二進位制分組,將每種物品拆成按 K 的二進位制分組組合的物品,
這樣就轉化成了 0/1 揹包,複雜度為\(O(NMlogK)\)

對於簡單版,詢問只有一次,考慮只選\(K-1\)個物品,列舉那個特殊的數字 x ,統計答案
還有個小性質:體積為6,9的物品都可以拆成為 3 的物品,就變成最多選 3*(k-1) 個物品了,必須這樣優化,不然還是過不去

將各組物品合併時:
直接在每組內對該組進行揹包更新答案,這樣可以保證當前組物品不被重複選取

未使用小性質優化的程式碼(TLE)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 1000000;

int k,m,num,cntf,cntg;
int di[maxn];
ll n,F[10],f[21][maxn],g[maxn],v[1000],w[1000],vg[1000],wg[1000];

int qsm(int i,int po){
	int res = 1;
	while(po){
		if(po&1) res *= i;
		po >>= 1;
		i *= i;
	}
	return res;
}

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
	num = 0;
	k = read();
	int K = k-1;
	int t = 0;
	while(K - (1<<t) > 0){ // 二進位制分組 
		di[++num] = 1<<t;
		K -= (1<<t);
		++t;
	}
	di[++num] = K;
	for(register int i=0;i<6;++i) F[i] = read();
	m = read();
	
	n = read();
	
	memset(f,-1,sizeof(f));
	f[0][0] = 0;
	
	memset(g,0,sizeof(g));

	for(register int d=1;d<=6;++d){ //  dp 
		cntf = 0; cntg = 0;
		for(int j=1;j<=num;++j){
			v[++cntf] = 0, w[cntf] = 0;
		}
		
		for(register int i=1;i<=3;++i){
			ll wei = 1ll * (3*i) * qsm(10,d-1);
			ll val = 1ll * i * F[d-1];
			
			for(register int j=1;j<=num;++j){
				v[++cntf] = 1ll * di[j] * val;
				w[cntf] = 1ll * di[j] * wei;
			}
		}
		
		if(d>1) for(int i=0;i<=n;++i) f[0][i] = f[num][i];
		for(register int i=1;i<=cntf;++i){
			for(register int p=1;p<=num;++p){
				for(register int j=n;j>=w[i];--j){	
					if(f[p-1][j-w[i]] != -1) f[p][j] = max(f[p][j],f[p-1][j-w[i]] + v[i]);
				}
			}
		}
	}
	
    ll ans = 0;
	for(register int x=0;x<=n;x++){
		int tmp = x, o = 1;
		ll ta = 0;
		while(tmp > 0){
			if((tmp%10) % 3 == 0){
				ta += 1ll * (tmp % 10)/3 * F[o-1];
			}
			tmp /= 10;
			++o;
		}
		ans = max(ans,f[num][n-x]+ ta);
	}
	
	printf("%lld\n",ans);
	
	return 0;
}

AC程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 1000005;
const ll inf = (1LL << 58LL);

int k,m,num,cntf,cntg;
int di[maxn];
ll n,F[10],dp[maxn];

int qsm(int i,int po){
	int res = 1;
	while(po){
		if(po&1) res *= i;
		po >>= 1;
		i *= i;
	}
	return res;
}

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
	num = 0;
	k = read();
	int K = k-1;
//	for(int i=1;i<=num;++i) printf("%d ",di[i]); printf("\n");
	
	for(int i=0;i<6;++i) F[i] = read();
	m = read();
	
	n = read();
	fill(dp,dp+n+1,-1ll);
	dp[0] = 0; 
	for(int d=1;d<=6;++d){
		ll left = 3 * (k-1);
		ll group = 1;
		while(left > 0){
			group = min(group,left);
			ll wei = 1ll * group * qsm(10,d-1) * 3 ;
			ll val = 1ll *  group * F[d-1];
			
			for(int i=n; i>=wei; i--){
				if(dp[i-wei]!=-1ll) dp[i] = max(dp[i],dp[i-wei] + val);
			}
			left -= group;
			group *= 2;
		}
	}
	
    ll ans = 0;
	for(int x=0;x<=n;x++){
		int tmp = x, o = 1;
		ll ta = 0;
		while(tmp > 0){
			if((tmp%10) % 3 == 0){
				ta += 1ll * (tmp % 10)/3 * F[o-1];
			}
			tmp /= 10;
			++o;
		}
		if(dp[n-x]!=-1) ans = max(ans,dp[n-x]+ ta); //
	}
	
	printf("%lld\n",ans);
	
	return 0;
}

對於加強版,由於有多組詢問,不可以再列舉 x,
因為已經處理出前(K-1)個物品的 \(DP\) 表,按位考慮最後一個數字,更新 \(DP\)
注意最後更新的時候要更換列舉順序,wei維在外,物品維在內,這樣可以保證每組物品最多隻選一個

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 1000010;
const ll inf = (1ll << 58ll);

int k,n,q;
int ten[10] = {1,10,100,1000,10000,100000};
ll F[7];
ll dp[maxn],g[maxn];

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
	k = read();
	for(int i=0;i<6;++i) F[i] = read();
	
	fill(dp,dp+1+999999,-inf);
//	for(int i=1;i<=10;++i) printf("%lld\n",dp[i]);
	dp[0] = 0;
	
	for(int d=1;d<=6;++d){
		ll left = 1ll * 3 * (k-1);
		ll group = 1ll;
		
		while(left > 0){ // 二進位制分組 
			group = min(group,left);
			
			ll val = 1ll * group * F[d-1];
			ll wei = 1ll * 3 * group * ten[d-1];
			
			for(int i=999999;i>=wei;--i){
				dp[i] = max(dp[i],dp[i-wei] + val);
			}
			left -= group;
			group *= 2;
		}
		
	}

	for(int d=1;d<=6;++d){
		for(int i=999999;i>=0;i--){
			for(int j=0;j<=9;++j){
				ll val, wei;
				if(j==3 || j==6 || j==9) val = 1ll * (j/3) * F[d-1];
				else val = 0;
				wei = 1ll * j * ten[d-1];
				
				if(wei > i) break;
				dp[i] = max(dp[i],dp[i-wei] + val);
			}
		}
		
	}	
	
	q = read();
	
	for(int i=1;i<=q;++i){
		n = read();
		printf("%lld\n",dp[n]);
	}
	
	return 0;
}