IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> ?·M·e·r·c·u·r·y·? AC自动机·杂题选做 -> 正文阅读

[C++知识库]?·M·e·r·c·u·r·y·? AC自动机·杂题选做

P8011 「Wdsr-3」蓬莱药局

多串匹配,考虑 A C AC AC自动机。

因为 2 t 2^t 2t个串是独立的,所以算出一个的概率乘上 2 t 2^t 2t就好了。

d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个位置,走到 A C AC AC自动机上 j j j这个节点,模式串的出现次数是奇数的概率,设 f [ i ] [ j ] f[i][j] f[i][j]为总概率。

c n t [ i ] cnt[i] cnt[i]为模数串在 i i i节点代表的节点中出现次数的奇偶性,那么转移时枚举这一位是什么字符,乘上对应的概率。

并且因为要求是奇数,所以转移完要看看如果某个节点的 c n t cnt cnt为1,也就是说走到这个节点就会让奇偶性改变,那么概率取反,也就是用总集 f [ i ] [ j ] f[i][j] f[i][j]减掉不合法的 g [ i ] [ j ] g[i][j] g[i][j]

同时,因为转移可以写成矩阵乘法的形式,所以我们可以提前把概率的矩阵做一下K次幂,因为矩阵乘法具有结合律。

同时因为对前两个点这样的话跑不过,需要数据分治一下。

第一个点直接就AC自动机匹配就好了,第二个点瓶颈在于矩阵乘法,不过发现只会出现数字1,那么我们只需要把不是1的压成一块就好了。

#include<bits/stdc++.h>
using namespace std;
template <typename T>inline void read(T &x)
{
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int mod = 1e9+7;
inline int myplus(int a,int b){return (a+b)>=mod?a+b-mod:a+b;}
inline int reduce(int a,int b){return (a-b<0?a-b+mod:a-b);}
int n,m,k,t;
const int N = 1e6+7;
int tr[N][101];
int cnt[N],f[2][N],g[2][N];
int Next[N];
int A,a[N];
int S[N];
int tot=0;
void Extend()
{
	int p=0;
	for(int i=1;i<=A;i++)
	{
		int c=a[i];
		if(!tr[p][c]) tr[p][c]=++tot;
		p=tr[p][c];
	}
	cnt[p]^=1;
}
queue<int> q;
int L=2;
void Build()
{
	for(int i=1;i<=L;i++)
	if(tr[0][i]) q.push(tr[0][i]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		cnt[x]^=cnt[Next[x]];
		for(int c=1;c<=L;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[y]=tr[Next[x]][c];
				q.push(y);
			} 
		}
	}
}
int P[1200][1200],Q[1200][1200],R[1200][1200];
void mulPP()
{
	for(int i=1;i<=k;i++)
	for(int j=1;j<=k;j++)
	R[i][j]=0;
	for(int i=1;i<=k;i++)
	for(int j=1;j<=L;j++)
	for(int l=1;l<=L;l++)
	R[i][j]=myplus(R[i][j],1ll*P[i][l]*P[l][j]%mod);
	for(int i=1;i<=k;i++)
	for(int j=1;j<=L;j++)
	P[i][j]=R[i][j];
}
void mulQP()
{
	for(int i=1;i<=k;i++)
	for(int j=1;j<=k;j++)
	R[i][j]=0;
	for(int i=1;i<=k;i++)
	for(int j=1;j<=L;j++)
	for(int l=1;l<=k;l++)
	R[i][j]=myplus(R[i][j],1ll*Q[i][l]*P[l][j]%mod);
	for(int i=1;i<=k;i++)
	for(int j=1;j<=k;j++)
	Q[i][j]=R[i][j];
}
void Powerful(int B)
{
	for(int i=1;i<=k;i++)
	Q[i][i]=1;
	while(B)
	{
		if(B&1) mulQP();
		mulPP();
		B>>=1;
	}
}
int Pow(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return res;
}
int main()
{
	read(n);read(m);read(k);read(t);
	for(int i=1;i<=n;i++)
	read(S[i]);
	for(int i=1;i<=m;i++)
	{
		read(A);
		for(int j=1;j<=A;j++)
		read(a[j]);
		Extend();
	}
	for(int i=1;i<=k;i++)
	{
		int sum=0;
		for(int j=1;j<=k;j++)
		{
			read(P[i][j]);
			sum=myplus(sum,P[i][j]);
		}
		sum=Pow(sum,mod-2);
		for(int j=1;j<=k;j++)
		P[i][j]=1ll*P[i][j]*sum%mod;
	}
	if(k>100)
	{
		for(int i=1;i<=k;i++)
		{
			P[i][2]=reduce(1,P[i][1]);
			for(int j=3;j<=k;j++)
			P[i][j]=0;
		}
		L=2;
	}
	else L=k;
	Build();
	if(!t)
	{
		int ans=0;
		int p=0;
		for(int i=1;i<=n;i++)
		{
			int c=S[i];
			p=tr[p][c];
			ans^=cnt[p];
			printf("%d\n",ans);
		}
		return 0;
	}
	Powerful(t);
	f[0][0]=1;
	int K=Pow(2,t);
	int *f0=f[0],*f1=f[1],*g0=g[0],*g1=g[1];
	for(int i=1;i<=n;i++,swap(f0,f1),swap(g0,g1))
	{
		for(int x=0;x<=tot;x++)
		f1[x]=g1[x]=0;
		for(int x=0;x<=tot;x++)
		{
			if(!f0[x])continue;
			for(int c=1;c<=k;c++)
			{
				int y=tr[x][c];
				f1[y]=myplus(f1[y],1ll*f0[x]*Q[S[i]][c]%mod);
				g1[y]=myplus(g1[y],1ll*g0[x]*Q[S[i]][c]%mod);
			}
		}
		int ans=0;
		for(int x=0;x<=tot;x++)
		{
			if(cnt[x]) g1[x]=reduce(f1[x],g1[x]);
			ans=myplus(ans,g1[x]);
		}
		printf("%lld\n",1ll*ans*K%mod);
	}
	return 0;
}

[JRKSJ R4] Salieri

把文本串在AC自动机上跑,走到一个点就给这个点到根的路径权值加1,那么每个点的权值就是出现次数。

可以想到建出所有走过的点的虚树,那么一条虚路径的权值是相同的。

那么我们就是要求 c n t × w cnt\times w cnt×w k k k的值。

二分答案,对于每个链单独判断,因为 c n t cnt cnt是相等的,所以就是求

w ≥ ? a n s c n t ? w\geq \lceil \frac{ans}{cnt}\rceil w?cntans??
的个数,用主席树维护就好了。

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
int tr[N][4];
int n,m;
char s[N];
int v[N];
int End[N];
int tot=1;
vector<int> adj[N];
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int Next[N]; 
void Extend(int x)
{
	int p=1;
	int len=strlen(s+1);
	for(int i=1;i<=len;i++)
	{
		int c=s[i]-'a';
		if(!tr[p][c]) tr[p][c]=++tot;
		p=tr[p][c];
	}
	End[x]=p;
	adj[p].push_back(x);
}
queue<int> q;
int rot[N];
const int M = 1e7+7;
int lson[M],rson[M],cnt[M];
int idx=0;
inline int clone(int x)
{
	int k=++idx;
	lson[k]=lson[x];
	rson[k]=rson[x];
	cnt[k]=cnt[x]+1;
	return k;
}
int ins(int A,int l,int r,int x)
{
	int k=clone(A);
	if(l==r)return k;
	int mid=(l+r)>>1;
	if(x<=mid) lson[k]=ins(lson[A],l,mid,x);
	else rson[k]=ins(rson[A],mid+1,r,x);
	return k;	
}
int ask(int x,int y,int l,int r,int L,int R)
{
	if(L>R)return 0;
	if(!y) return 0;
	if(L<=l&&r<=R) return cnt[y]-cnt[x];
	int mid=(l+r)>>1;
	int res=0;
	if(L<=mid) res+=ask(lson[x],lson[y],l,mid,L,R);
	if(R>mid) res+=ask(rson[x],rson[y],mid+1,r,L,R);
	return res;
}
int top[N],fa[N],dep[N],siz[N],son[N]; 
int dfn[N],timer=0;
void dfs(int x)
{
	dfn[x]=++timer;
	siz[x]=1;
	rot[x]=rot[fa[x]];
	for(auto u:adj[x])
	rot[x]=ins(rot[x],1,1000,v[u]);
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs(y);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])son[x]=y;
	}
}
void Exdfs(int x,int topth)
{
	top[x]=topth;
	if(!son[x])return ;
	Exdfs(son[x],topth);
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa[x]||y==son[x])continue;
		Exdfs(y,y);
	}
}
int LCA(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
void Build()
{
	for(int c=0;c<4;c++)
	tr[0][c]=1;
	q.push(1);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int c=0;c<4;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[y]=tr[Next[x]][c];
				q.push(y);
			}
		}
	}
	for(int i=2;i<=tot;i++)
	add(Next[i],i);
	dfs(1);
	Exdfs(1,1);
}
bool cmp(int x,int y)
{
	return dfn[x]<dfn[y];
}
int h[N];
int sta[N],tp=0,K=0;
void Extract()
{
	sort(h+1,h+K+1,cmp);
	t=0;
	tp=0;
	sta[++tp]=1;
	link[1]=0;
	for(int i=1;i<=K;i++)
	{
		if(h[i]==1||h[i]==h[i-1]) continue;
		int lca=LCA(h[i],sta[tp]);
		if(lca!=sta[tp])
		{
			while(dfn[sta[tp-1]]>dfn[lca]) 
			add(sta[tp-1],sta[tp]),tp--;
			if(sta[tp-1]!=lca)
			{
				link[lca]=0;
				add(lca,sta[tp--]);
				sta[++tp]=lca;
			}
			else  add(lca,sta[tp--]);
		}
		link[h[i]]=0;
		sta[++tp]=h[i];
	}
	for(int i=1;i<tp;i++)
	add(sta[i],sta[i+1]);
}
int tim[N];
void getsiz(int x)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		getsiz(y);
		tim[x]+=tim[y];
	}
}
void getclr(int x)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		getclr(y);
	}
	tim[x]=0;
}
template <typename T>inline void read(T &x)
{
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
int Ans=0;
int ceil(int x,int y)
{
	if(x%y==0) return x/y;
	return x/y+1;
}
void getans(int x,int limit)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		getans(y,limit);
		Ans+=ask(rot[x],rot[y],1,1000,ceil(limit,tim[y]),1000);
//		cout<<x<<'-'<<y<<' '<<ceil(limit,tim[y])<<' '<<ask(rot[x],rot[y],1,1000,1,1000)<<endl;
	}
}
int account(int mid)
{
	Ans=0;
	getans(1,mid);
	return Ans;
}
int main()
{
	read(n);read(m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		read(v[i]);
		Extend(i);
	}
	Build();
	while(m--)
	{
		scanf("%s",s+1);
		int len=strlen(s+1);
		int k;
		read(k);
		K=0;
		int p=1;
		for(int i=1;i<=len;i++)
		{
			int c=s[i]-'a';
			p=tr[p][c];
			h[++K]=p;
			tim[p]++;
		}
		Extract();
		getsiz(1);
		int l=1,r=1e9,mid,ans=0;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(account(mid)>=k)
			{
				ans=mid;
				l=mid+1;
			} 
			else r=mid-1;
		}
		getclr(1);
		printf("%d\n",ans);
	}
	return 0;
}

DDOSvoid 的馈赠

官方题解太神了,写了个较劣的做法。

首先建出AC自动机,设 F [ x ] F[x] F[x] x x x到根的路径上经过的字符串的集合,用 b i t s e t bitset bitset存一下。可以在建AC自动机的时候存出来。

G [ x ] G[x] G[x] t x t_x tx?的子串集合,这个只需要把这个串在AC自动机上跑,然后求经过所有节点的 F F F的并就行了。

询问可以直接合并两个bitset。

时间复杂度 O ( n 2 + n m + n q w ) O(\frac{n^2+nm+nq}{w}) O(wn2+nm+nq?)可以冲过去,但是空间不太行。

考虑把 n n n分块,每块分别处理,这样空间就变成了了 O ( n + m B w ) O(\frac{n+mB}{w}) O(wn+mB?)

在空间保证的情况下B可以取大点。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int tr[N][26];
int Next[N];
int End[N];
char str[N];
int tot=0; 
const int B=18000;
char s[N];
bitset<B> F[N],H[N];
void Extend(int x,int l,int r)
{
	int p=0;
	for(int i=l;i<=r;i++)
	{
		int c=s[i]-'a';
		if(!tr[p][c]) tr[p][c]=++tot;
		p=tr[p][c];
	}
	F[p][x]=1;
}
queue<int> q;
void Build()
{
	for(int c=0;c<26;c++)
	if(tr[0][c]) q.push(tr[0][c]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		F[x]|=F[Next[x]];
		for(int c=0;c<26;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[y]=tr[Next[x]][c];
				q.push(y);
			}
		}
	}
}
int n,m,Q;
int l[N],r[N];
char t[N];
int L[N],R[N];
int X[N],Y[N];
template <typename T>inline void read(T &x)
{
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
int lpos[N],rpos[N];
int bel[N],cnt;
int ans[N];
void clear()
{
	for(int i=0;i<=tot;i++)
	{
		F[i].reset();
		for(int c=0;c<26;c++)
		tr[i][c]=0;
		Next[i]=0;
	}
	tot=0;
	for(int i=1;i<=m;i++)
	H[i].reset();
}
int main()
{
	read(n);read(m);read(Q);
	int now=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",str+1);
		int len=strlen(str+1);
		l[i]=now+1;
		r[i]=l[i]+len-1;
		now=r[i];
		for(int p=1;p<=len;p++)
		s[l[i]+p-1]=str[p];
	}
	now=0;
	for(int i=1;i<=m;i++)
	{
		scanf("%s",str+1);
		int len=strlen(str+1);
		L[i]=now+1;
		R[i]=L[i]+len-1;
		now=R[i];
		for(int p=1;p<=len;p++)
		t[L[i]+p-1]=str[p];
	}
	for(int i=1;i<=Q;i++)
	read(X[i]),read(Y[i]);
	for(int i=1;i<=n;i++)
	{
		bel[i]=(i-1)/B+1;
		rpos[bel[i]]=i;
		if(!lpos[bel[i]]) lpos[bel[i]]=i;
		cnt=bel[i];
	}
	for(int o=1;o<=cnt;o++)
	{
		clear();
		for(int i=lpos[o];i<=rpos[o];i++)
		Extend(i-lpos[o]+1,l[i],r[i]);
		Build();
		for(int i=1;i<=m;i++)
		{
			int p=0;
			for(int j=L[i];j<=R[i];j++)
			{
				int c=t[j]-'a';
				p=tr[p][c];
				H[i]|=F[p];
			} 
		}
		for(int i=1;i<=Q;i++)
		ans[i]+=(H[X[i]]&H[Y[i]]).count();
	}
	for(int i=1;i<=Q;i++)
	printf("%d\n",ans[i]);
	return 0;
} 

P6698 [BalticOI 2020 Day2] 病毒/CF1387C Viruses

考虑把匹配看成在 A C AC AC自动机上走,那么就是强制不能匹配任何一个串,也就是说不能走到任何一个祖先链上有结束节点的点。

那么询问的就是最短路,答案为 Y e s Yes Yes可以看成无法到达,也就是最短路为正无穷。

f [ c ] [ x ] [ y ] f[c][x][y] f[c][x][y]为把 c c c字符变异后从 x x x走到 y y y的最短路。

考虑怎么求,我们用类似SPFA的方式转移,一开始把字符0和1放入队列里。

使用迭代来更新,每次用一个当前队首的字符c来更新其他的,那么只有当c存在于某个字符x的b序列里时,x才可能被更新,因此枚举出现过x的某个突变 o o o

考虑用这个突变来更新其他的 f f f

枚举一个起点 S S S,设 d p [ i ] [ x ] dp[i][x] dp[i][x]为考虑了突变序列的前i个位置,S到x号节点的最短路。

转移时简单的, d p [ i + 1 ] [ y ] = m i n ( d p [ i ] [ x ] + f [ b [ i ] [ x ] [ y ] ] ) dp[i+1][y]=min(dp[i][x]+f[b[i][x][y]]) dp[i+1][y]=min(dp[i][x]+f[b[i][x][y]])
所有的转移完了后看一下有没有某个值被更新了,如果被更新了,那么就把当前字符入队。

复杂度看起来很玄学,但是跑的还挺快的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 120;
int tr[N][2];
int n,m,K;
vector<int> appear[N];
int f[N][N][N],g[N][N];
bool adj[N];
int tot=0;
vector<int> b[N];
int a[N];
int k[N];
int s[N];
int l;
void Extend()
{
	int p=0;
	for(int i=1;i<=l;i++)
	{
		int c=s[i];
		if(!tr[p][c]) tr[p][c]=++tot;
		p=tr[p][c];
	}
	adj[p]=1;
}
int Next[N];
queue<int> q;
void Build()
{
	for(int c=0;c<2;c++)
	if(tr[0][c]) q.push(tr[0][c]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		adj[x]|=adj[Next[x]];
		for(int c=0;c<2;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[y]=tr[Next[x]][c];
				q.push(y);
			}
		}
	}
}
template <typename T>inline void read(T &x)
{
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
int INF=1e18;
bool vis[N];
bool update(int o)
{
	bool rel=0;
	for(int S=0;S<=tot;S++)
	{
		memset(g,0x3f,sizeof(g));
		g[0][S]=0;
		for(int i=0;i<k[o];i++)
		{
			int c=b[o][i];
			for(int x=0;x<=tot;x++)
			{
				if(g[i][x]>=INF)continue;
				for(int y=0;y<=tot;y++)
				g[i+1][y]=min(g[i+1][y],g[i][x]+f[c][x][y]);
			}
		} 
		for(int x=0;x<=tot;x++)
		{
			if(g[k[o]][x]<f[a[o]][S][x])
			{
				f[a[o]][S][x]=g[k[o]][x];
				rel=1;
			}
		}	
	}
	return rel;
}
signed main()
{
	read(n);read(m);read(K);
	for(int i=1;i<=m;i++)
	{
		read(a[i]);read(k[i]);
		for(int j=0;j<k[i];j++)
		{
			int x;read(x);
			b[i].push_back(x);
			appear[x].push_back(i);
		}
	}
	for(int i=1;i<=K;i++)
	{
		read(l);
		for(int j=1;j<=l;j++)
		read(s[j]);
		Extend(); 
	}
	Build();
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	for(int i=0;i<=tot;i++)
	{
		if(adj[i])continue;
		for(int c=0;c<2;c++)
		{
			int y=tr[i][c];
			if(!adj[y]) f[c][i][y]=1;
		}
	}
	q.push(0);q.push(1);
	vis[0]=vis[1]=1;
	while(!q.empty())
	{
		int ch=q.front();
		q.pop();
		vis[ch]=0;
		for(auto i:appear[ch])
		{
			if(update(i)&&!vis[a[i]])
			{
				vis[a[i]]=1;
				q.push(a[i]);
			}
		}
	}
	for(int i=2;i<=n-1;i++)
	{
		int ans=INF;
		for(int x=0;x<=tot;x++)
		ans=min(ans,f[i][0][x]);
		if(ans>=INF) printf("YES\n");
		else printf("NO %lld\n",ans);
	}
	return 0;
}

CF585F Digits of Number Pi

显然是数位dp,先差分一下。

建出 A C AC AC自动机,那么出现了长度大于 ? d 2 ? \lfloor \frac{d}{2} \rfloor ?2d??的子串,当且匹配时仅当经过了AC自动机上深度大于这个限制的点。

那么状态就很简单了。

d p [ i ] [ 0 / 1 ] [ 0 / 1 ] [ x ] dp[i][0/1][0/1][x] dp[i][0/1][0/1][x]表示填了前 i i i个位置,是否卡上界,是否已经经过了符合的点,走到了AC自动机的某个点的方案数。

转移是简单的。

同样的因为我们只需要支持子串匹配,SAM也是可以的,就是能走就走,不能走就跳。

因为某些原因写的是SAM的版本。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e3+7;
const int mod = 1e9+7;
int n,m;
int len[N],tr[N][10],fa[N];
char s[N],A[N],B[N];
int tot=1,last=1;
void copy(int x,int y)
{
	fa[x]=fa[y];
	len[x]=len[y];
	for(int c=0;c<10;c++)
	tr[x][c]=tr[y][c];
} 
int hung[N][10];
void Extend(int c)
{
	int p=last,np=last=++tot;
	len[np]=len[p]+1;
	while(p&&!tr[p][c])
	{
		tr[p][c]=np;
		p=fa[p];	
	}	
	if(!p) fa[np]=1;
	else
	{
		int q=tr[p][c];
		if(len[q]==len[p]+1) fa[np]=q;
		else
		{
			int nq=++tot;
			copy(nq,q);
			len[nq]=len[p]+1;
			fa[np]=fa[q]=nq;
			while(p&&tr[p][c]==q)
			{
				tr[p][c]=nq;
				p=fa[p];
			}
		}
	}
} 
int f[55][2020][55][2][2];
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
void dfs(int x)
{
	for(int c=0;c<10;c++)
	if(tr[x][c]) hung[x][c]=x;
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		for(int c=0;c<10;c++)
		hung[y][c]=hung[x][c];
		dfs(y);
	}
}
int plu(int x,int y) 
{
	return (x+y>=mod?x+y-mod:x+y);
}
int mul(int x,int y)
{
	return 1ll*x*y%mod;
}
int a[N];
int dp(int x,int cur,int lenth,bool limit,bool ok)
{
	if(x==m+1) return ok;
	if(f[x][cur][lenth][limit][ok]!=-1) return f[x][cur][lenth][limit][ok];
	int up=(limit?a[x]:9);
	int res=0;
	for(int c=0;c<=up;c++) 
	{
		if(ok) res=plu(res,dp(x+1,cur,lenth,limit&&c==up,1));
		else
		{
			if(tr[cur][c]) res=plu(res,dp(x+1,tr[cur][c],lenth+1,limit&&c==up,(lenth+1)>=m/2));
			else
			{
				int p=hung[cur][c];
				if(p) res=plu(res,dp(x+1,tr[p][c],len[p]+1,limit&&c==up,(len[p]+1)>=m/2));
				else res=plu(res,dp(x+1,1,0,limit&&c==up,0));
			}			
		}
	}
	f[x][cur][lenth][limit][ok]=res;
	return res;
}
int main()
{
	scanf("%s %s %s",s+1,A+1,B+1);
	n=strlen(s+1);m=strlen(A+1);
	for(int i=1;i<=n;i++)
	Extend(s[i]-'0');
	for(int i=2;i<=tot;i++)
	add(fa[i],i);
	dfs(1);
	memset(f,-1,sizeof(f));
	for(int i=1;i<=m;i++)
	a[i]=B[i]-'0';
	A[m]--;
	int o=m;
	while(A[o]=='0'-1)
	{
		A[o-1]--;
		A[o--]='9';
	} 
	int res=dp(1,1,0,1,0);
	memset(f,-1,sizeof(f));
	for(int i=1;i<=m;i++)
	a[i]=A[i]-'0';
	res=(res-dp(1,1,0,1,0)+mod)%mod;
	cout<<res;
	return 0;
}

CF590E Birthday

前置知识: D i l w o r t h Dilworth Dilworth定理。

可以看一下P4298 [CTSC2008]祭祀

匹配关系也是一种偏序关系。

建出AC自动机,然后把有子串关系的节点连边,那么就会有一个DAG,我们要找出最长反链。

建边可以用路径压缩,详情看代码。

剩下的就是上边的那个题里。

输出方案真恶心。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e7+7;
char s[N];
int S[N];
int tr[N][2],Next[N];
const int M = 755;
int End[N];
int St[M],Ed[M],len[M];
int tot=0;
int n,m;
void Insert(int x)
{
	scanf("%s",s+1);
	len[x]=strlen(s+1);
	St[x]=Ed[x-1]+1;
	Ed[x]=St[x]+len[x]-1;
	int p=0;
	for(int i=1;i<=len[x];i++)
	{
		int c=s[i]-'a';
		if(!tr[p][c]) tr[p][c]=++tot;
		S[++m]=c;
		p=tr[p][c];
	}
	End[p]=x;
} 
int pre[N];
bool vis[N];
queue<int> q;
void GetNxt()
{
	for(int c=0;c<2;c++)
	if(tr[0][c]) q.push(tr[0][c]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int c=0;c<=1;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[tr[x][c]]=tr[Next[x]][c];
				q.push(tr[x][c]);
			}
		}
	}
}
int get(int x)
{
	if(!x) return -1;
	if(End[x]) return x;
	if(pre[x]) return pre[x];
	return pre[x]=get(Next[x]);
}
void GetPre()
{
	for(int i=0;i<=tot;i++)
	pre[i]=-1;
	q.push(0);
	while(!q.empty())
	{
		int x=q.front();
		if(pre[x]==-1) pre[x]=get(Next[x]);
		q.pop();
		for(int c=0;c<=1;c++)
		{
			int y=tr[x][c];
			if(!vis[y])
			{
				vis[y]=1;
				q.push(y);
			}
		}
	}
}
bool d[M][M];
void Substring(int x)
{
	int p=0;
	for(int i=St[x];i<=Ed[x];i++)
	{
		int c=S[i];
		p=tr[p][c];
		int l=(End[p]?p:pre[p]);
		while(l&&l!=-1&&End[l]==x)
		l=pre[l];
		if(l!=-1&&l)
		{
			d[x][End[l]]=1;
		}
	}
}
void GetSub()
{
	for(int i=1;i<=n;i++)
	Substring(i);
}
int ins[M],match[M];
int tag=0;
struct edge
{
	int y,next;
}e[M*M];
int link[M],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int pos[M],A[M],B[M];
bool dfs(int x)
{
	if(ins[x]==tag) return 0;
	ins[x]=tag;
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(!match[y]||dfs(match[y]))
		{
			match[y]=x;
			pos[x]=y;
			return 1;
		}
	} 
	return 0;
}
void mark(int x)
{
	if(A[x]) return;
	A[x]=1;
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(!B[y])
		{
			B[y]=1;
			mark(match[y]);
		}
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	Insert(i);
	GetNxt();
	GetPre();
	GetSub();
	for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	d[i][j]|=(d[i][k]&d[k][j]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(i==j) continue;
		if(d[i][j]) add(i,j);
	}
	int ans=n;
	for(int i=1;i<=n;i++)
	{
		tag++;
		ans-=(int)dfs(i);
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
	if(!pos[i]) mark(i);
	for(int i=1;i<=n;i++)
	if(A[i]&&!B[i]) printf("%d ",i);
 	return 0;
}

CF587F Duff is Mad

对串长根号分治,

s k s_k sk?长度大于根号,那么可以对每个 k k k分别做。

s k s_k sk?经过的点权值加一,那么一个串的出现次数就是子树和。

可以提前预处理一下。

差分一下再扫描线就好了。

小于根号的话,我们把答案拆成两个的差,然后扫描n个串,给每个串的子树加,询问暴力遍历就好了。

用分块平衡复杂度可以做到
O ( n n ) O(n\sqrt n) O(nn ?)

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
const int S = 400;
typedef long long LL;
int tr[N][27],Next[N];
vector<int> str[N];
int len[N],End[N];
int tot=0;
void Insert(int x)
{
	int p=0;
	for(int i=1;i<=len[x];i++)
	{
		int c=str[x][i];
		if(!tr[p][c]) tr[p][c]=++tot;
		p=tr[p][c];
	}
	End[x]=p;
}
queue<int> q;
void Build()
{
	for(int c=0;c<26;c++)
	if(tr[0][c]) q.push(tr[0][c]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int c=0;c<26;c++)
		{
			int y=tr[x][c];
			if(!y) tr[x][c]=tr[Next[x]][c];
			else
			{
				Next[y]=tr[Next[x]][c];
				q.push(y);
			}
		}
	}
}
int n,m;
char s[N];
int B;
struct Query
{
	int x,tp,id;
};
vector<Query> Q1[N],Q2[N];
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int st[N],ed[N],siz[N];
int top=0;
int seq[N];
void dfs(int x)
{
	siz[x]=1;
	st[x]=++top;
	seq[top]=x;
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		dfs(y);
		siz[x]+=siz[y];
	}
	ed[x]=top;
}
int L[N],R[N],cnt=0;
int bel[N];
LL a[N],tag[N];
void Blocks()
{
	for(int i=1;i<=tot+1;i++)
	{
		bel[i]=(i-1)/B+1;
		R[bel[i]]=i;
		if(!L[bel[i]]) L[bel[i]]=i;
		cnt=max(cnt,bel[i]);
	}
}
void Add(int l,int r,int v)
{
	if(bel[r]-bel[l]<=1)
	{
		for(int i=l;i<=r;i++)
		a[i]+=v;
		return;
	}
	else
	{
		for(int i=l;i<=R[bel[l]];i++) a[i]+=v;
		for(int i=L[bel[r]];i<=r;i++) a[i]+=v;
		for(int i=bel[l]+1;i<bel[r];i++) tag[i]+=v;
		return; 
	}
}
LL Ans[N];
bool cmp(Query a,Query b)
{
	return a.x<b.x;
}
int main()
{
	cin>>n>>m;
	for(int x=1;x<=n;x++)
	{
		scanf("%s",s+1);
		str[x].push_back(0);
		len[x]=strlen(s+1);
		for(int i=1;i<=len[x];i++)
		str[x].push_back(s[i]-'a');
		Insert(x);
	}
	Build();
	B=sqrt(tot*1.0+1);
	for(int i=1;i<=m;i++)
	{
		int l,r,x;
		scanf("%d %d %d",&l,&r,&x);
		if(len[x]>B)
		{
			if(l!=1) Q2[x].push_back((Query){l-1,-1,i});
			Q2[x].push_back((Query){r,1,i});
		}
		else
		{
			if(l!=1) Q1[l-1].push_back((Query){x,-1,i});
			Q1[r].push_back((Query){x,1,i});
		}
	}
	for(int i=1;i<=tot;i++)
	add(Next[i],i);
	dfs(0);
	Blocks();
	for(int i=1;i<=n;i++)
	{
		Add(st[End[i]],ed[End[i]],1);
		for(int p=0;p<(int)Q1[i].size();p++)
		{
			int x=Q1[i][p].x,tp=Q1[i][p].tp,id=Q1[i][p].id;
			int r=0;
			for(int j=1;j<=len[x];j++)
			{
				int c=str[x][j];
				r=tr[r][c];
				Ans[id]+=1ll*(a[st[r]]+tag[bel[st[r]]])*tp;
			} 
		}
	}
	for(int x=1;x<=n;x++)
	{
		if(Q2[x].size())
		{
			memset(a,0,sizeof(a));
			int p=0;
			for(int i=1;i<=len[x];i++)
			{
				int c=str[x][i];
				p=tr[p][c];
				a[p]++;
			}
			sort(Q2[x].begin(),Q2[x].end(),cmp);
			for(int i=top;i>=2;i--)
			a[Next[seq[i]]]+=a[seq[i]];		
			int j=1;
			LL sum=0;
			for(p=0;p<(int)Q2[x].size();p++)
			{
				int y=Q2[x][p].x;
				while(j<=y)  sum+=a[End[j++]];
				Ans[Q2[x][p].id]+=1ll*sum*Q2[x][p].tp;
			}
		}
	}
	for(int i=1;i<=m;i++)
	printf("%lld\n",Ans[i]);
	return 0;
} 

其他题目

CF1110H Modest Substrings

P5599 【XR-4】文本编辑器

CF1483F Exam

[BJOI2016]打字机

P3735 [HAOI2017]字符串

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章           查看所有文章
加:2022-09-30 00:33:07  更:2022-09-30 00:38:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年3日历 -2024/3/28 19:43:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码