题意:长度为n的数组,数组中的每个元素的取值在1-k的范围内或者是-1,-1代表这个元素要自己选择一个1-k的数字去填写,然后要求填完的数组中不能出现连续长度大于len的情况,询问填空的方案数。
题解:初始思想是设置一个dp[][],数组第一维代表当前连续长度,第二维代表选择的数字。那最终答案就是最后的dp数组元素之和。
然后考虑更新这个数组:
当前元素是-1的话,那么对于每个dp[1][x] (1<=x<=k),他就是上一次dp数组元素之和 - 上一次符合dp[i][x]的元素之和(也就是去掉自己,把别的都加上)。
对于每个dp[i][x] (1<i<len, 1<=x<=k),他就是上一次对应的dp[i - 1][x]。①
当前元素不是-1,那么对于当前元素对应的值x,更新方法是一样的。与此同时,把别的不等于当前元素的值的dp数组清空。
接着考虑len的限制,那么len的限制其实就是要我们在更新dp的同时,去掉某个元素连续长度大于等于len的部分。②
也就是说对于更新提出了两个要求,第一个就是可以快速滑动(①要求),第二个就是快速删除位于一端的数字(②要求),那么队列可以很好的满足要求。
#include<bits/stdc++.h> using namespace std; const int P = 998244353; int a[100005], S[108], sum[108][2]; queue<int> q[108]; int main(){ int n, k, len, Sum[2]; scanf("%d%d%d", &n, &k, &len); for(int i = 1; i <= n; i++){ scanf("%d", a + i); } if(k == 1){ if(len <= n) return puts("0"),0; else return puts("1"),0; } if(len == 1) return puts("0"),0; if(a[1] == -1){ for(int i = 1; i <= k; i++) S[i] = sum[i][0] = 1, q[i].push(1); Sum[0] = k; } else S[a[1]] = sum[a[1]][0] = 1, q[a[1]].push(1), Sum[0] = 1; for(int i = 2; i <= n; i++){ int ii = i & 1; Sum[ii ^ 1] = 0; if(a[i] == -1){ for(int j = 1; j <= k; j++){ q[j].push((Sum[ii] - sum[j][ii] + P) % P); sum[j][ii ^ 1] = Sum[ii]; if(++S[j] == len) { --S[j]; sum[j][ii ^ 1] = (sum[j][ii ^ 1] - q[j].front() + P) % P; q[j].pop(); } Sum[ii ^ 1] = (Sum[ii ^ 1] + sum[j][ii ^ 1]) % P; } } else{ q[a[i]].push((Sum[ii] - sum[a[i]][ii] + P) % P); sum[a[i]][ii ^ 1] = Sum[ii]; if(++S[a[i]] == len) { --S[a[i]]; sum[a[i]][ii ^ 1] = (sum[a[i]][ii ^ 1] - q[a[i]].front() + P) % P; q[a[i]].pop(); } for(int j = 1; j <= k; j++) if(j != a[i]){ while(!q[j].empty()) q[j].pop(); sum[j][ii ^ 1] = 0; S[j] = 0; } Sum[ii ^ 1] = sum[a[i]][ii ^ 1]; } } printf("%d ", Sum[((n + 1) & 1)]); }