【日记】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.萨博的方程式

萨博有个方程式:

x1xorx2xorx3xor...xorxn=k

其中xor指代位运算中的异或符号。

萨博同时还对每个未知数限制了范围为0≤ximi,希望你计算出解的个数,最终答案对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

思路:这题真的是思维题目,确实很难想,但代码很好写。

  1. 需要特判0的情况。如果输入字母全都是相同的,那么输出0。
  2. 否则,集合里面的元素是连续的,从0-mex-1。实际上求的就是集合最大值+1。
  3. 考虑从i转移到i+1的情况,lcp增加的那些,肯定后缀整个都是lcp,也就是后缀整个是另外一个的前缀。这种情况下,这个整个后缀一定是endpos>1的(一次出现在最后,另一次出现在lcp另一个串的对应位置)。因此,lcp增加之后最大的那个,长度为maxlen(fa(last))。这是因为属于last节点的一定都没有增加。
  4. 那么每次检验一下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]+代价)即可。

反省:

  1. 字符串题目做的太少,最近又没有复习,不会。很难过。
  2. 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.采蘑菇的克拉莉丝

回头再补

猜你喜欢

转载自www.cnblogs.com/diorvh/p/12190114.html