题目大意
已知序列 (A) 以及序列 (B) 的和 (m) 要求对每一个 (a_i) 分配一个 (b_i) 使得每次分配完之后 (a_i+b_i) 都是 (A) 序列中所有数字的最大值。
每次修改完 (a_i o a_i+b_i).
Solution:
看到 (n) 的范围就会想到状压:设 (f[S,i,j,k]) 表示已经揭榜的人的集合为 (S,) 上一个揭榜的人是 (i,) 他的 (b) 是 (j) 还剩下 (k) 的题目数能用。复杂度是 (O(2^{n}nm^2))
那么看到后面那两个东西很不顺眼……和 (m) 有关的维度必须被砍掉。
观察到题面的一条重要信息: (b_i)是单调不降的。
那么这个东西怎么用呢?先暂且不谈,考虑一下我们维护 (b) 的实际意义有多大。
我们维护它完全是因为后面的暴力转移需要它,它和我们最后的答案 一点关系都没有
那么,我们考虑一下有多少方案是一样的:对于一个固定下来的排名,分配 (b) 的方式有很多,但是这种方案只有一个。
从这里就能看出来维护 (b) 的冗余性质了。考虑对于一个已知的排名序列 (p,) (b) 的最优分配(即做到最小)就是每次尽量让 (b) 相等。
那么每一个 (b) 的作用无非就是让 (p_{i+1}+b_{i+1}) 与前一项尽量接近,以此来保证我们选择的 (sum b) 最小。
剩下的和 (m) 的差距都可以在最后一项补回来。
但是值得注意的是,我们这样看似漏下很多东西,但实际上,这样分配(b) 所丢失的仅仅只是同一个排列下不同的 (b) 的排列方案,而这些方案数并不是我们所需要的。
于是,我们可以思考题干中给定那个 (b) 不降的条件有啥用了。它也恰好规约了我们在揭榜一个人以后,后面所有人揭榜的增加量都是在 (b_i) 以上的。
考虑对 (b) 进行差分,就是考虑我们在 (a_i) 的时候对它加上了多少。这样,每一个差分后的值 (c_i) 都会在后面的揭榜过程中对 (sum b) 贡献 ((n-i+1)c_i) 的值。
这样我们就会发现,实际上我们已经不需要维护 (b_j) 了。只要知道当前状态下最后来的人和 pre-state 最后进来的人,就可以根据他们的 (a_i) 推算出最小代价了。因为我们只需要知道最小代价。
至于为什么只需要最小代价……因为我们对每一种排名序列只需要求出一个 (sum b) 最小的就可以了。
于是可以预处理 (cost[i][j]) 表示将 (a_i) 变成 (a_j) 的最小代价。
注意一下题目中给的编号的细节。
那么,现在的方程就可以被我们转化为: (f[S][i][j]) 表示揭榜的人状态为 (S,) 最后一个进来的人是 (i,) 一共用掉了 (j) 题的方案数。
于是我们可以枚举状态,枚举 (i,pre) 从而转移。转移复杂度 (O(2^n n^2 m))
注意用一些技巧优化,比如 lowbit
回过头来想一下,这个题去掉 (b) 的限制,我们实际上用了什么样的思想?
想到了差分,而这一步骤实际上是把每一个人的贡献给分开了——分成了对于全局的贡献。
分开之后呢?我们发现,可以通过对代价的提前计算来求解这个题。
于是,我们就可以把 (b) 这一维度放到 代价提前计算 这一部分中了。
所以有一点启示:有时候可以考虑把不想要的状态提前计算掉。
当然需要一些转化。
最后提一下初始化的问题:每次滚榜之后的最大值一定是大于最初始序列的最大值的,而这个最大值也是原序列唯一一个有资格当榜上最大值的数,也就是说,它的所有榜一值上最小的。
所以我们需要计算一个状态的最小值能不能满足条件,这同时也是最开始状态转移——转移到最初榜首的位置,这样才是最小的。这是最小代价,注意。
考场时候没做出来 现在终于明白了)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,a[200];
int f[1<<14][14][501];
int cost[14][14],maxn[14];
int num[1<<14],pw[1<<14];
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
inline int pop_count(int x){
int res=0;
for(int j=x;j;j-=lowbit(j))res++;
return res;
}
void solve(){
scanf("%lld%lld",&n,&m);
for(int i=0;i<=13;++i)pw[1<<i]=i;
for(int i=0;i<n;++i)scanf("%lld",&a[i]);
for(int i=0;i<n;++i)
for(int j=0;j<n;++j){
//min cost{i o j}
cost[i][j]=Max(a[j]-a[i]+(i>j),0);
maxn[i]=Max(maxn[i],cost[i][j]);
}
for(int i=0;i<(1<<n);++i)num[i]=pop_count(i);
for(int i=0;i<n;++i)if(m>=n*maxn[i])f[1<<i][i][n*maxn[i]]=1;//mean that 'i' can be any number
//最优策略下的数字一定最后不高于变成最大值的代价,所以初始化只能处理掉变成最大值所消耗的代价
for(int i=1;i<(1<<n);++i){
if(num[i]<=1)continue;
for(int j=i;j;j-=lowbit(j)){
int state=i-lowbit(j);//state/j
int pre=pw[lowbit(j)];//pos_j
for(int k=state;k;k-=lowbit(k)){
int pstate=lowbit(k);//1<<p
int pos=pw[pstate];
if(pos==pre)continue;
int g=cost[pre][pos]*(n-num[state]);
for(int v=g;v<=m;++v){
f[i][pre][v]+=f[state][pos][v-g];
}
}
}
}
int ans=0;
for(int i=0;i<n;++i)
for(int j=0;j<=m;++j)
ans+=f[(1<<n)-1][i][j];
cout<<ans<<endl;
}
signed main(){
freopen("111.txt","r",stdin);
solve();
return 0;
}