【HDU5741 2016 Multi-University Training Contest 2H】【线段树 扫描线】Helter Skelter 超级01串是否有恰好x个0y个1的子串

Helter Skelter

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 647    Accepted Submission(s): 188


Problem Description
A non-empty string   s  is called binary, if it consists only of characters "0" and "1". A substring   s[lr]   (1lr|s|)  of string   s=s1s2s|s|  (where   |s|  is the length of string   s ) is string   slsl+1...sr .

Professor Zhang has got a long binary string   s  starting with "0", and he wants to know whether there is a substring of the string   s  that the occurrences of "0" and "1" in the substring is exact   a  and   b , respectively, where   a  and   b  are two given numbers.

Since the binary string is very long, we will compress it. The compression method is:
1. Split the string to runs of same characters.
2. Any two adjacent runs consist of different characters. Use the length of each run to represent the string.

For example, the runs of a binary string 00101100011110111101001111111 are {00, 1, 0, 11, 000, 1111, 0, 1111, 0, 1, 00, 1111111}, so it will be compressed into {2, 1, 1, 2, 3, 4, 1, 4, 1, 1, 2, 7}.
 

Input
There are multiple test cases. The first line of input contains an integer   T , indicating the number of test cases. For each test case:

The first line contains two integers   n  and   m   (1n1000,1m5105)  - the number of runs and the number of queries. The next line contains   n  integers:   x1,x2,...,xn   (1xi106) , indicating the length of the each run.

Each of the following   m  lines contains two integers   ai  and   bi   (0ai,bi|s|,1ai+bi|s|) , which means Professor Zhang wants to know whether there is a substring of the string   s  that the occurrences of "0" and "1" in the substring is exact   ai  and   bi .
 

Output
For each test case, a binary string of length   n . The   i -th number is "1" if the answer for the   i -th query is yes, or "0" otherwise.
 

Sample Input
  
  
3 2 3 3 4 3 0 3 4 1 2 3 4 1 2 3 5 1 4 2 1 3 3 2 12 10 2 1 1 2 3 4 1 4 1 1 2 7 2 1 2 2 2 3 2 4 2 5 4 1 4 2 4 3 4 4 4 5
 

Sample Output
  
  
111 0101 1111101111
 

Author
zimpha
 

Source

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 1010, M = 5e5 + 10, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m;
int d[N];
//y->1的个数;x->0的个数
struct A
{
	int y, l, r, v;
	const bool operator < (const A&b)const
	{
		if (y != b.y)return y < b.y;
		return v > b.v;
	}
}a[N*N + M];
int b[N*N + M];
//线段树要开到多大呢?1 << 22
int flag[1 << 22];
void build(int o, int l, int r)
{
	flag[o] = 0;
	if (l == r)return;
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
}
int L, R, V;
void add(int o, int l, int r)
{
	if (l >= L&&r <= R)
	{
		flag[o] += V;
		return;
	}
	int mid = (l + r) >> 1;
	if (L <= mid)add(lson);
	if (R > mid)add(rson);
}
bool check(int o, int l, int r)
{
	if (flag[o])return 1;
	if (l == r)return 0;
	int mid = (l + r) >> 1;
	if (L <= mid)return check(lson);
	else return check(rson);
}
char ans[M];
int main()
{
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)scanf("%d", &d[i]);
		int g = 0;
		int t = 0; b[++t] = 0;
		for (int l = 1; l <= n; ++l)
		{
			int V0 = 0; int V1 = 0;
			int v0 = 0; int v1 = 0;
			int s0 = 0; int s1 = 0;
			if (l & 1)s0 += d[l]; else s1 += d[l];
			a[++g] = { 0, 0, s0, 1 };
			a[++g] = { s1 + 1, 0, s0, -1 };
			b[++t] = s0;

			if (l > 1)
			{
				if (l - 1 & 1)v0 += d[l - 1]; else v1 += d[l - 1];
			}
			for (int r = l - 1; r <= n; ++r)
			{
				if (r >= l)
				{
					if (r & 1)V0 += d[r];
					else V1 += d[r];
				}
				int v00 = v0;
				int v11 = v1;
				if (r < n)
				{
					if (r + 1 & 1)v00 += d[r + 1];
					else v11 += d[r + 1];
				}
				a[++g] = { V1, V0, V0 + v00, 1 };
				a[++g] = { V1 + v11 + 1, V0, V0 + v00, -1 };
				b[++t] = V0;
				b[++t] = V0 + v00;
			}
		}
		for (int i = 1; i <= m; ++i)
		{
			int x, y;
			scanf("%d%d", &x, &y);
			a[++g] = { y, x, i, -100 };
			b[++t] = x;
		}
		sort(a + 1, a + g + 1);
		sort(b + 1, b + t + 1);
		n = t = unique(b + 1, b + t + 1) - b - 1;
		build(1, 1, n);
		for (int i = 1; i <= g; ++i)
		{
			V = a[i].v;
			L = lower_bound(b + 1, b + n + 1, a[i].l) - b;
			R = (V == -100 ? a[i].r : lower_bound(b + 1, b + t + 1, a[i].r) - b);
			if (V == -100)
			{
				ans[R] = check(1, 1, n) + '0';
			}
			else add(1, 1, n);
		}ans[m + 1] = 0;
		puts(ans + 1);
	}
	return 0;
}
/*
【trick&&吐槽】
妈蛋,有的题目真的并不一定不可做。
多读题,敢写敢试,注意细节。

【题意】
一个数串s,只包含'0'或'1',但是这个字符串太长了,我们不好给出。
于是,我们按照连续相同字符串的划分,这个数串能划分成恰好n(1000)段。

然后,我们有m(5e5)个询问,
对于每个询问(x,y),
我们问你——
是否存在一个子串,
使得该子串中恰好有x个'0'和y个'1'

【类型】
线段树 扫描线

【分析】
这道题据说可以用凸包做——

但是似乎自己会的做法是线段树扫描线算法。
一共有n段01串,我们枚举哪些01段位于区间的内部。
然后就知道区间内部有多少个1和0.

接下来,对于区间的两头,我们是可以向外延展一定数量的0或1,
也就使得一个[l1,r1],[l2,r2]的询问为合法询问。

我们不妨使得0的个数为横坐标(即存在basci_num+=1的细节)
同时使得1的个数为纵坐标。
这样便可以展开我们的线段树扫描线算法。

我们把所有的询问,也按照1的个数排序。
我们以(+1,-1,询问)的顺序排序所有操作(询问排在后面即可)
然后对于询问,查询区间是否有1即可。
所以自然还需要做关于0个数的离散化。


【时间复杂度&&优化】
O((n^2+m)log(n^2))

【数据】
样例1解释
2(2段数) 3(3个询问)
0001111
询问——
3 0(1)
3 4(1)
1 2(1)

自己制造数据——
00111000011111
100
4 40
2 3 4 5
0 0
0 1
0 2
0 3
0 4
0 5
0 6
0 7
1 0
1 1
1 2
1 3
1 4
1 5
1 6
1 7
2 0
2 1
2 2
2 3
2 4
2 5
2 6
2 7
3 0
3 1
3 2
3 3
3 4
3 5
3 6
3 7
4 0
4 1
4 2
4 3
4 4
4 5
4 6
4 7

*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值