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;
}