21.6.30 t1
阿新 • • 發佈:2021-06-30
tag:狀壓dp,貪心
屑模擬賽三道題資料全部木大,最高可能得分30
首先要猜想一個結論,除去限定長度,剩下的一定是儘量點滿一個技能點。所以最終技能樹應該是限定長度的技能+一堆點滿的技能+剩下的全部點到一個技能上。
感性證明:如果把某個技能送一個技能點給另外一個技能更優,那麼直接把全部點都送過去一定更優(根據題目給定的性質:差分序列嚴格遞增)
然後問題變為,要選出幾個點滿的技能,還要選出幾個固定長度的技能。
假設我們已經選擇好了固定長度的技能,那麼點滿的技能一定是剩下的技能中 \(a_{i,k}\) 最大的幾個。
所以按 \(a_{i,k}\) 排序,設 \(f_{i,S}\) 表示前 \(i\)
這樣做的好處在於,\(i-|S|\) 就表示在前 \(i\) 大的技能中有多少個技能是沒有固定長度的,而我們又是按照 \(a_{i,k}\) 排序後從大到小進行dp,所以一定是能選就選。
注意 \(n\) 過大的情況,要和 \(k(m-S)\) 取 \(\min\)。
不過不知道資料有沒有這種情況。。
這個是可以輸出方案的,拿來對拍的程式碼
這個是過了對拍的程式碼
#include<bits/stdc++.h> using namespace std; template<typename T> inline void Read(T &n){ char ch; bool flag=false; while(!isdigit(ch=getchar()))if(ch=='-')flag=true; for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48)); if(flag)n=-n; } typedef long long ll; enum{ MAXN = 200005 }; int n, m, k, S; ll f[MAXN][1<<6]; int _a[MAXN], *a[MAXN]; int id[MAXN], lim[7]; inline bool cmp(const int &u, const int &v){return a[u][k]>a[v][k];} inline int val(int x, int y){return y?a[id[x]][y]:0;} inline void upd(ll &a, ll b){if(a<b) a = b;} ll pre[MAXN]; int cnt1[1<<6]; int main(){ freopen("1.in","r",stdin); freopen("11.out","w",stdout); Read(n); Read(m); Read(k); Read(S); a[1] = _a; for(int i=1; i<=m; i++){ id[i] = i; for(int j=1; j<=k; j++) Read(a[i][j]); if(i<m) a[i+1] = a[i]+k; } sort(id+1,id+m+1,cmp); for(int i=0; i<S; i++) Read(lim[i]); sort(lim,lim+S); S = unique(lim,lim+S)-lim; for(int i=0; i<S; i++) n -= lim[i]; n = min(n,(m-S)*k); if(n%k) lim[S] = n%k, S++; int num = n/k, tp = 1<<S; memset(f,0xcf,sizeof f); f[0][0] = 0; for(int i=0; i<tp; i++) cnt1[i] = cnt1[i>>1]+(i&1); for(int i=1; i<=m; i++){ for(int s=tp-1; ~s; s--) if(f[i-1][s]>=0){ for(int j=0; j<S; j++) if(~s>>j&1) upd(f[i][s|(1<<j)],f[i-1][s]+val(i,lim[j])); int dlt=0; if(i-cnt1[s]<=num) dlt = val(i,k); upd(f[i][s],f[i-1][s]+dlt); } } cout<<f[m][tp-1]<<'\n'; return 0; }