Post 34

时光没有教会我任何东西,却教会了我不要轻易去相信童话。

LOJ#6284. 数列分块入门 8

题目描述

给出一个长为 nn 的数列,以及 nn 个操作,操作涉及区间询问等于一个数 cc 的元素,并将这个区间的所有元素改为 cc。

输入格式

第一行输入一个数字 nn。

第二行输入 nn 个数字,第 i 个数字为 a_iai,以空格隔开。

接下来输入 nn 行询问,每行输入三个数字 ll、rr、cc,以空格隔开。

表示先查询位于 [l,r][l,r] 的数字有多少个是 cc,再把位于 [l,r][l,r] 的数字都改为 cc。

输出格式

对于每次询问,输出一行一个数字表示答案。

样例

样例输入

1
2
3
4
5
6
4
1 2 2 4
1 3 1
1 4 4
1 2 2
1 4 2

样例输出

1
2
3
4
1
1
0
2

数据范围与提示

对于 100\%100% 的数据,$1 \leq n \leq 100000,$

题解

似乎是我最近写的最简单的分块了吧。

主要还是懒标记应用,然后注意块标记时效性, 有点类似于时间戳。

注意由于可能c为0,所以当不完整的块破坏标记整体性使得我们要像块内每个元素下推标记的时候,不要赋成0,用一个奇奇怪怪的数来代替0.

然后就没什么了,关于昨天那道单点插入的题,可以把排序改成insert,不过一直RE调不对奇怪的边界

关于本题还有一个有趣的地方就是我们发现query函数中看上去很暴力,完全就是线性的复杂度。

但是我们再分析一下题意:只要询问完一个区间,就立刻修改。

这意味着每个数在初始的时候被修改一次后,后面的查询在整块上就可以$O(1)$来判断当前块了。

或者我们可以把初始数的暴力枚举和后面已经经过至少一次修改的数分开来看。

每次修改查询连在一起,修改将产生$O(\sqrt{N})$个不完整块,其他块将被标记。这样复杂度高的线性查询最多进行$O(n/sqrt{n})$次

不管怎样,大量查询后每个元素因为初始没有任何标记至多被暴力计算一次,其他情况下就是整段快速统计边角暴力了。这样由于最初没有任何标记只是小复杂度。当时写的时候还差点以为我写了一个假复杂度的暴力233.

然后我们就愉快的1A本题。

BUG:

img

这里如果底下加一个else就会造成巨难调的错误,所以千万要注意!

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cmath>
#define _MAX 100005
#define _MAXSQRT 355
#define Rand -0x3f7f3ff
int mk[_MAXSQRT] , n , v[_MAX] , L[_MAXSQRT] , R[_MAXSQRT] , pos[_MAX] , blnum , blsize;


inline void print(int l , int r)
{
puts("ARRAY");
for(int i = l ; i <= r ; ++i)
{
if(mk[pos[i]] == Rand)
printf("%d ",v[i]);
else printf("%d ",mk[pos[i]]);
}
putchar(10);
}

inline void pre()
{
blsize = (int)std::sqrt(n);
blnum = n / blsize;
for(int i = 1 ; i <= blnum ; ++i)
L[i] = (i-1)*blsize + 1, R[i] = i * blsize;
if(R[blnum] < n) ++blnum , L[blnum] = R[blnum-1]+1 , R[blnum] = n;
for(int i = 1 ; i <= blnum ; ++i)
for(int j = L[i] ; j <= R[i] ; ++j)
pos[j] = i ;
for(int i = 1 ; i <= blnum ; ++i)
mk[i] = Rand;
// printf("BLNUM : %d BLSIZE%d\n",blnum,blsize);
// for(int i = 1 ; i <= blnum ; ++i)
// printf("L:%d R:%d\n",L[i],R[i]);
}


inline void update(int l , int r , int c)
{
// printf("UPD:%d %d %d\n",l,r,c);
if(pos[l] == pos[r])
{
// puts("UPD SAME BLOCK");
if(mk[pos[l]] != Rand)
for(int i = L[pos[l]] ; i <= R[pos[l]] ; ++i)
v[i] = mk[pos[l]];
mk[pos[l]] = Rand;
for(int i = l ; i <= r ; ++i)
v[i] = c;
// print(t1,t2);
return ;
}

if(mk[pos[l]] != Rand)
for(int i = L[pos[l]] ; i <= R[pos[l]] ; ++i)
v[i] = mk[pos[l]];
mk[pos[l]] = Rand;
for( ; L[pos[l]] != l ; ++l)
v[l] = c;
if(mk[pos[r]] != Rand)
for(int i = L[pos[r]] ; i <= R[pos[r]] ; ++i)
v[i] = mk[pos[r]];
mk[pos[r]] = Rand;
for( ; R[pos[r]] != r ; --r)
v[r] = c;
// if(l > r) puts("RETURN !");
if(l > r) return ;

for(int i = pos[l] ; i <= pos[r] ; ++i)
mk[i] = c;
// puts("UPD NOW");
// print(t1,t2);
return ;
}

inline int query(int l , int r , int c)
{
// printf("QUERY %d %d %d\n",l,r,c);
// print(l,r);
int ans = 0;
if(pos[l] == pos[r])
{
// puts("SAME BLOCK!");
if(mk[pos[l]] == c) return r-l+1; //maybe the c is zero!!
else if(mk[pos[l]] != Rand) return 0;
else
for(int i = l ; i <= r ; ++i)
if(v[i] == c)
++ans;
return ans;
}

for(int i = pos[l] ; i <= pos[r] ; ++i)
{
// printf("THE I BLOCK MK: %d\n",mk[i]);
if(mk[i] == Rand)
{
for(int j = std::max(L[i],l) ; j <= std::min(r,R[i]) ; ++j)
if(v[j] == c)
++ans;
}
else if(mk[i] == c) ans += std::min(R[i],r) - std::max(l,L[i]) + 1;
}
return ans;
}

int main()
{
// freopen("data.in","r",stdin);
// freopen("my.out","w",stdout);
scanf("%d",&n);
for(int i = 1 ; i <= n ; ++i)
scanf("%d",&v[i]);
pre();
for(int i = 1 ; i <= n ; ++i)
{
int l , r , c;
scanf("%d%d%d",&l,&r,&c);
printf("%d\n",query(l,r,c));
update(l,r,c);
}
}

分块系列就没了,剩下几个都是做过的了。

接下来就做做字符串和POI.


NOIp 2018 货币系统

题目描述

在网友的国度中共有 nn 种不同面额的货币,第 ii 种货币的面额为 a[i]a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 nn、面额数组为 a[1..n]a[1..n] 的货币系统记作 (n,a)(n,a)。

在一个完善的货币系统中,每一个非负整数的金额 xx 都应该可以被表示出,即对每一个非负整数 xx,都存在 nn 个非负整数 t[i]t[i] 满足 a[i] \times t[i]a[i]×t[i] 的和为 xx。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 xx不能被该货币系统表示出。例如在货币系统 n=3n=3, a=[2,5,9]a=[2,5,9] 中,金额 1,31,3 就无法被表示出来。

两个货币系统 (n,a)(n,a) 和 (m,b)(m,b) 是等价的,当且仅当对于任意非负整数 xx,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 (m,b)(m,b),满足 (m,b)(m,b) 与原来的货币系统 (n,a)(n,a)等价,且 mm 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 mm。

输入输出格式

输入格式:

输入文件的第一行包含一个整数 TT,表示数据的组数。

接下来按照如下格式分别给出 TT 组数据。 每组数据的第一行包含一个正整数 nn。接下来一行包含 nn 个由空格隔开的正整数 a[i]a[i]。

输出格式:

输出文件共有 TT 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a)(n,a) 等价的货币系统 (m,b)(m,b) 中,最小的 mm。

输入输出样例

输入样例#1:

复制

1
2
3
4
5
2 
4
3 19 10 6
5
11 29 13 19 17

输出样例#1:

复制

1
2
2   
5

说明

在第一组数据中,货币系统 (2, [3,10])(2,[3,10]) 和给出的货币系统 (n, a)(n,a) 等价,并可以验证不存在 m < 2m<2 的等价的货币系统,因此答案为 22。 在第二组数据中,可以验证不存在 m < nm<n 的等价的货币系统,因此答案为 55。

【数据范围与约定】

img

对于 100\%100% 的数据,满足 1 ≤ T ≤ 20, n,a[i] ≥ 11≤T≤20,n,a[i]≥1。

题解

我大概就是被这道题搞死的吧。。

这题当时要是我能看出一个结论:必须是真子集也许就A了吧。

可惜最后获得了0分的好成绩。

对于b,设其内部元素大的不能被小的表示出来。从小到大假如其中有一个元素不是a中的,如果能被a中的凑出来,那用a中凑出来的就小于它,因此仅单选哪些元素中的一个b就表示不出来。假如不能被a凑出来,那单选这个也不满足题意。。

然而考场上就是没有想到这个显而易见的事实。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#define _MAX 505
int a[_MAX] , vis[_MAX<<10] , n , t , mx = -0x7fffff;
int main()
{
scanf("%d",&t);
while(~--t)
{
std::memset(vis,0,sizeof(vis));
mx = -0x7fffff;
scanf("%d",&n);
for(int i = 1 ; i <= n ; ++i)
scanf("%d",&a[i]) , mx = std::max(mx , a[i]);
std::sort(a+1,a+n+1);
vis[0] = 1;
int ans = n;
for(int i = 1 ; i <= n ; ++i)
{
if(vis[a[i]]){
--ans;
continue;
}
for(int j = a[i] ; j <= mx ; ++j)
if(vis[j-a[i]]) vis[j] = true;
}
printf("%d\n",ans);
}
}

LOJ#2452. 「POI2010」反对称 Antisymmetry

题目描述

对于一个 0/1字符串,如果将这个字符串 0 和 1 取反后,再将整个串反过来和原串一样,就称作「反对称」字符串。比如 00001111 和 010101 就是反对称的,而 1001 就不是。
现在给出一个长度为 n 的 0/1 字符串,求它有多少个子串是反对称的,注意这里相同的子串出现在不同的位置会被重复计算。

输入格式

第一行一个正整数 nn 。
第二行一个长度为 nn 的 0/10/1 字符串。

输出格式

一行一个整数,表示原串的反对称子串个数。

样例

样例输入

1
2
8
11001011

样例输出

1
7

数据范围与提示

对于 $100\%$的数据, $1\le n\le 500\ 000$ 。

题解

挺不错的一道题,POI的题果然质量高。

我们得观察串的性质。

首先第一点是长度必须为偶数,并且一个串从中点为对称轴左右必须互异。

能得到这个性质不难写出$O(n^2)$的Hash算法优化暴力,可以获得50分。

然后不妨再想想根据我们观察出的性质如何优化。
我们来想想暴力统计的都是什么样的, 输出观察一下发现很多都互相包含,而且连续。

然后不难想出枚举中间对称点,二分长度了,之所以能二分是因为对于相同对称轴长的包含短的。

注意方向,左边是从右向左和对称轴右边的从左向右,不难想到只需要把左边的从右边开始后缀Hash即可。

最重要的bug,不要随便用static!!那个东西在函数里声明是有特殊用途的,没事别瞎写。

忘了是从右向左忘了开longlong结果WA了两次。。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#define _MAX 500005
int n , r[_MAX];
unsigned long long h[2][_MAX] , p[_MAX] , ans;
char s[_MAX];

inline void pre()
{
for(int i = 1 ; i <= n ; ++i)
r[i] = s[i] - 48 ^ 1;
for(int i = 1 ; i <= n ; ++i)
h[0][i] = (h[0][i-1] * 137) + s[i] ;
for(int i = n ; i >= 1 ; --i)
h[1][i] = (h[1][i+1] * 137) + r[i] + 48;
}

inline unsigned long long pow(int x , int y)
{
unsigned long long ans = 1 , base = x;
for( ; y ; y >>= 1 , base *= base)
if(y & 1)
ans *= base;
return ans;
}

inline unsigned long long Prehash(int l , int r){
unsigned long long ans = h[0][r] - h[0][l-1] * p[r-l+1];
return ans;
}

inline unsigned long long Sufhash(int l , int r){
return h[1][l] - h[1][r+1] * p[r-l+1];
}

int main()
{
scanf("%d",&n);
scanf("%s",s+1);
pre();
p[0] = 1;
for(int i = 1 ; i <= n ; ++i)
p[i] = p[i-1]*137;
for(int i = 1 ; i < n ; ++i)
{
int l = 1 , r = std::min(i , n - i) , cur = 0;
while(l <= r)
{
int mid = l + r >> 1;
// printf("NOW %d %d %llu %d %d %llu\n",i-mid+1,i,hash(i-mid+1,i,1),i+1,i+mid,hash(i+1,i+mid,0));
if(Sufhash(i-mid+1,i) == Prehash(i+1,i+mid)) cur = mid , l = mid + 1;
else r = mid - 1;
}
// printf("THE LCP:%d\n",cur);
ans += cur;
}
printf("%llu\n",ans);

}