codeforces 1428G Lucky Numbers (貪心+dp)
阿新 • • 發佈:2020-10-25
題目連結: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;
}