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.萨博的方程式
萨博有个方程式:
x1xorx2xorx3xor...xorxn=k
其中xor指代位运算中的异或符号。
萨博同时还对每个未知数限制了范围为0≤xi≤mi,希望你计算出解的个数,最终答案对109+7取模后输出。
输入格式:
本题设有多组数据,请处理到文件尾,保证数据组数不超过100。
对于每组测试数据:
第一行读入2个正整数n,k(n≤50,k<231);
第二行读入n个非负整数m1,m2,m3,...,mn(mi<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.采蘑菇的克拉莉丝
回头再补
来源:https://www.cnblogs.com/diorvh/p/12190114.html