HDU 6107 Typesetting(倍增法 17多校第六场)

来源:互联网 发布:淘宝双十一红包口令 编辑:程序博客网 时间:2024/05/18 13:25
  • 题目大意

    在一个页面上有n个单词组成的一段话和一个图片,页面宽度、图片宽度、图片两边边距是确定的。告诉了你这段话中每个单词的长度,这些单词在页面中要满足:

    1. 图片上不能放单词
    2. 一行中的连续区间中的两个单词之间要有一个空格

    现在给出Q组询问,每次询问给出一个x和h,x表示图片的起始行数,h表示图片长度
    问这些单词加图片一共覆盖了多少行

  • 分析

    采用倍增的做法
    整个页面可以分为两个部分,有图片的行和没有图片的行
    f1[i][j]表示不含图片的部分以第i个单词作为开头,行数为2j的一段内单词的总数
    f2[i][j]表示含图片的部分以第i个单词作为开头,行数为2j的一段内单词的总数
    然后就是通过倍增的思想对三段分别进行处理

  • 总结
    这道题代码比较难写,边界条件处理起来比较复杂

  • 代码

#include<bits/stdc++.h>using namespace std;const int  MAXN=200005;int T;int n,w,pw,lw;//单词数,页面宽度,图片宽度,页左边距int a[MAXN];int Q;//查询次数int xi,hi;int mx;int f1[MAXN][30];//f1[i][j]表示从第i个单词开头,占据2^j行能放置的最大单词数int f2[MAXN][30];//f1[i][j]表示有图片的情况下从第i个单词开头,占据2^j行能放置的最大单词数void Init_ST(){    memset(f1,0,sizeof(f1));    memset(f2,0,sizeof(f2))    a[n+1]=w+1;//在最后添加一个虚拟的单词长度超过页面宽度    for(int i=1;i<=n;i++)//求出f1[i][1]    {        int tw=a[i];//宽度        int j=i+1;        while(tw+a[j]+1<=w){tw+=a[j]+1;j++;}        f1[i][0]=j-i;    }    for(int k=1;(1<<k)<=mx;k++)    {        for(int i=1;i<=n;i++)        {            int t=f1[i][k-1];            f1[i][k]=t+f1[i+t][k-1];        }    }    for(int i=1;i<=n;i++)    {        int j=i;        int tw=0;        if(tw+a[j]<=lw){tw+=a[j];j++;}        while(tw+a[j]+1<=lw) {tw+=a[j]+1;j++;}        tw=0;        int rw=w-pw-lw;        if(tw+a[j]<=rw){tw+=a[j];j++;}        while(tw+a[j]+1<=rw){ tw+=a[j]=1;j++;}        f2[i][0]=j-i;    }    for(int k=1;(1<<k)<=mx;k++)    {        for(int i=1;i<=n;i++)        {            int t=f2[i][k-1];            f2[i][k]=t+f2[i+t][k-1];        }    }}int RMQ1(int i,int x)//不含图片,以i开头长度为x的段落之后的单词标号{    if(x==0)return i;    while(x!=0 && i<=n)    {         int j=0;         while((1<<(j+1))<=x)j++;         i+=f1[i][j];         x-=(1<<j);    }    return i;}int RMQ2(int i,int x)//含图片,以i开头长度为x的段落之后的单词标号{    if(x==0)return i;    while(x!=0 && i<=n)    {         int j=0;         while((1<<(j+1))<=x)j++;         i+=f2[i][j];         x-=(1<<j);    }    return i;}int RMQ3(int i)//不含图片,以i开头到末尾的行数{    int ans=0;    while(i<=n)    {        int j=0;        while(i+f1[i][j+1]<=n)j++;        i+=f1[i][j];        ans+=(1<<j);    }    return ans;}int  Work(int x,int h){    int temp=RMQ3(1);    if(temp<=x-1)return temp+h;    int ans=x+h-1;    int i=0;    i=RMQ1(1,x-1);    i=RMQ2(i,h);    if(i<=n)ans+=RMQ3(i);    return ans;}int main(){    scanf("%d",&T);    while(T--)    {        mx=1000000;//设置一个最大的长度        scanf("%d%d%d%d",&n,&w,&pw,&lw);        for(int i=1;i<=n;i++)scanf("%d",&a[i]);        Init_ST();        scanf("%d",&Q);        for(int i=1;i<=Q;i++)        {            scanf("%d%d",&xi,&hi);            printf("%d\n",Work(xi,hi));        }    }}/*22 7 4 31 331 22 25 23 8 2 31 1 311 1*/