BZOJ4180 字符串计数 和 SCOI2015 小凸玩矩阵

字符串计数

SD有一名神犇叫做Oxer,他觉得字符串的题目都太水了,于是便出了一道题来虐蒟蒻yts1999。

他给出了一个字符串T,字符串T中有且仅有4种字符 'A', 'B', 'C', 'D'。现在他要求蒟蒻yts1999构造一个新的字符串S,构造的方法是:进行多次操作,每一次操作选择T的一个子串,将其加入S的末尾。

对于一个可构造出的字符串S,可能有多种构造方案,Oxer定义构造字符串S所需的操作次数为所有构造方案中操作次数的最小值。

Oxer想知道对于给定的正整数N和字符串T,他所能构造出的所有长度为N的字符串S中,构造所需的操作次数最大的字符串的操作次数。

蒟蒻yts1999当然不会做了,于是向你求助。

对于100%的数据,1 ≤ N ≤ 1018,1 ≤ |T| ≤ 105

题解

http://jklover.hs-blog.cf/2020/05/27/bzoj-4180-字符串计数/#more

SAM + 矩阵快速幂 floyd.

考虑如果给出了串 \(S\) ,如何算最小操作次数,将 \(S\) 放到 \(T\) 的 SAM 上去匹配,若没有出边则返回 \(1\) ,且操作次数 \(+1\) .

可以二分答案 \(mid\) ,尝试检查用 \(mid\) 次操作能构造出的串最小长度,若 \(\le n\) ,说明答案 \(\ge mid\) .

考虑如何求出用 \(mid\) 次操作能构造的串的最小长度,其实转移只会出现在根节点的出边指向的点中.

bfs 预处理出它们之间的距离,即要加入多少个字符,对转移矩阵求 \(mid​\) 次幂即可得到用 \(mid​\) 次操作的最短距离.

时间复杂度 \(O(|\sum|\cdot |T|+|\sum|^3\log^2 n)\) ,其中 \(|\sum|\) 代表字符集大小.

CO int N=2e5+10;
CO int64 inf=2e18;
char str[N];
int last=1,tot=1;
int ch[N][4],fa[N],len[N];

void extend(int c){
	int x=last,cur=last=++tot;
	len[cur]=len[x]+1;
	for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur;
	if(!x) {fa[cur]=1; return;}
	int y=ch[x][c];
	if(len[y]==len[x]+1) {fa[cur]=y; return;}
	int clone=++tot;
	copy(ch[y],ch[y]+4,ch[clone]);
	fa[clone]=fa[y],len[clone]=len[x]+1;
	fa[cur]=fa[y]=clone;
	for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
}

struct matrix {int64 v[4][4];};

matrix operator*(CO matrix&a,CO matrix&b){
	matrix ans;
	for(int i=0;i<4;++i) fill(ans.v[i],ans.v[i]+4,inf);
	for(int k=0;k<4;++k)
		for(int i=0;i<4;++i)for(int j=0;j<4;++j)
			ans.v[i][j]=min(ans.v[i][j],a.v[i][k]+b.v[k][j]);
	return ans;
}

matrix pow(matrix a,int64 b){
	matrix ans;
	for(int i=0;i<4;++i) fill(ans.v[i],ans.v[i]+4,0);
	for(;b;b>>=1,a=a*a)
		if(b&1) ans=ans*a;
	return ans;
}

matrix A;
int64 dis[N];
int vis[N];

void bfs(int id,int st){
	for(int i=1;i<=tot;++i) dis[i]=inf,vis[i]=0;
	dis[st]=0,vis[st]=1;
	deque<int> que(1,st);
	while(que.size()){
		int x=que.front();que.pop_front();
		for(int c=0;c<4;++c)if(!vis[ch[x][c]]){
			if(!ch[x][c]) A.v[id][c]=min(A.v[id][c],dis[x]+1);
			else{
				dis[ch[x][c]]=dis[x]+1,vis[ch[x][c]]=1;
				que.push_back(ch[x][c]);
			}
		}
	}
}

int64 solve(int64 k){
	matrix B=pow(A,k);
	int64 ans=inf;
	for(int i=0;i<4;++i) ans=min(ans,*min_element(B.v[i],B.v[i]+4));
	return ans;
}

int main(){
	int64 len=read<int64>();
	scanf("%s",str+1);
	int n=strlen(str+1);
	for(int i=1;i<=n;++i) extend(str[i]-'A');
	for(int i=0;i<4;++i) fill(A.v[i],A.v[i]+4,inf);
	for(int c=0;c<4;++c) bfs(c,ch[1][c]);
	int64 L=1,R=len,ans;
	while(L<=R){
		int64 mid=(L+R)>>1;
		if(solve(mid)<=len) ans=mid,L=mid+1;
		else R=mid-1;
	}
	if(solve(ans)<len) ++ans;
	printf("%lld\n",ans);
	return 0;
}

小凸玩矩阵

小凸和小方是好朋友,小方给小凸一个 \(N \times M\)\(N \leq M\))的矩阵 \(A\),要求小凸从其中选出 \(N\) 个数,其中任意两个数字不能在同一行或同一列,现小凸想知道选出来的 \(N\) 个数中第 \(K\) 大的数字的最小值是多少。

\(1 \leq K \leq N \leq M \leq 250, 1 \leq A_{i, j} \leq 10 ^ 9\)

题解

首先行列连边变成二分图。

二分答案,看看保留\(\leq \text{mid}\)的边能不能凑齐\(n-k+1\)个即可。

时间复杂度\(O(nm\sqrt{n+m}\log a)\)

CO int N=510,inf=1e9;
namespace Flow{
	int S,T;
	struct edge {int y,c,a;};
	vector<edge> to[N];
	int dis[N];
	
	IN void init(int n){
		S=n-1,T=n;
		for(int i=1;i<=n;++i) to[i].clear();
	}
	IN void link(int x,int y,int c){
		to[x].push_back({y,c}),to[y].push_back({x,0});
		to[x].back().a=to[y].size()-1,to[y].back().a=to[x].size()-1;
	}
	bool bfs(){
		fill(dis+1,dis+T+1,inf),dis[S]=0;
		deque<int> que={S};
		while(que.size()){
			int x=que.front();que.pop_front();
			for(CO edge&e:to[x])if(e.c and dis[e.y]==inf)
				dis[e.y]=dis[x]+1,que.push_back(e.y);
		}
		return dis[T]<inf;
	}
	int dfs(int x,int lim){
		if(x==T) return lim;
		int rest=lim;
		for(edge&e:to[x])if(e.c and dis[e.y]==dis[x]+1){
			int delta=dfs(e.y,min(rest,e.c));
			if(delta==0) {dis[e.y]=inf; continue;}
			rest-=delta,e.c-=delta,to[e.y][e.a].c+=delta;
			if(rest==0) break;
		}
		return lim-rest;
	}
	int main(){
		int ans=0;
		while(bfs()) ans+=dfs(S,inf);
		return ans;
	}
}

int a[N][N];

int main(){
	int n=read<int>(),m=read<int>(),K=n-read<int>()+1;
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) read(a[i][j]);
	function<bool(int)> check=[&](int lim)->bool{
		Flow::init(n+m+2);
		for(int i=1;i<=n;++i) Flow::link(Flow::S,i,1);
		for(int i=1;i<=m;++i) Flow::link(i+n,Flow::T,1);
		for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
			if(a[i][j]<=lim) Flow::link(i,j+n,1);
		return Flow::main()>=K;
	};
	int l=1,r=inf;
	while(l<r){
		int mid=(l+r)>>1;
		check(mid)?r=mid:l=mid+1;
	}
	printf("%d\n",l);
	return 0;
}

posted on 2020-06-07 19:31  autoint  阅读(178)  评论(0编辑  收藏  举报

导航