题面


思路概述

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

转移方程 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+;
int d[N],sl[N],sa[N],n,m,k,f[N];
char s[];
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=;i<=n;i++)
{
scanf("%s",s);
if(s[]=='A') sa[i]=sa[i-]+,sl[i]=sl[i-];
else if(s[]=='L') sl[i]=sl[i-]+,sa[i]=sa[i-];//统计
}
memset(f,0x7f,sizeof(f));//寻找最少需要多少条船故设为无穷大
f[]=;
for(int i=;i<=n;i++)
{
int be=max(i-m,);//边界
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]+);//dp
}
}
printf("%d\n",f[n]);
return ;
}

复杂度为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

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

  1. Codeforces Round #426 (Div. 2) D 线段树优化dp

    D. The Bakery time limit per test 2.5 seconds memory limit per test 256 megabytes input standard inp ...

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

    题目描述 Just like humans enjoy playing the game of Hopscotch, Farmer John's cows have invented a varian ...

  3. BZOJ2090: [Poi2010]Monotonicity 2【线段树优化DP】

    BZOJ2090: [Poi2010]Monotonicity 2[线段树优化DP] Description 给出N个正整数a[1..N],再给出K个关系符号(>.<或=)s[1..k]. ...

  4. [AGC011F] Train Service Planning [线段树优化dp+思维]

    思路 模意义 这题真tm有意思 我上下楼梯了半天做出来的qwq 首先,考虑到每K分钟有一辆车,那么可以把所有的操作都放到模$K$意义下进行 这时,我们只需要考虑两边的两辆车就好了. 定义一些称呼: 上 ...

  5. POJ 2376 Cleaning Shifts (线段树优化DP)

    题目大意:给你很多条线段,开头结尾是$[l,r]$,让你覆盖整个区间$[1,T]$,求最少的线段数 题目传送门 线段树优化$DP$裸题.. 先去掉所有能被其他线段包含的线段,这种线段一定不在最优解里 ...

  6. 洛谷$P2605\ [ZJOI2010]$基站选址 线段树优化$dp$

    正解:线段树优化$dp$ 解题报告: 传送门$QwQ$ 难受阿,,,本来想做考试题的,我还造了个精妙无比的题面,然后今天讲$dp$的时候被讲到了$kk$ 先考虑暴力$dp$?就设$f_{i,j}$表示 ...

  7. D - The Bakery CodeForces - 834D 线段树优化dp···

    D - The Bakery CodeForces - 834D 这个题目好难啊,我理解了好久,都没有怎么理解好, 这种线段树优化dp,感觉还是很难的. 直接说思路吧,说不清楚就看代码吧. 这个题目转 ...

  8. 4.11 省选模拟赛 序列 二分 线段树优化dp set优化dp 缩点

    容易想到二分. 看到第一个条件容易想到缩点. 第二个条件自然是分段 然后让总和最小 容易想到dp. 缩点为先:我是采用了取了一个前缀最小值数组 二分+并查集缩点 当然也是可以直接采用 其他的奇奇怪怪的 ...

  9. Codeforces 1603D - Artistic Partition(莫反+线段树优化 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 学 whk 时比较无聊开了道题做做发现是道神题( 介绍一种不太一样的做法,不观察出决策单调性也可以做. 首先一个很 trivial 的 o ...

随机推荐

  1. 记录安装Python第三方包“tesserocr”的方法和遇到的坑

    1. 环境: 系统环境:Win7 32 位系统 Python版本: 3.6.5        虚拟环境为:Miniconda3 2. 共需要安装的模块: a. tesserocr b. tessera ...

  2. monorepo仓库管理方式探秘

    前言 随着功能和业务量级的飙升,前端代码量级也越来越大,管理运维的成本也进一步增加. 代码仓库的运营管理挑战也浮出水面. 主流方案有两种:一是multirepo式的分散式的独立仓库,二是monorep ...

  3. 并查集的超市问题---溜TM的

    三个月前我就错了,现在又错了,我就是个傻****** 服了,看图哇 打扰了... #include<cstdio> #include<iostream> #include< ...

  4. 如何利用腾讯云COS为静态博客添加动态相册

    前言 本文首发于个人网站Jianger's Blog,欢迎访问订阅.个人博客小站刚建站不久,想着除了主题里的功能外再添加上相册模块,于是半搜索半摸索把相册模块搞出来了,最后采用了利用腾讯云对象存储作图 ...

  5. 只用这 6 个字符,就可以写出任意 JavaScript 代码!

    你可能在网上见过有人用 几个不同的字符写的各种稀奇古怪的 JavaScript 代码,虽然看起来奇怪,但是能正常运行!比如这个: (!(~+[])+{})[--[~+""][+[] ...

  6. 对sql server查询速度的优化

    处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考 ...

  7. mysql授权用户权限

    mysql 用户管理和权限设置   用户管理 mysql>use mysql; 查看 mysql> select host,user,password from user ; 创建 mys ...

  8. 【题解】毒蛇越狱(FWT+容斥)

    [题解]毒蛇越狱(FWT+容斥) 问了一下大家咋做也没听懂,按兵不动没去看题解,虽然已经晓得复杂度了....最后感觉也不难 用FWT_OR和FWT_AND做一半分别求出超集和和子集和,然后 枚举问号是 ...

  9. 【题解】有标号的DAG计数3

    [HZOI 2015] 有标号的DAG计数 III 我们已经知道了\(f_i\)表示不一定需要联通的\(i\)节点的dag方案,考虑合并 参考[题解]P4841 城市规划(指数型母函数+多项式Ln), ...

  10. 1067 试密码 (20分)C语言

    当你试图登录某个系统却忘了密码时,系统一般只会允许你尝试有限多次,当超出允许次数时,账号就会被锁死.本题就请你实现这个小功能. 输入格式: 输入在第一行给出一个密码(长度不超过 20 的.不包含空格. ...