【TJOI2015】BZOJ3998 弦论题解(SAM+DP)

题目:BZOJ3998.
题目大意:给定一个串 S S S和一个数 T T T,求 S S S的第 k k k小子串,若 T = 0 T=0 T=0表示子串重复算一个,否则不算一个,若子串数不到 k k k个输出 − 1 -1 1.
1 ≤ ∣ S ∣ ≤ 5 ∗ 1 0 5 , 0 ≤ T ≤ 1 1\leq |S|\leq 5*10^5,0\leq T\leq 1 1S5105,0T1.

首先看到子串就对 S S S建立SAM.

建立SAM后,我们根据 T T T分类讨论,先考虑 T = 0 T=0 T=0的情况.

首先着第 k k k小的子串可以先考虑直接从原点出发,从小到大枚举所有字符转移边.若这条字符转移边到达的状态可以构成的子串数量不足 k k k个,则肯定不往这条字符转移边转移,直接让 k k k减掉这个值,然后尝试下一条字符转移边;否则进入这条字符转移边即可.

考虑何时停止,很明显通过之前的路径到达当前状态构成的子串是唯一的,所以只要当前 k k k 1 1 1就可以停止了.

再来考虑如何求出每一个状态开始可以构成的子串数量,很明显我们可以用DP来预处理出这些值,设 f [ i ] f[i] f[i]为到状态 i i i可以构成的数量,那么:
f [ x ] = 1 + ∑ ( x , y ) ∈ E f [ y ] f[x]=1+\sum_{(x,y)\in E}f[y] f[x]=1+(x,y)Ef[y]

T = 1 T=1 T=1的情况其实类似,只是这样子一个状态可以表示的子串数量就要乘上它所代表的Right集合大小了.

时间复杂度 O ( ∣ S ∣ Σ ) O(|S|\Sigma) O(SΣ).

代码如下:

#include<bits/stdc++.h> 
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000,C=26;

int n,t,k;
char c[N+9],sc[N+9];
struct automaton{
  int s[C],len,par;
}tr[N*2+9];
int cn,last,rght[N*2+9];

void Build_sam(){last=cn=1;}

void extend(int x){
  int np=++cn,p=last;
  tr[np].len=tr[p].len+1;rght[np]=1;
  last=np;
  while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
  if (!p) tr[np].par=1;
  else{
  	int q=tr[p].s[x];
  	if (tr[p].len+1==tr[q].len) tr[np].par=q;
  	else{
	  tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
	  tr[q].par=tr[np].par=cn;
	  while (p&&tr[p].s[x]==q) tr[p].s[x]=cn,p=tr[p].par;
	}
  }
}

int q[N*2+9],v[N+9],sum[N*2+9];

void pre(){
  int tt;
  for (int i=1;i<=cn;++i) ++v[tr[i].len];
  for (int i=1;i<=n;++i) v[i]+=v[i-1];
  for (int i=cn;i>=1;--i) q[v[tr[i].len]--]=i;
  for (int i=cn;i>=1;--i)
    if (t==1) rght[tr[q[i]].par]+=rght[q[i]];
    else rght[q[i]]=1;
  rght[1]=0;
  for (int i=cn;i>=1;--i){
    tt=q[i];sum[tt]=rght[tt];
	for (int j=0;j<C;++j)
      sum[tt]+=sum[tr[tt].s[j]];
  }
}

void dfs(int x,int pos){
  if (k<=rght[x]) {k=0;return;}
  k-=rght[x];
  for (int i=0;i<C;++i)
    if (tr[x].s[i]){
      if (k<=sum[tr[x].s[i]]){
      	sc[pos]='a'+i;
      	dfs(tr[x].s[i],pos+1); 
	    return;
	  }
	  k-=sum[tr[x].s[i]];
    }
}

Abigail into(){
  scanf("%s",c+1);
  n=strlen(c+1);
  scanf("%d%d",&t,&k);
}

Abigail work(){
  Build_sam();
  for (int i=1;i<=n;++i)
    extend(c[i]-'a');
  pre();
  dfs(1,1);
}

Abigail outo(){
  k>0?puts("-1"):printf("%s\n",sc+1);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值