Diorvh

导航

【日记】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**im**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

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

  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.采蘑菇的克拉莉丝

回头再补

posted on 2020-01-14 01:11  diorvh  阅读(275)  评论(0编辑  收藏  举报