理想乡题解 (线段树优化dp)

题面



思路概述

首先,不难想到本题可以用动态规划来解,这里就省略是如何想到动态规划的了。

转移方程 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

时间: 2024-09-30 04:49:48

理想乡题解 (线段树优化dp)的相关文章

【bzoj3939】[Usaco2015 Feb]Cow Hopscotch 动态开点线段树优化dp

题目描述 Just like humans enjoy playing the game of Hopscotch, Farmer John's cows have invented a variant of the game for themselves to play. Being played by clumsy animals weighing nearly a ton, Cow Hopscotch almost always ends in disaster, but this has

BZOJ 1835 基站选址(线段树优化DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1835 题意:有N个村庄坐落在一条直线上,第 i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci.如果在距离第i个村 庄不超过Si的范围内建立了一个通讯基站,那么就成它被覆盖了.如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi.现在的问题是,选择基站的位 置,使得总费用最小. 思路: 另外,程序中的n=n+1,m=

[Poi2010]Monotonicity 2 (线段树优化DP)

题目描述 给出N个正整数a[1..N],再给出K个关系符号(>.<或=)s[1..k].选出一个长度为L的子序列(不要求连续),要求这个子序列的第i项和第i+1项的的大小关系为s[(i-1)mod K+1].求出L的最大值. 输入 第一行两个正整数,分别表示N和K (N, K <= 500,000).第二行给出N个正整数,第i个正整数表示a[i] (a[i] <= 10^6).第三行给出K个空格隔开关系符号(>.<或=),第i个表示s[i]. 输出 一个正整数,表示L的

降临(线段树优化dp)

降临 选定点i会有代价\(c_i\),如果一个区间j内的点全被选择,就可以获得回报\(p_j\).点数和区间个数\(<1e5\). 还以为是线段树优化网络流(50万个点200万条边看上去很可做的样子毕竟lbn说过网络流20万万条边完全没问题),没想到是个线段树dp. (虽然这两个线段树完全扯不上关系) 用\(f[i][j]\)表示考虑到第i个点,向左最近的尚未选定的点为j时的最大值.那么,i+1可以选也可以不选.不选i时,\(f[i][j]\rightarrow f[i+1][i+1]\).选i

题解:CF115E(线段树优化dp)

题目描述 你是一个赛车比赛的组织者,想在线性王国中安排一些比赛. 线性王国有n条连续的从左到右的道路.道路从左到右依次编号为从1到n,因此道路按照升序排列.在这些道路上可能会有几场比赛,每一场比赛都将使用这些道路的某个连续的子序列.而且,如果某场比赛举行了,你将获得一定数额的金钱.没有比赛在时间上重叠,所以每一段道路可以在多个比赛中使用. 不幸的是,所有道路的状况都不佳,需要修理.每条路都有与之相关的维修费用,你需要支付这笔费用来修理道路.只有在某场比赛中需要使用的所有道路都进行了修复,才能进行

Codeforces 833B 线段树优化 dp

Codeforces  833B  The Bakery 题意: n 个数要分成 k 块,每块的价值是其不同数的个数,问价值和最大是多少. tags: dp[i][j]表示前 j 个数分成 i 块的最大权值和,转移: dp[i][j] = max( dp[i-1][k] + val[k+1][j] ) , k是 1~j . 但这个过程其实并不好转移,要利用累加的特点,用线段树进行优化 (感觉我不看题解是想不到的,2333) 大概就是,对于第 i 层,我们假定已经知道了第 i-1 层,也就是求出了

【8.23校内测试】【贪心】【线段树优化DP】

$m$的数据范围看起来非常有问题??仔细多列几个例子可以发现,在$m<=5$的时候,只要找到有两行状态按位$&$起来等于$0$,就是可行方案,如果没有就不行. #include<iostream> #include<cstdio> #include<cstring> using namespace std; int cnt[1<<4+1], n, m; int main ( ) { freopen ( "prob.in",

bzoj 1835 基站选址(线段树优化Dp)

Description 题意:有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di 需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci 如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就成它被覆盖了 如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi 现在的问题是,选择基站的位置,使得总费用最小. Solution 首先可以想到dp,用dp[i][j]表示前i个村庄建了j个通讯站且第j个建在i处 dp[i][j]=min(dp[k][

hdu45221——小明系列问题——小明序列 线段树优化dp

小明系列问题--小明序列 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 1918    Accepted Submission(s): 583 Problem Description 大家都知道小明最喜欢研究跟序列有关的问题了,可是也就因为这样,小明几乎已经玩遍各种序列问题了.可怜的小明苦苦地在各大网站上寻找着新的序列问题,可是找来找