zoukankan      html  css  js  c++  java
  • NOI 2011 阿狸的打字机

    https://vjudge.net/problem/%E8%AE%A1%E8%92%9C%E5%AE%A2-T2372

    题目

    阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
    经阿狸研究发现,这个打字机是这样工作的:
    l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
    l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
    l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
    例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
    a
    aa
    ab
    我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
    阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

    1<=N<=10^5
    1<=M<=10^5
    输入总长<=10^5

    #Sample Input
    
    aPaPBbP
    3
    1 2
    1 3
    2 3
    
    #Sample Output
    
    2
    1
    0
    

    题解

    刚抄了一遍AC自动机板子的时候高兴地去做这道题,直接套板子,然后就悲剧了……

    如果直接暴力查询,会发现时间复杂度是O(NM),直接TLE

    然后就不会了……

    实际上每次查询只是判断从trie的根节点到第x个字符串的末尾中,有多少节点,通过fail指针不断前进,可以指向y字符串的末尾

    如果把fail指针反向,会发现这些指针形成了一棵树,除了trie的根节点,每个节点都只有一个父亲(最长后缀)

    那么只是统计,从trie的根节点到第x个字符串的末尾中,有多少个节点是y字符串末尾的后代

    然后就有个没见过的操作= =,因为前序遍历在树上形成的时钟有个特殊的性质,后代的时钟包含在前面的时钟区间内,因此可以直接用树状数组统计后代个数

    时间复杂度$O(NMlog N)$,还是TLE

    然后发现可以离线,没必要每次都从头走,到相应节点的时候再计算,时间复杂度$O((N+M)log N)$,可以过了

    由于数据范围比较大,还需要加个快读

    AC代码

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<queue>
    #include<stack>
    using namespace std;
    char buf[1<<21], *p1=buf, *p2=buf;
    #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    template<class T>
    inline void read(T&x) {
    	x=0; char ch; do ch=getchar(); while(!isdigit(ch));
    	do {x=x*10+ch-'0'; ch=getchar();} while(isdigit(ch));
    }
    #define MAXN 100007
    char S[MAXN];
    int ans[MAXN];
    namespace ql { int hd[MAXN], to[MAXN], nxt[MAXN];}
    namespace fl { int hd[MAXN], to[MAXN], nxt[MAXN], tn;}
    namespace ac {
    	int trie[MAXN][26], sz, fail[MAXN];
    	int ite[MAXN];
    	int L[MAXN], R[MAXN], dfsclock;
    	inline void init(int x) {
    		memset(trie[x],0,sizeof trie[x]);
    	}
    	void dfs(int p) {
    		using namespace fl;
    		L[p]=++dfsclock;
    		for(int i=hd[p]; ~i; i=nxt[i]) {
    			dfs(to[i]);
    		}
    		R[p]=dfsclock;
    	}
    	inline void calcf() {
    		using namespace fl; tn=0; memset(hd,-1,sizeof hd);
    		queue<int> q; fail[0]=0;
    		for(int i=0; i<26; i++) if(trie[0][i]) {
    			q.push(trie[0][i]); fail[trie[0][i]]=0;
    			nxt[tn]=hd[0]; hd[0]=tn; to[tn]=trie[0][i]; tn++;
    		}
    		while(!q.empty()) {
    			int now=q.front(); q.pop();
    			for(int i=0; i<26; i++) {
    				int t=trie[now][i];
    				if(!t) {
    					trie[now][i] = trie[fail[now]][i];
    					continue;
    				}
    				fail[t]=trie[fail[now]][i];
    				nxt[tn]=hd[fail[t]]; hd[fail[t]]=tn; to[tn]=t; tn++;
    				q.push(t);
    			}
    		}
    		dfsclock=0;
    		dfs(0);
    	}
    	inline void build() {
    		int l=strlen(S); stack<int> st;
    		init(0); sz=0;
    		int now=0, id=1;
    		for(int i=0; i<l; i++) {
    			if(S[i]=='B') {
    				if(st.size()) {
    					now = st.top();
    					st.pop();
    				} else now=0;
    			} else if(S[i]=='P') {
    				ite[id]=now;
    				id++;
    			} else {
    				st.push(now);
    				int t=trie[now][S[i]-'a'];
    				if(!t) {
    					t=trie[now][S[i]-'a']=++sz;
    					init(t);
    				}
    				now=t;
    			}
    		}
    		calcf();
    	}
    	int fw[MAXN];
    	inline void opa(int x, int y) {
    		for(;x<=dfsclock; x+=x&-x) {
    			fw[x]+=y;
    		}
    	}
    	inline int opq(int x) {
    		int ans=0;
    		for(;x;x-=x&-x) {
    			ans+=fw[x];
    		}
    		return ans;
    	}
    	inline void calcans() {
    		memset(fw,0,sizeof fw);
    		int l=strlen(S); stack<int> st;
    		int now=0; int id=1;
    		for(int i=0; i<l; i++) {
    			if(S[i]=='B') {
    				if(st.size()) {
    					opa(L[now],-1);
    					now=st.top();
    					st.pop();
    				} else now=0;
    			} else if(S[i]=='P') {
    				using namespace ql;
    				for(int i=hd[id]; ~i; i=nxt[i]) {
    					int t=ite[to[i]];
    					ans[i]=opq(R[t])-opq(L[t]-1);
    				}
    				id++;
    			} else {
    				st.push(now);
    				now = trie[now][S[i]-'a'];
    				opa(L[now],1);
    			}
    		}
    
    	}
    };
    int main() {
    	scanf("%s", S);
    	int m; read(m);
    	using namespace ql;
    	memset(hd,-1,sizeof hd);
    	for(int i=0; i<m; i++) {
    		int x,y; read(x); read(y);
    		nxt[i]=hd[y]; to[i]=x; hd[y]=i;
    	}
    	ac::build();
    	ac::calcans();
    	for(int i=0; i<m; i++) printf("%d
    ", ans[i]);
    }
    
  • 相关阅读:
    Kubernetes实战:高可用集群的搭建和部署
    华为云MVP程云:知识化转型,最终要赋能一线
    支持60+数据传输链路,华为云DRS链路商用大盘点
    关于单元测试的那些事儿,Mockito 都能帮你解决
    深入原生冰山安全体系,详解华为云安全服务如何构筑全栈安全
    云小课|ModelArts Pro 视觉套件:零代码构建视觉AI应用
    FLINK重点原理与机制:内存(1)task之间的数据传输
    FLINK重点原理与机制:内存(2)网络流控及反压机制剖析(一)
    FLINK重点原理与机制:状态(3)两阶段提交
    FLINK重点原理与机制:状态(2)Flink的检查点算法CHECKPOINT
  • 原文地址:https://www.cnblogs.com/sahdsg/p/13049987.html
Copyright © 2011-2022 走看看