大致题意:
有a,ba,ba,b两种属性,给定一个数组ccc,长度为nnn,其中有mmm个为000。1≤m≤5000;m<n≤2⋅1061≤m≤5000
; m<n≤2⋅10^61≤m≤5000;m<n≤2⋅106。如果ci=0c_i=0ci=0,那么可以选择一种属性,使其值加1,如果ci>0c_i>0ci>0,那么会有一次检查,如果a>cia>c_ia>ci,那么通过检查。ci<0c_i<0ci<0的时候检查bbb属性,如果b>∣ci∣b>\left | c_i \right |b>∣ci∣,那么通过检查。
现在询问,在某种分配策略情况下,最多可以通过多少检查。
解
设sss为截至目前为止遇到的总的分配点数。我们首先可以想到一个很朴素的dpdpdp:设f[i][j]f[i][j]f[i][j]表示前iii个记录中有jjj个属性点分配给了aaa的前提下,通过的最多的检查数。那么可以得到转移方程:
ci=0:f[i][j]=max(f[i−1][j],f[i−1][j−1])
c_i=0:f[i][j]=max(f[i-1][j],f[i-1][j-1])
ci=0:f[i][j]=max(f[i−1][j],f[i−1][j−1])
ci>0:f[i][j]=f[i−1][j]+1(j>=ci),f[i][j]=f[i−1][j](j<ci)
c_i>0:f[i][j]=f[i-1][j]+1(j>=c_i),f[i][j]=f[i-1][j](j<c_i)
ci>0:f[i][j]=f[i−1][j]+1(j>=ci),f[i][j]=f[i−1][j](j<ci)
ci<0:f[i][j]=f[i−1][j]+1(s+ci>=j),f[i][j]=f[i−1][j](s+ci<j)
c_i<0:f[i][j]=f[i-1][j]+1(s+c_i>=j),f[i][j]=f[i-1][j](s+c_i<j)
ci<0:f[i][j]=f[i−1][j]+1(s+ci>=j),f[i][j]=f[i−1][j](s+ci<j)
但是这样的时间复杂度比较高。
考虑优化:我们发现,多数操作花在了第二种和第三种情况中的加法。而第一种情况只有mmm次,非常少。那么我们可以考虑用差分数组来代替原先的加法,而对于第一种情况,把差分数组的信息暴力传给fff数组更新以后再转移方程。
我们维护一个f[i]f[i]f[i]的差分数组ddd,用来实现O(1)O(1)O(1)的区间加法操作。
每次遇到ci=0c_i=0ci=0的时候,我们把ddd的信息传给当前状态下的f[i]f[i]f[i],然后再更新f[i]f[i]f[i]。
code
void solve() {
int n, m;
cin >> n >> m;
int s = 0;
vector<int> dp(m + 1);
vector<int> cf(m + 2);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x == 0) {
//还原
int temp = 0;
for (int j = 0; j <= m; j++) {
temp += cf[j];
cf[j] = 0;
if (j <= s)
dp[j] += temp;
}
for (int j = m; j >= 1; j--) {
dp[j] = max(dp[j], dp[j - 1]);
}
s++;
} else if (x > 0) {
cf[x]++;
cf[m + 1]--;
} else {
if (s + x < 0) continue;
cf[0]++;
cf[s + x + 1]--;
}
}
int temp = 0;//注意这里,如果c数组最后全是检查,那么我们就没机会把差分数组信息传回
//来了 所以必须在结束以后再更新一次
for (int j = 0; j <= m; j++) {
temp += cf[j];
if (j <= s)
dp[j] += temp;
}
int mi = 0;
for (int i = 0; i <= m; i++)
mi = max(mi, dp[i]);
cout << mi << endl;
}