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;
}