Hdu 4391 Paint The Wall

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4391

给出一个长度为n(n <= 10 ^ 5)每个点标有颜色的数列,现有如下两种操作,操作总数不大于10 ^ 5:
1、C l r z      将[l, r]区间内所有点的颜色标为z。
2、Q l r z      查询[l, r]区间内颜色为z的点的数目。

本题可以用线段树+剪枝优化做。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;

#define Maxn 100005
#define lx (x<<1)
#define rx ((x<<1) | 1)
#define MID ((l + r)>>1)

//既充当标记,又充当值
int S[Maxn<<2];
//剪枝用
int mi[Maxn<<2],mx[Maxn<<2];
int A[Maxn];

int n,m;

void pushUp(int x)
{
   S[x] = S[lx] == S[rx] ? S[lx] : -1;
   mi[x] = min(mi[lx],mi[rx]);
   mx[x] = max(mx[lx],mx[rx]);
}
void pushDown(int l,int r,int x)
{
   if(S[x]!=-1)
   {
      S[rx] = S[lx] = S[x];
      mi[rx] = mi[lx] = S[x];
      mx[rx] = mx[lx] = S[x];
   }
}
void build(int l,int r,int x)
{
   if(l == r)
   {
      S[x] = mi[x] = mx[x] = A[l];
      return;
   }
   build(l,MID,lx);
   build(MID+1,r,rx);
   pushUp(x);
}

void update(int L,int R,int d,int l,int r,int x)
{
   if(S[x] == d) return;
   if(L<=l && r<=R)
   {
      S[x] = mi[x] = mx[x] = d;
      return;
   }
   pushDown(l,r,x);
   if(L<=MID) update(L,R,d,l,MID,lx);
   if(MID+1<=R) update(L,R,d,MID+1,r,rx);
   pushUp(x);
}
int query(int L,int R,int z,int l,int r,int x)
{
   if(L<=l && r<=R)
   {
      if(z>=mi[x] && z<=mx[x])
      {
         if(S[x]!=-1)
         {
            if(S[x] == z) return r-l+1;
            else return 0;
         }
         else return query(L,R,z,l,MID,lx) + query(L,R,z,MID+1,r,rx);
      }
      else return 0;
   }
   pushDown(l,r,x);
   int ans = 0;
   if(L<=MID) ans += query(L,R,z,l,MID,lx);
   if(MID+1<=R) ans += query(L,R,z,MID+1,r,rx);
   return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
   int op,a,b,z;
   while(scanf(" %d %d",&n,&m)!=EOF)
   {
      for(int i=1;i<=n;i++)
      {
         scanf(" %d",&A[i]);
      }
      build(1,n,1);
      for(int i=0;i<m;i++)
      {
         scanf(" %d %d %d %d",&op,&a,&b,&z);
         a++;b++;
         if(op == 1) update(a,b,z,1,n,1);
         else if(op == 2)
         {
            int ans = query(a,b,z,1,n,1);
            printf("%d\n",ans);
         }
      }
   }
   return 0;
}

另一种很好的做法是分块哈希。

将直线分成sqrt(n)段,每段用一个hash表存储,然后将每次修改时,如果是部分修改,则在这段内遍历修改,然后重新生成hash表(此时注意标记的下放)。如果是整段修改,则给这块设置一个标记,标记为这一整段都被涂成该色,查询的方法和修改相同。因为任意一个线段能分成 :

i*sqrt(n) -1  ->  L,(i+1)*(sqrt)-1 -> i*sqrt(n),….r -> j*sqrt(n).这样的小于等于sqrt(n)的段。除了两头需要暴力查询及修改需要O(sqrt(n))外,其他部分都只用O(1)所以每次操作复杂度为O(sqrt(n))

Hash用Map来模拟。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
using namespace std;

#define Maxn 100010
#define BLOCK 510
int n,m;
int A[Maxn];

//分块相关
int block_num;
int block_size;
map<int ,int> hash[BLOCK];
int color[BLOCK];

void updateOne(int blockNo)
{
   hash[blockNo].clear();
   color[blockNo] = -1;
   int st = blockNo*block_size;
   int ed = st + block_size;
   if(ed>n) ed = n;
   for(int j = st;j<ed;j++)
   {
      hash[blockNo][A[j]]++;
   }
}
void initHash()
{
   block_size = (int)(sqrt(n));
   block_num = n/block_size + (n%block_size ? 1:0);
   for(int i=0;i<block_num;i++)
   {
      updateOne(i);
   }
}

//标记下放
void pushDown(int blockNo)
{
   if(color[blockNo]!=-1)
   {
      int st = blockNo*block_size;
      int ed = st + block_size;
      if(ed>n) ed = n;
      for(int i = st; i<ed;i++)
      {
         A[i] = color[blockNo];
      }
      color[blockNo] = -1;
   }
}
void update(int l,int r,int z)
{
   int lBlockNum = l/block_size;
   int lBlockSt = l%block_size;
   int rBlockNum = r/block_size;
   int rBlockSt = r%block_size;
   //在同一块
   if(lBlockNum == rBlockNum)
   {
      //未完全覆盖则暴力修改整个块
      //结束点在末尾算作完全覆盖的情况
      if((rBlockSt - lBlockSt + 1)!=block_size && r!=n-1)
      {
         pushDown(lBlockNum);
         for(int i=l;i<=r;i++) A[i] = z;
         updateOne(lBlockNum);
      }
      //完全覆盖染色
      else color[lBlockNum] = z;
   }
   else
   {
      //未完全覆盖则暴力修改两个块,完全覆盖则染色
      if(lBlockSt!=0)
      {
         pushDown(lBlockNum);
         for(int i=l;i<(lBlockNum+1)*block_size;i++) A[i] = z;
         updateOne(lBlockNum);
      }
      else color[lBlockNum] = z;
      if(rBlockSt!=block_size-1 && r!=n-1)
      {
         pushDown(rBlockNum);
         for(int i=rBlockNum*block_size;i<=r;i++) A[i] = z;
         updateOne(rBlockNum);
      }
      else color[rBlockNum] = z;
      for(int i=lBlockNum+1;i<rBlockNum;i++)
      {
         color[i] = z;
      }
   }
}
int getOne(int l,int r,int z)
{
   int ans = 0;
   for(int i=l;i<=r;i++)
   {
      if(A[i] == z) ans++;
   }
   return ans;
}
int query(int l,int r,int z)
{
   int ans = 0;
   int lBlockNum = l/block_size;
   int lBlockSt = l%block_size;
   int rBlockNum = r/block_size;
   int rBlockSt = r%block_size;
   //同一块
   if(lBlockNum == rBlockNum)
   {
      if(color[lBlockNum] == z) ans += rBlockSt - lBlockSt + 1;
      else if(color[lBlockNum] == -1) ans += getOne(l,r,z);
   }
   else
   {
      if(color[lBlockNum] == z) ans += (lBlockNum + 1)*block_size - l;
      else if(color[lBlockNum] == -1) ans += getOne(l,(lBlockNum + 1)*block_size - 1,z);  
      if(color[rBlockNum] == z) ans += r - rBlockNum* block_size + 1;
      else if(color[rBlockNum] == -1) ans += getOne(rBlockNum*block_size,r,z);
      for(int i=lBlockNum+1;i<rBlockNum;i++)
      {
         if(color[i] == z) ans += block_size;
         else if(color[i] == -1 && hash[i].find(z)!=hash[i].end()) ans += hash[i][z];
      }
   }
   return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
   int op,a,b,z;
   while(scanf(" %d %d",&n,&m)!=EOF)
   {
      for(int i=0;i<n;i++) scanf(" %d",&A[i]);
      initHash();
      for(int i=0;i<m;i++)
      {
         scanf(" %d %d %d %d",&op,&a,&b,&z);
         if(op == 1)
         {
            update(a,b,z);
         }
         else if(op == 2)
         {
            int ans = query(a,b,z);
            //int ans = 0;
            printf("%d\n",ans);
         }
      }
   }
   return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值