Link
Solution
建立AC自动机,条件等价于沿着非单词节点走出一个环。这是比较显然的:走到了一个曾经到过的节点,就说明两次经过这个节点之间的字符串循环起来就符合题意了。
Tips
又被自己乱想的不存在的边界情况卡住了。以后如果对自己的想法有什么异议,一定要造出具体到数据的反例。
Code
1 | //Code by Lucida |
建立AC自动机,条件等价于沿着非单词节点走出一个环。这是比较显然的:走到了一个曾经到过的节点,就说明两次经过这个节点之间的字符串循环起来就符合题意了。
又被自己乱想的不存在的边界情况卡住了。以后如果对自己的想法有什么异议,一定要造出具体到数据的反例。
1 | //Code by Lucida |
Fail树。子树和。经典思路。
AC自动机好像用到了不少记录某个字符匹配到哪个状态的情况啊。一开始太naive,直接暴力插入暴力查询,时间爆炸了。
1 | //Code by Lucida |
每加入一个字符,用栈记录匹配到的位置。删除后缀就通过弹栈回到之前相应的位置实现
1 | //Code by Lucida |
1 | //Code by Lucida |
外层区间线段树,内层平衡树存数值。K值用到二分答案,比较经典。 之前写过,为了试试新的代码写法,写了一遍。结果Treap旋转的时候没有更新信息,WA了一次。
treap在trans的时候没有pushup
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253//Code by Lucida
#include<bits/stdc++.h>
#define get(x) scanf("%d",&x)
#define put(x) printf("%d\n",x)
//#define debug(x) std::cout<<#x<<'='<<x<<std::endl
template <class T> inline bool chkmx(T &a,const T &b){return a<b?a=b,1:0;}
template <class T> inline bool chkmn(T &a,const T &b){return a>b?a=b,1:0;}
const int MAXN=50000+10,INF=0x1f1f1f1f;
using std::max;
using std::min;
namespace IO
{
#ifndef get
const int INBUF=1e6;
char in[INBUF],*iptr=in,*iend=in;
#define getc() ( (iptr==iend && (iend=(iptr=in)+fread(in,1,INBUF,stdin),iptr==iend))?EOF:*iptr++ )
inline void get(int &x)
{
static int f,ch;
f=1;
while(!isdigit(ch=getc())) if(ch=='-') f=-1;
x=ch-'0';
while(isdigit(ch=getc())) (x*=10)+=ch-'0';
x*=f;
}
#endif
#ifndef put
const int OUTBUF=1e6;
char out[OUTBUF],*optr=out,*oend=out+OUTBUF-1;
#define putc(x) (optr==oend?(fwrite(out,1,optr-out,stdout),optr=out,*optr++=(x)):*optr++=(x))
inline void put(int x)
{
if(x==0) putc('0');
if(x<0) putc('-'),x=-x;
if(x)
{
static int stack[100],*top=stack;
while(x) *++top=x%10,x/=10;
while(top!=stack) putc((*top--)+'0');
}
putc('\n');
}
#endif
void flush()
{
#ifndef put
fwrite(out,1,optr-out,stdout);
#endif
}
}
using namespace IO;
namespace Treap
{
const int P=1e5+7;
struct Node
{
Node *son[2];
int v,py,sz,cnt;
Node(){py=P;sz=0;}
void up(){sz=cnt+son[0]->sz+son[1]->sz;}
int LowCount(int);
int Pred(int);
int Succ(int);
}*null=new Node;
void out(Node *pos)
{
if(pos==null) return;
out(pos->son[0]);printf("%d(%d) ",pos->v,pos->cnt);out(pos->son[1]);
}
Node *newNode(int v)
{
static const int SIZE=MAXN*30;
static Node *ME=(Node*)malloc(SIZE*sizeof(Node)),*cur;
cur=ME++;cur->v=v;cur->py=rand()%P;cur->cnt=cur->sz=1;
cur->son[0]=cur->son[1]=null;
return cur;
}
void trans(Node *&pos,int d)
{
Node *s=pos->son[d];
pos->son[d]=s->son[!d];s->son[!d]=pos;
pos->up(),s->up();
pos=s;
}
int adjust(Node *&pos)
{
int d=pos->son[0]->py>pos->son[1]->py;
if(pos->py>pos->son[d]->py) trans(pos,d),d^=1;
else d=-1;return d;
}
void Insert(Node *&pos,int v)
{
if(pos==null) pos=newNode(v);
else if(pos->v==v) pos->cnt++,pos->up();
else
{
Insert(pos->son[pos->v<v],v),pos->up();
adjust(pos);
}
}
int Node::LowCount(int v)
{
Node *pos=this;int res=0;
while(pos!=null)
{
if(pos->v<v) res+=pos->son[0]->sz+pos->cnt,pos=pos->son[1];
else pos=pos->son[0];
}
return res;
}
int Node::Pred(int v)
{
Node *pos=this;int res=-INF;
while(pos!=null)
{
if(pos->v<v) chkmx(res,pos->v),pos=pos->son[1];
else pos=pos->son[0];
}
return res;
}
int Node::Succ(int v)
{
Node *pos=this;int res=INF;
while(pos!=null)
{
if(pos->v>v) chkmn(res,pos->v),pos=pos->son[0];
else pos=pos->son[1];
}
return res;
}
void DelNode(Node *&pos)
{
int d=adjust(pos);
if(~d)
{
assert(pos!=null);
DelNode(pos->son[d]),pos->up();
}
else pos=null;
}
void Delete(Node *&pos,int v)
{
if(pos->v==v)
{
pos->cnt--,pos->up();
if(!pos->cnt)
pos->py=P,DelNode(pos);
}
else
{
Delete(pos->son[pos->v<v],v),pos->up();
assert(adjust(pos)==-1);
}
}
}
using namespace Treap;
struct Data
{
int cnt,pred,succ;
Data(int cnt=0,int pred=-INF,int succ=INF):cnt(cnt),pred(pred),succ(succ){}
}nData;
Data Merge(Data a,Data b){return Data(a.cnt+b.cnt,max(a.pred,b.pred),min(a.succ,b.succ));}
namespace Seg
{
typedef Treap::Node tNode;
struct Node
{
Node *son[2];
tNode *treap;
}*null=new Node,*root=null;int TL,TR;
Node *newNode()
{
static int SIZE=MAXN<<2;
static Node *ME=(Node*)malloc(SIZE*sizeof(Node)),*cur;
cur=ME++;cur->son[0]=cur->son[1]=null;cur->treap=Treap::null;
return cur;
}
void Add(Node *&pos,int L,int R,int goal,int v)
{
if(pos==null) pos=newNode();
Insert(pos->treap,v);
if(L!=R)
{
int MID=(L+R)>>1;
if(goal<=MID) Add(pos->son[0],L,MID,goal,v);
else Add(pos->son[1],MID+1,R,goal,v);
}
}
Data Access(Node *pos,int L,int R,int l,int r,int v)
{
if(L==l && R==r) return Data(pos->treap->LowCount(v),pos->treap->Pred(v),pos->treap->Succ(v));
else
{
int MID=(L+R)>>1;
if(r<=MID) return Access(pos->son[0],L,MID,l,r,v);
else if(MID+1<=l) return Access(pos->son[1],MID+1,R,l,r,v);
else return Merge(Access(pos->son[0],L,MID,l,MID,v),Access(pos->son[1],MID+1,R,MID+1,r,v));
}
}
void Edit(Node *&pos,int L,int R,int goal,int ori,int v)
{
Delete(pos->treap,ori);
Insert(pos->treap,v);
if(L!=R)
{
int MID=(L+R)>>1;
if(goal<=MID) Edit(pos->son[0],L,MID,goal,ori,v);
else Edit(pos->son[1],MID+1,R,goal,ori,v);
}
}
}
using namespace Seg;
int a[MAXN];
int Kth(int l,int r,int K)
{
int L=0,R=1e8,res;K--;
while(L!=R)
{
int MID=(L+R+1)>>1;
if((res=Access(root,TL,TR,l,r,MID).cnt)<=K) L=MID;
else R=MID-1;
}
return L;
}
int main()
{
freopen("input","r",stdin);
int n,m;get(n),get(m);TL=1,TR=n;
for(int i=1;i<=n;i++) get(a[i]),Add(root,TL,TR,i,a[i]);
for(int i=1;i<=m;i++)
{
int opt,pos,l,r,k;
get(opt);
if(opt==3) get(pos),get(k);
else get(l),get(r),get(k);
switch(opt)
{
case 1:put(Access(root,TL,TR,l,r,k).cnt+1);break;
case 2:put(Kth(l,r,k));break;
case 3:Edit(root,TL,TR,pos,a[pos],k);a[pos]=k;break;
case 4:put(Access(root,TL,TR,l,r,k).pred);break;
case 5:put(Access(root,TL,TR,l,r,k).succ);break;
}
}
flush();
return 0;
}
/* AC Record(Bugs) *
* WA trans pushup
* * * * * * * * * */
这篇的所有内容纯属一个超级蒟蒻的自言自语。 今天做一道树剖题,看完题目之后想到了一种很诡的做法:
在每条链上挂一个Treap,里面装这个链上含有的线段树的根节点。
(你笑笑说直接全局建线段树就行了,不需要每个点建一个,但我太蒟蒻,怎么研究过那种写法)
于是就很愉快地开始写
数据结构这样存储
1
2struct TreapNode;
struct SegNode;
然后发现,这样做,每次进行操作要传递很多可以不传递的变量,比如
1
SegTree.Access(root,1,PathSize,Rank(pos),PathSize);
前三个都是与此次操作无关的。
写完A掉之后,感觉不爽。
于是重新设计重写。
1
2
3
4
5
6
7
8struct Treap;
{
struct TreapNode;
};
struct Seg;
{
struct SegNode;
};
发现这样做比原来的写法在实现数据结构上麻烦了不少,就比如需要四种null,重名了,只好改成nullSeg什么的。而且struct套struct写起来各种坑。
A掉之后,感觉不爽。
于是开始研究stl的写法。。发现stl_tree.h中定义的_Rb_tree是这样的
1
2
3
4
5struct _Rb_tree_node;
class _Rb_tree
{
_Rb_tree_node ...;
};
感觉不错。于是写写写,写完之后,感觉好些了。
然后看到Sengxian大神的代码,发现好多namespace啊
试了试,发现namespace确实好,用着很舒服。。其中一个好就是null不会重复了。。
于是 我的数据结构 就变成了这样
1
2
3
4
5
6
7
8namespace Treap
{
struct Node;
struct Treap
{
Node ...;
};
}
以后就这么写了。。。 这两天状态很差,明天一定要加油了。
定义F(i)为i的约数和,则F(i)可以O(nlogn)求出。Po姐的代码好象是线性筛??OTZ。 要求的是
嗯,先不考虑>a的限制。 考虑每个数字x作为gcd对答案的贡献。有
P(x)=F(x)×c(x)其中c(x)为gcd(i,j)==x的对数。 而
c(x)=x∣d∑μ(xd)[dn][dm]于是,
Ans=x∑P(x)=x∑{F(x)x∣d∑μ(xd)[dn][dm]}又见熟悉的分块加速项,把求和式改成枚举d,则
Ans=d∑[dn][dm]x∣d∑F(x)μ(xd)后面的项是可以暴力前缀和的。 有了a的限制,就需要让所有>a的F(i)不被算进去。于是就用树状数组,动态地加入F(i)。为了保证复杂度,把询问离线,按照a排序,然后依次处理,可以保证修改树状数组的复杂度为O(nlogn)
对2的整次幂取模,可以用&。
1 | //Code by Lucida |
定义F(x)为x∣gcd(a,b)的对数 f(x)为gcd(a,b)==x的对数 则
F(x)=[xn][xm]=x∣d∑f(d)f(x)=x∣d∑μ(xd)F(d)=x∣d∑μ(xd)[dn][dm]按照套路,一般把x提出来可以简化问题 于是
f(1)=1∣d∑μ(d)F(d)=1∣d∑μ(d)[xdn][xdm]要求的是
p∈P∑f(p)=p∈P∑1∣d∑μ(d)F(d)=p∈P∑1∣d∑μ(d)[pdn][pdm]数据规模决定不能分别枚举一遍p,d。看到这个式子长得比较可爱,令T=pd,则问题变成了
T∑(p∣T∑μ(pT)[Tn][Tm])于是,看到了熟悉的可以分块加速的项,思考能不能把
p∣T∑μ(pT)弄出个前缀和。然后这是可以暴力求的。
1 | //Code by Lucida |
裸的反演
1 | //Code by Lucida |
又被克了很长时间 官方数据有误?
DP方程为
f(i)=min{f(j−1)+[S+cT(j,i)]×cF(j,n)∣j<=i}
cT,cF分别表示相应量的区间和
j<k且j优于k当且仅当
Fj−1−Fk−1[f(j−1)+(S−Tj−1)(Fn−Fj−1)]−[fk−1+(S−Tk−1)(Fn−Fk−1)]>Ti
T,F表示相应量的前缀和
一开始没变号。。抓瞎了。。
如果Ti递增的话,就可以单调队列维护一个下凸壳。
Ti不递增,没办法,只能CDQ或者平衡树。
CDQ做法:
分治之前按照Ti排序,分治的每层把前半部分挑出来分治下去,回溯的时候按照id的大小merge。
然后就可以单调队列了。
判上下凸最好用叉积,就不用讨论x为0之类的。 比较斜率与一个数最好讨论x的符号,通过移项,相应变号,避免除法。 因为这个WA了很长时间。 还有,第一次编译一定要-Wall!! (Windows需要alias可以用doskey实现)
1 | //Code by Lucida |
uva太慢了,还是再vjudge上看题目吧。。
学习了FHQ Treap的基本操作,以及可持久化Treap的写法。
Merge(a,b)把a,b合并为一个Treap。前提是a中的所有元素都比b中的小,这样就可以按照可并堆的方法合并了。
Split(pos,K)将treap分裂成两个,第一个包含了前K个元素,第二个包含剩下的元素。写法和平衡树的查找第K大有些类似。 函数返回一个pair,具体实现比较容易脑补出来。
Build(seq)把一个序列O(n)构建成Treap。 如果是其它的平衡树的话,插入一个序列一般是用递归建立一个完全二叉树。Treap并不能那么做,因为以节点的优先级来确保全局的平衡。 于是Orz Sengxian。 对于一个有序的序列,依次处理每个元素。 维护一个栈,为每个元素新建一个节点,如果新的节点的优先级比栈顶元素小,就退栈,相应地连边。
在Merge和Split中,对节点的所有修改都要在副本上进行。每次的花费空间期望应该是O(log n)级别的。但是由于一次操作可能要多次Split和Merge,导致空间常数大了一些。。如果大神们有又好写又省空间的写法欢迎指教。。。
1 | //Code by Lucida |