题解 AtCoder-s8pc_5_h【Percepts of Atcoder】/ SS221112D【W】 / ZROI1410【超简单题】

problem

将字符串 \(S\) 的所有非空的可以不连续的子序列拎出来去重并排序,\(q\leq 10^5\) 次询问第 \(k\) 小字典序的子序列的后 \(p\) 个字符。\(|S|,\sum p\leq 10^5,k\leq 10^{18}\)

solution 0

建出子序列自动机,然后乱搞!\(O(nq)\)

我们复习一下子序列自动机:

  • 首先这是个自动机。
  • 子序列自动机是一个 DAG,每一条路径唯一的对应了原串的一个子序列。
  • 我们可以令 \(f_i\) 表示从点 \(i\) 出发能到达的路径的个数,是 DAG 上 DP。
  • 时间 \(O(n)\),空间 \(O(n|\Sigma|)\)
void build(){
	fill(last,last+26,n+1);
	memcpy(to[n+1],last,sizeof last);
	f[n+1]=0;
	for(int i=n;i>=0;i--){
		memcpy(to[i],last,sizeof last);
		f[i]=1;
		for(int j=0;j<26;j++) red(f[i]+=f[to[i][j]]);
		if(1<=i&&i<=n) last[a[i]-'a']=i;
	}
} 

有个细节:\(f\) 可能很大,我们对 \(f\)\(10^{18}\) 取个 \(\min\) 就好了。

solution 1

DAG 链剖分你可曾听过?

像重链剖分一样,我们选出一个 \(son_u\) 表示 \(\max_{u\to v}f_v\)\(v\),并说 \(u\to son_u\) 是重边,其它是轻边。

和树剖一样,每条一次轻边,要么是 \(\infty\to 10^{18}\),要么是 \(k\to k/2\),方案数至少除以 \(2\),总的跳轻边的复杂度 \(O(\log k|\Sigma|)\)

但是我们要跳重链,和树剖不同的是,我们走的重边只是重链的一段,所以我们倍增一下找到最远能跳到那里,轻松的。跳一次重链 \(O(\log |S|)\),一共 \(O(\log k)\) 条。

这样我们将一个子序列表示成 \(O(\log |S|)\) 个重链和轻边的交错,输出后 \(p\) 个字符就倒着扫就行。

总的复杂度是 \(O\left(|S|\log k\left(\log |S|+|\Sigma|\right)\right)\)

code

细节:怎么知道能跳到哪里?倍增的时候,算一下跳完之后能减掉多少方案数,同时要求跳过去之后还在重链上。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr LL inf = 2e18;
int n, lst[26], to[300010][26], son[300010], q, p;
LL f[300010];
char a[300010], stk[1000010];
pair<LL, int> st[20][300010];
void putc(int ch) {
  if (p) stk[p--] = ch + 'a';
}
void print(int j, int u) {
  if (j) {
    if (p) print(j - 1, st[j - 1][u].second);
    if (p) print(j - 1, u);
  } else {
    putc(son[u]);
  }
}
void dfs(int i, LL k) {
  if (k-- == 0) return ;
  vector<pair<int, int>> sta;
  for (int j = 19; j >= 0; j--) if (st[j][i].second <= n && k >= st[j][i].first && k - st[j][i].first + 1 < f[st[j][i].second]) {
    sta.emplace_back(j, i);
    k -= st[j][i].first;
    i = st[j][i].second;
  }
  for (int j = 0; j < 26; j++) {
    if (k >= f[to[i][j]]) k -= f[to[i][j]];
    else { dfs(to[i][j], k), putc(j); break; }
  }
  reverse(sta.begin(), sta.end());
  for (auto e : sta) print(e.first, e.second);
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> a;
  n = strlen(a);
  fill(lst, lst + 26, n + 1);
  memcpy(to[n + 1], lst, sizeof lst);
  st[0][n + 1] = make_pair(0, n + 1);
  for (int i = n; i >= 0; i--) {
    memcpy(to[i], lst, sizeof lst);
    f[i] = 1;
    for (int j = 0; j < 26; j++) {
      f[i] = min(f[i] + f[to[i][j]], inf);
      if (f[to[i][j]] > f[to[i][son[i]]]) son[i] = j;
    }
    LL pre = 1;
    for (int j = 0; j < son[i]; j++) pre = min(pre + f[to[i][j]], inf);
    st[0][i] = make_pair(pre, to[i][son[i]]);
    if (i) lst[a[i - 1] - 'a'] = i;
  }
  for (int j = 1; j <= 19; j++) {
    for (int i = 0; i <= n + 1; i++) {
      int nxt = st[j - 1][i].second;
      st[j][i] = make_pair(min(st[j - 1][i].first + st[j - 1][nxt].first, inf), st[j - 1][nxt].second);
    }
  }
  cin >> q;
  while (q--) {
    LL k;
    cin >> k >> p;
    stk[p + 1] = 0;
    if (k >= f[0]) cout << -1 << endl;
    else {
      dfs(0, k);
      cout << (stk + p + 1) << endl;
    }
  }
  return 0;
}

点击查看代码
typedef long long LL;
LL mod(LL x){return min(x,(LL)2e18);}
void red(LL&x){x=mod(x);}
struct node{
	LL sum; int last;
	node(LL sum=0,int last=0):sum(sum),last(last){}
	node operator+(node b){return node(mod(sum+b.sum),b.last);}
};
int n,to[300010][26],last[26],son[300010];
LL f[300010];
char a[300010];
node st[20][300010];
void build(){
	fill(last,last+26,n+1);
	memcpy(to[n+1],last,sizeof last);
	son[n+1]=0,st[0][n+1]=node(0,n+1),f[n+1]=0;
	for(int i=n;i>=0;i--){
		memcpy(to[i],last,sizeof last);
		for(int j=0;j<26;j++) if(f[to[i][j]]>f[to[i][son[i]]]) son[i]=j;
		f[i]=1;
		for(int j=0;j<26;j++) red(f[i]+=f[to[i][j]]);
		LL sum=1;
		for(int j=0;j<son[i];j++) red(sum+=f[to[i][j]]);
		st[0][i]=node(sum,to[i][son[i]]);
		if(1<=i&&i<=n) last[a[i]-'a']=i;
	} 
	for(int j=1;j<=19;j++){
		for(int i=0;i<=n+1;i++) st[j][i]=st[j-1][i]+st[j-1][st[j-1][i].last];
	}
} 
int top;
char stk[1000010];
void print(int j,int i){
	if(j==0) return top&&(stk[--top]=son[i]+'a'),void();
	if(top) print(j-1,st[j-1][i].last);
	if(top) print(j-1,i);
}
void solve(int u,LL k){
	if(k==1) return ;
	vector<pair<int,int>> used={};
	for(int j=19;j>=0;j--){
		if(k>st[j][u].sum&&k-st[j][u].sum<=f[st[j][u].last]){
			used.push_back({j,u});
			debug("k=%lld, jump st[%d][%d]={%lld,%d}\n",k,j,u,st[j][u].sum,st[j][u].last);
			k-=st[j][u].sum;
			u=st[j][u].last;
		}
	}
	if(k>1){
		k--;
		for(int j=0;j<26;j++){
			if(k>f[to[u][j]]) k-=f[to[u][j]];
			else{solve(to[u][j],k),top&&(stk[--top]=j+'a');break;};
		}	
	}
	for(int j=(int)used.size()-1;j>=0;j--) print(used[j].first,used[j].second);
}
int main(){
	scanf("%s%*d",a+1),n=strlen(a+1);
	build();
	for(LL k,p;~scanf("%lld%lld",&k,&p);){
		if(++k>f[0]) puts("-1");
		else solve(stk[top=p]=0,k),puts(stk+top);
	}
	return 0;
}
posted @ 2022-11-13 10:53  caijianhong  阅读(21)  评论(0编辑  收藏  举报