1. 程式人生 > 其它 >21.6.30 t1

21.6.30 t1

tag:狀壓dp,貪心


屑模擬賽三道題資料全部木大,最高可能得分30

首先要猜想一個結論,除去限定長度,剩下的一定是儘量點滿一個技能點。所以最終技能樹應該是限定長度的技能+一堆點滿的技能+剩下的全部點到一個技能上。

感性證明:如果把某個技能送一個技能點給另外一個技能更優,那麼直接把全部點都送過去一定更優(根據題目給定的性質:差分序列嚴格遞增)

然後問題變為,要選出幾個點滿的技能,還要選出幾個固定長度的技能。

假設我們已經選擇好了固定長度的技能,那麼點滿的技能一定是剩下的技能中 \(a_{i,k}\) 最大的幾個。

所以按 \(a_{i,k}\) 排序,設 \(f_{i,S}\) 表示前 \(i\)

個,滿足的條件集合為 \(S\)

這樣做的好處在於,\(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;
}