题面
思路概述
首先,不难想到本题可以用动态规划来解,这里就省略是如何想到动态规划的了。
转移方程 f[i]=min(f[j]+1)(max(i-m,0)<=j<i 且j符合士兵限定)
注意要用 max(i-m,0)以防止越界
我们先用两个数组sl,sa分别统计1~i个士兵中有多少个Lencer和Archer
然后在max(i-m,0)中寻找符合条件的j
(1).两种士兵相差不超过k。
这个好说abs((sl[i]-sl[j])-(sa[i]-sa[j]))<=k
不要忘了第二种情况!!!
我也就小小地吃了个亏
(2).全为一种士兵。
sl[i]-sl[j]==i-j || sa[i]-sa[j]==i-j
接下来上代码
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int N=25e4+10; int d[N],sl[N],sa[N],n,m,k,f[N]; char s[5]; int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) { scanf("%s",s); if(s[0]==‘A‘) sa[i]=sa[i-1]+1,sl[i]=sl[i-1]; else if(s[0]==‘L‘) sl[i]=sl[i-1]+1,sa[i]=sa[i-1];//统计 } memset(f,0x7f,sizeof(f));//寻找最少需要多少条船故设为无穷大 f[0]=0; for(int i=1;i<=n;i++) { int be=max(i-m,0);//边界 for(int j=be;j<i;j++)//i-j+1~i { int a=sl[i]-sl[j],b=sa[i]-sa[j]; if(abs(a-b)<=k || a==i-j || b==i-j) f[i]=min(f[i],f[j]+1);//dp } } printf("%d\n",f[n]); return 0; }
复杂度为O(n*m),显然会超时,勉强70分
于是我们想到了优化
仔细观察dp方程,我们发现f[j]会反复求最值,符合单调队列优化的条件(这不是我们的正文,故此处不详细讲,有兴趣可以去看一本通5.5)
#include <cstdio> #include <cstdlib> #include <cstring> #include <queue> using namespace std; const int N=250010; int sl[N],sa[N],n,m,K,f[N],now=1; struct node { int d,k; node(int dd,int kk) { d=dd,k=kk; } node(){ } bool operator <(const node&o)const { return d>o.d; } }; priority_queue <node> q; char s[5]; void find() { if(now-q.top().k>m) { q.pop(); find(); return ; } node t=q.top(); int a=sl[now]-sl[t.k],b=sa[now]-sa[t.k],dua=now-t.k; if(abs(a-b)<=K || a==dua ||b==dua)//若符合必为最小值 { f[now]=f[t.k]+1; q.push(node(f[now],now)); return ; } else { q.pop(); find(); if(t.k+m>now) q.push(t); } } int main() { scanf("%d%d%d\n",&n,&m,&K); for(int i=1;i<=n;i++) { scanf("%s",s); if(s[0]==‘A‘) sa[i]=sa[i-1]+1,sl[i]=sl[i-1]; else if(s[0]==‘L‘) sl[i]=sl[i-1]+1,sa[i]=sa[i-1]; } q.push(node(0,0)); for(;now<=n;now++) find(); printf("%d\n",f[n]); return 0; }
然而。。。
这个60分。。。
是我单调队列不够骚,还是暴力出奇迹23333333
正文开始
大量文字预警
如果用最简单的想法,就是O(n * m)的DP。我们要用线段树,把m压成log(m)。
为了方便地统计某一段中弓箭手与枪兵人数的差值,我们可以用一遍扫描,求出队伍开头到某个位置时,这两个兵种的人数差。
如果只有第一个“人数差不超过k”的要求,当前在处理第i个人,前1~i人中兵种人数差为j,那么前i个人
所需要的最小船数f[i],就是在f[i - 1]~f[i - m]中,选出兵种人数差在(j - k)~(j + k)间的,用它的f值加1来尝试更新f[i]。如果把某种兵种人数差的最小f值做成点
,那么我们需要的,就是一个求区间最小值的程序。
这,就让线段树有了用武之地。 然后,我们再处理另一种情况:连续的不超过m个人。
为了这个,我们同样可以维护一个最小堆,存最后的连续相同的最多m个人的f值。
满分代码(我知道你们只想看这个)
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <queue>
using namespace std;
const int N=25e4+10,M=5e5+10;
int con,d[N],n,m,k,re[M],tot,rt,nn,ad,mi,ma,dp[N];
bool flag[N];
struct node
{
int l,r,lc,rc,mn,fa;
}f[M*2];//一个找区间最小值的线段树
struct mode
{
int d,k;
mode(int dd,int kk)
{
d=dd,k=kk;
}
mode()
{
}
bool operator <(const mode&o)const
{
return d>o.d;
}
};
priority_queue <mode> q[M];
void build(int &g,int l,int r)
{
g=++tot,f[g].mn=1<<30;
f[g].l=l,f[g].r=r;
if(l==r)
{
re[l]=g;
return ;
}
int mid=(l+r)>>1;
build(f[g].lc,l,mid);
build(f[g].rc,mid+1,r);
f[f[g].lc].fa=f[f[g].rc].fa=g;
}
void push_up(int g)
{
f[g].mn=min(f[f[g].lc].mn,f[f[g].rc].mn);
}
void add(int g,int k)
{
q[f[g].l].push(mode(dp[k],k));
if(f[g].mn>dp[k])
{
f[g].mn=dp[k];
g=f[g].fa;
while(g!=0)
{
push_up(g);
g=f[g].fa;
}
}
}
void cut(int g,int i)//删点
{
flag[i]=true;
int mm=f[g].l,last=f[g].mn;
while(!q[mm].empty() && flag[q[mm].top().k]) q[mm].pop();
if(!q[mm].empty()) f[g].mn=q[mm].top().d;
else f[g].mn=1<<30;
if(f[g].mn!=last)
{
last=f[g].mn;
g=f[g].fa;
while(g!=0)
{
push_up(g);
g=f[g].fa;
}
}
}
int get(int g,int l,int r)
{
if(f[g].l>=l && f[g].r<=r)
return f[g].mn;
int mid=(f[g].l+f[g].r)>>1;
if(r<=mid) return get(f[g].lc,l,r);
else if(l>mid) return get(f[g].rc,l,r);
else return min(get(f[g].lc,l,mid),get(f[g].rc,mid+1,r));
}
char s[N][5];
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]);
if(s[i][0]==‘A‘) d[i]=d[i-1]+1;
else d[i]=d[i-1]-1;//求人数差
mi=min(mi,d[i]),ma=max(ma,d[i]);//求线段树范围
}
ad=1-mi,nn=ma+ad;
for(int i=0;i<=n;i++) d[i]+=ad; //必须转成>=0的值才能存到re中
build(rt,1,nn),add(re[ad],0);//建树
f[0].mn=1<<30;
for(int i=1;i<=n;i++)
{
if(s[i][0]==s[i-1][0]) con++;
else con=0;//统计目前有多少个连续相同的士兵
if(i>m) cut(re[d[i-m-1]],i-m-1);//删去越界的点
dp[i]=get(rt,max(d[i]-k,1),min(d[i]+k,nn))+1;
for(int j=max(i-con-1,i-m);j<i;j++)//处理上文中第二种情况
dp[i]=min(dp[i],dp[j]+1);
add(re[d[i]],i);//加点
}
printf("%d\n",dp[n]);
return 0;
}
Thanks?(?ω?)?(第一次发题解,有不足请指教)
2019.10.14
原文地址:https://www.cnblogs.com/hsez-cyx/p/11669945.html