【日记】1.13
1.13
数列分块
1.数列分块1:区间加减+单点查询
思路:边角暴力,整块用lazy标记整体加减。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=5e4+20,P=1e9+7;
int bl_size,blnum[M],v[M],lazy[M];
void bl_init(int n){
bl_size=sqrt(n);
for(int i=1;i<=n;++i)
blnum[i]=(i-1)/bl_size+1;
}
void bl_add(int l,int r,int c){
//先加左边多余块
for(int i=l;i<=min(blnum[l]*bl_size,r);++i)//l到本块最右边和r最小值
v[i]+=c;
//再加右边多余块
if (blnum[l]!=blnum[r])
for(int i=(blnum[r]-1)*bl_size+1;i<=r;++i)//r所在块第一个数到r
v[i]+=c;
//最后处理整块
for(int i=blnum[l]+1;i<=blnum[r]-1;++i)
lazy[i]+=c;
}
struct TTTT{
int n,a[M];
void init(){
scanf("%d",&n);
bl_init(n);
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
}
void run(){
init();
for(int i=1;i<=n;++i){
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if (op==0)
bl_add(l,r,c);
else
printf("%d\n",v[r]+lazy[blnum[r]]);
}
}
}TTT;
int main(){
TTT.run();
return 0;
}
2.数列分块2:区间加减+询问小于x的元素个数
这里询问需要保证每个块是有序的,这样可以直接二分找到小于x的元素个数。因此加减的时候,对于边角需要暴力修改完之后,对原数组排序。显然排序的结果需要新开一个东西存储。整体部分则直接lazy加减即可。
对于查询,边角仍然暴力统计,整块的话,对于每个块都在内部lowerbound一下查询小于x的元素个数,最后加起来。
时间复杂度是\(O(n\log n\sqrt{n\log n})\)
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
#define lf(x) ((x)-1)*bl_size+1
#define rt(x,n) min((x)*bl_size,(n))
const int M=2e5+20,P=1e9+7;
int n,bl_size,blnum[M],lazy[M],v[M];
vector<int> blk[M];
inline void reset(int x){
blk[x].clear();
for(int i=lf(x);i<=rt(x,n);++i)
blk[x].push_back(v[i]);
sort(blk[x].begin(),blk[x].end());
}
inline void bl_init(int n){
bl_size=sqrt(n/log(n));
for(int i=1;i<=n;++i)
blnum[i]=(i-1)/bl_size+1,blk[blnum[i]].push_back(v[i]);
for(int i=blnum[1];i<=blnum[n];++i)
sort(blk[i].begin(),blk[i].end());
}
inline void bl_add(int l,int r,int c){
for(int i=l;i<=rt(blnum[l],r);++i)
v[i]+=c;
reset(blnum[l]);
if (blnum[l]!=blnum[r]){
for(int i=lf(blnum[r]);i<=r;++i)
v[i]+=c;
reset(blnum[r]);
}
for(int i=blnum[l]+1;i<=blnum[r]-1;++i)
lazy[i]+=c;
}
inline int query(int l,int r,int c){
int ans=0;
for(int i=l;i<=rt(blnum[l],r);++i)
if (v[i]+lazy[blnum[l]]<c)
++ans;
if (blnum[l]!=blnum[r])
for(int i=lf(blnum[r]);i<=r;++i)
if (v[i]+lazy[blnum[r]]<c)
++ans;
for(int i=blnum[l]+1;i<=blnum[r]-1;++i)
ans+=lower_bound(blk[i].begin(),blk[i].end(),c-lazy[i])-blk[i].begin();
return ans;
}
struct TTTT{
void init(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
bl_init(n);
}
void run(){
init();
for(int i=1;i<=n;++i){
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==0)
bl_add(l,r,c);
else
printf("%d\n",query(l,r,c*c));
}
}
}TTT;
int main(){
TTT.run();
return 0;
}
Wannafly Day 2
A.托米的字符串
托米有一个字符串,他经常拿出来玩。这天在英语课上,他学习了元音字母a,e,i,o,u以及半元音y。“这些字母是非常重要的!”,托米这样想着,“那么我如果随机取一个子串,里面元音占比期望会有多大呢?”
于是,请你求出对于托米的字符串,随机取一个子串,元音(a,e,i,o,u,y)字母占子串长度比的期望是多少。
输入格式:
读入一个长度不超过106的只包含小写字母的字符串,即托米的字符串。
输出格式
输出所求的期望值,要求相对(绝对)误差不超过10−6。
输入样例:
legilimens
输出样例:
0.446746032
思路:统计每个元音字母的贡献。若元音字母的位置是i,及k=min(i+1,len-i),则包含它的,长度为1-k的字符串的个数是1-k个,贡献为\(1*1+2*1/2+3*1/3+……\),总和就是k。长度为k+1-len-k+1的字符串的个数都是k个,贡献是\([1/(k+1)+...+1/(len-k+1)]*k\),可以预处理调和级数分数。最后一部分的个数分别是k-1-1个,也可以预处理,时间复杂度\(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=1e6+20,P=1e9+7;
struct TTTT{
int n,len;
char s[M];
double pre[M],pre2[M];
void init(){
scanf("%s",s);
len=strlen(s);
for(int i=1;i<=len;++i)
pre[i]=pre[i-1]+1.0/i,pre2[i]=pre2[i-1]+1.0*i/(len-i+1);
}
void run(){
init();
double sum=0,ans=0;
for(int i=1;i<=len;++i)
sum+=i;
for(int i=0;i<len;++i)
if (s[i]=='a'||s[i]=='e'||s[i]=='i'||s[i]=='o'||s[i]=='u'||s[i]=='y'){
int mn=min(len-i,i+1),mx=max(len-i,i+1);
ans+=mn+(pre[mx]-pre[mn])*mn+pre2[mn-1];
}
printf("%.16lf\n",ans/sum);
}
}TTT;
int main(){
TTT.run();
return 0;
}
B.萨博的方程式
萨博有个方程式:
x1xor**x2xor**x3xor...xorxn=k
其中xor指代位运算中的异或符号。
萨博同时还对每个未知数限制了范围为0≤x**i≤m**i,希望你计算出解的个数,最终答案对109+7取模后输出。
输入格式:
本题设有多组数据,请处理到文件尾,保证数据组数不超过100。
对于每组测试数据:
第一行读入2个正整数n,k(n≤50,k<231);
第二行读入n个非负整数m1,m2,m3,...,m**n(m**i<231)。
输出格式
对于每组测试数据,输出一个数,为题目所求。
输入样例:
7 127
64 32 16 8 4 2 1
6 127
64 32 16 8 4 2
4 5
1 2 3 4
输出样例:
1
0
6
思路:数位DP。
C. 纳新一百的石子游戏
D.卡拉巴什的字符串
卡拉巴什是字符串大师,这天他闲着无聊,又造了个字符串问题。
给定一个长度为N字符串S,定义后缀i为从第i个位置开始的后缀,即sisi+1...sn,定义lcp(i, j)为后
缀i和后缀j的最长公共前缀。
卡拉巴什想要知道,每次他给出一组i,j,你能否快速告诉他lcp(i, j)。
卡拉巴什的好朋友葫芦是字符串宗师,他认为这个题太无聊,于是他想了另一个问题,假设有一个
集合{lcp(i, j)|1 ≤ i < j ≤ N},他想知道这个集合的MEX值是多少。一个集合的MEX值为最小的没有
出现在集合中的非负整数。
这个问题对卡拉巴什来说太容易了, 于是葫芦想知道, 对于字符串的每一个前缀, 对应的集合
的MEX值是多少。
Input
第一行一个整数T(1 ≤ T ≤ 105
),表示数据组数。
对于每组数据,共一行,表示字符串S(1 ≤ |S| ≤ 106
)。
输入数据保证所有字符串长度和不超过5 ∗ 106。保证字符串只包含小写字母。
Output
对于每组数据,输出|S|个整数,表示每一个前缀对应的MEX值。
Example
standard input standard output
2
ababa
baa
0 1 2 3 4
0 1 2
思路:这题真的是思维题目,确实很难想,但代码很好写。
- 需要特判0的情况。如果输入字母全都是相同的,那么输出0。
- 否则,集合里面的元素是连续的,从0-mex-1。实际上求的就是集合最大值+1。
- 考虑从i转移到i+1的情况,lcp增加的那些,肯定后缀整个都是lcp,也就是后缀整个是另外一个的前缀。这种情况下,这个整个后缀一定是endpos>1的(一次出现在最后,另一次出现在lcp另一个串的对应位置)。因此,lcp增加之后最大的那个,长度为maxlen(fa(last))。这是因为属于last节点的一定都没有增加。
- 那么每次检验一下maxlen(fa(last))是否比当前mex更大,如果是就mex=那个。而且你可以发现mex每次一定都只+1。具体看cyy的题解吧。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=1e6+20;
char s[M],s2[M];
struct SAM{
int last,cnt,ch[M<<1][26],fa[M<<1],len[M<<1];
int mex;
void ins(int c){
int p=last,np=++cnt;
last=np,len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p])
ch[p][c]=np;
if(!p)
fa[np]=1;
else{
int q=ch[p][c];
if(len[p]+1==len[q])
fa[np]=q;
else{
int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p])
ch[p][c]=nq;
}
}
}
void build(){
scanf("%s",s+1);
int lenn=strlen(s+1);
last=cnt=1;
printf("0"),ins(s[1]-'a');
int p=2;
while(p<=lenn&&s[p]==s[1])
ins(s[p]-'a'),printf(" 0"),++p;
if (p>lenn){
putchar('\n');
for(int i=1;i<=cnt;++i)
fa[i]=len[i]=0,memset(ch[i],0,sizeof(ch[i]));
return;
}
mex=p-2;
for(int i=p;i<=lenn;i++){
ins(s[i]-'a');
if(len[fa[last]]>mex)
++mex;
printf(" %d",mex+1);
}
putchar('\n');
for(int i=1;i<=cnt;++i)
fa[i]=len[i]=0,memset(ch[i],0,sizeof(ch[i]));
}
}sam;
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;++i)
sam.build();
return 0;
}
K.破忒头的匿名信
思路:一般提供了和不超过某个值的条件的时候,会有一个性质,不同长度最多sqrt(n)个。
这道题实际就是AC自动机上dp。对所有模式串建AC自动机,之后直接拿文本串上去跑,每跑到一个节点就跳它的所有match(后面会解释)。这是因为,所有match一定是当前文本串前缀的后缀,所以可以从前面转移过来,由于不同长度最多sqrt(n)个,针对一个节点,同一深度只能有一个match,所以跳的match的个数一定不超过sqrt(n)个,因此证明了时间复杂度。
要注意,如果跳裸fail树,会T,因为fail树上不一定都是终止节点,这个时候就需要用match进行优化,match表示当前节点及往上的所有fail树上的父亲中,最靠下的终止节点。跳match的时候就是trie[trie[cur].fail].match来跳。详情见代码。处理match和处理fail是同时进行的。
dp[i]表示文本串1-i最小合成代价,跑到第i+1个点的时候,跳所有match。有一个终止节点就dp[i+1]=min(dp[i+1],dp[i+1-depth]+代价)即可。
反省:
- 字符串题目做的太少,最近又没有复习,不会。很难过。
- AC自动机板子要改一下,zbh的板子过于抽象,容易忘掉初始化trie[0].fail=-1。
#include <bits/stdc++.h>
using namespace std;
#define LETTER 26
#define LL long long
const int M=5e5+90;
struct Trie{
int v,fail,depth,match,next[LETTER];
}pool[M];
Trie* const trie=pool+1;
int cnt;
char str[M],pstr[M];
int insert(char *s,int pri){
int cur=0;
for (int i=0;s[i];++i){
int &pos=trie[cur].next[s[i]-'a'];
if (!pos)
pos=++cnt;
cur=pos,trie[cur].depth=i+1;
}
if (trie[cur].v==0||trie[cur].v>pri)
trie[cur].v=pri;
return cur;
}
void build(){
queue <int>q;q.push(0);
while(!q.empty()){
int t=q.front();q.pop();
for(int i=0;i<LETTER;i++){
int &cur=trie[t].next[i];
if(cur){
q.push(cur),
trie[cur].fail=trie[trie[t].fail].next[i],
trie[cur].match=trie[cur].v?cur:trie[trie[cur].fail].match;
}
else
cur=trie[trie[t].fail].next[i];
}
}
}
LL dp[M];
void search(char *s){
int cur=0;
for(int i=0;s[i];++i){
cur=trie[cur].next[s[i]-'a'];
for(int j=trie[cur].match;j;j=trie[trie[j].fail].match)
dp[i+1]=min(dp[i+1],dp[i+1-trie[j].depth]+trie[j].v);
}
}
int main(){
int n;
trie[0].fail=-1;
scanf("%d",&n);
for (int i=1;i<=n;++i){
int c;
scanf("%s%d",pstr,&c),insert(pstr,c);
}
build();
scanf("%s",str);
int len=strlen(str);
for(int i=1;i<=len;++i)
dp[i]=1e15;
search(str);
if (dp[len]==1e15)
printf("-1\n");
else
printf("%lld\n",dp[len]);
return 0;
}
F.采蘑菇的克拉莉丝
回头再补