luogu P5202 [USACO19JAN]Redistricting P
题面传送门
吸了口氧一发最优解可还行。把\(G\)设为\(1\),把\(H\)设为\(-1\),\(sum\)为前缀和。
显然有\(O(nk)\)的\(dp\):\(dp_i=\min\limits_{j=\max(1,i-k)}^{i-1}{dp_j+(sumi\geq sumj)}\)
这东西两个看起来不好优化。
但是注意后面那个只能是\(1/0\),所以只要\(dp_i<dp_j\)那么\(i\)的转移就一定不会比\(j\)的转移劣。
那么就可以按照\(dp_i\)单调队列了,注意\(dp_i\)相同要判\(sum_i\)的大小关系。
时间复杂度\(O(n)\)
代码实现:
#include<cstdio>
using namespace std;
int n,m,k,x,y,z,q[300039],head,tail,sum[300039],dp[300039];
char s;
inline bool cmp(int x,int y){return dp[x]==dp[y]?sum[x]<sum[y]:dp[x]>dp[y];}
int main(){
freopen("1.in","r",stdin);
register int i,j;
scanf("%d%d",&n,&k);
for(i=1;i<=n;i++){
s=getchar();
while(s<'A'||s>'Z') s=getchar();
sum[i]=sum[i-1]+(s=='G'?1:-1);
}
for(i=1;i<=n;i++){
while(head<=tail&&q[head]+k<i) head++;
j=q[head];dp[i]=dp[j]+(sum[i]-sum[j]>=0);//printf("%d ",dp[i]);
while(head<=tail&&cmp(q[tail],i)) tail--;q[++tail]=i;
}
printf("%d\n",dp[n]);
}