题意
求和 sum = |LCS(x,y)| , x <= n, y <= n , n <= 1e5
题解
先说一个错误的做法
考场上思考太不成熟了
枚举S作为lcs的子串,计数包含lcs的串的个数,然后再减掉以包含S的LCS方案数。
这样是错误的,因为两个串的lcs可能有多个长度相同的串,比如“12“,“21”的LCS可以是“1“或“2”
正解是枚举一个数a,建立后缀自动机,然后数位dp。
dp的状态记录在后缀自动机上求LCS的过程,当前匹配节点,匹配长度,历史匹配最大值。还要记有无前导0,是否顶上界。
注意到这里只需要统计<=a的数。所有的答案可以预处理。所以询问组数再多都可以很方便的做。
数位DP的时候先预处理顶上界这个状态的转移,在DP的时候少分类讨论,思路更清晰,不容易写错
int DP(int n,int t,int fl,int cur,int len,int mx){
if ( !n ) return mx;
if ( vis[n][t][fl][cur][len][mx] ) return f[n][t][fl][cur][len][mx];
vis[n][t][fl][cur][len][mx] = 1; int &res = f[n][t][fl][cur][len][mx];
rep(i,0,9){
int t2 = trans[n][t][i];
if ( t2 == -1 ) break;
if ( fl || i ){
int p = cur,cmx = mx,l = len;
while ( p && !nxt[p][i] ) p = pnt[p];
l = min(l,val[p]);
if ( nxt[p][i] ) p = nxt[p][i] , l++;
cmx = max(cmx,l);
res += DP(n - 1,t2,1,p,l,cmx);
}
else res += DP(n - 1,t2,0,0,0,0);
}
return res;
}
void solve(int x){
int cnt = 0,n = x;
while ( n ) a[++cnt] = n % 10 , n /= 10;
rep(i,1,cnt){
rep(t,0,1){
rep(j,0,9){
int x;
if ( t ){
if ( a[i] > j ) x = 0;
else if ( a[i] == j ) x = 1;
else x = -1;
}
else x = 0;
trans[i][t][j] = x;
}
}
}
clear();
repd(i,cnt,1) Add(a[i]);
memset(vis,0,sizeof(vis));
memset(f,0,sizeof(f));
res[x] = DP(cnt,1,0,0,0,0);
// cout<<x<<" "<<res<<endl;
res[x] = res[x] * 2 - cnt;
}
这题还要一种做法。两个串的长度和最多是10(特判100000),枚举10个位置的等价关系划分,这个方案数是Bell(10),只有1.2e5. 显然LCS只和位置等价关系有关,并且这样是不会算重的
然后数位dp求下方案数。
感觉没有后缀自动机好写
下面是完整的代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].nxt)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<ll,ll> pr;
const ll inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 200020;
const ll mod = 1e9 + 7;
const int tmp = 138794;
int s[10],cnt;
int f[6][2][2][20][6][6],vis[6][2][2][20][6][6],trans[6][2][10],a[20];
int n,T,len,res[maxn];
ll ans;
namespace SAM{
int nxt[maxn][10],tot,pnt[maxn],val[maxn],last,sz[maxn];
int a[maxn],b[maxn],jump[20][maxn];
void clear(){
rep(i,0,tot) memset(nxt[i],0,sizeof(nxt[i])) , sz[i] = val[i] = pnt[i] = 0;
last = tot = 0;
}
void Add(int x){ //插入一个字符
int np = ++tot ,p = last;
val[np] = val[p] + 1 , sz[np] = 1; //只有np节点有sz
while ( !nxt[p][x] && p ) nxt[p][x] = np , p = pnt[p];
int q = nxt[p][x];
if ( !q ) pnt[np] = p , nxt[p][x] = np;
else if ( val[p] + 1 == val[q] ) pnt[np] = q;
else{
int nq = ++tot;
val[nq] = val[p] + 1;
pnt[nq] = pnt[q];
pnt[np] = pnt[q] = nq;
memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
while ( nxt[p][x] == q && p ) nxt[p][x] = nq , p = pnt[p];
if ( nxt[p][x] == q ) nxt[p][x] = nq;
}
last = np;
}
void getsize(){ //桶排序(相同于拓扑序),求right集合大小
for (int i = 1 ; i <= tot ; i++) a[val[i]]++;
for (int i = 1 ; i <= n ; i++) a[i] += a[i - 1];
for (int i = tot ; i >= 1 ; i--) b[a[val[i]]--] = i;
for (int i = tot ; i >= 1 ; i--) sz[pnt[b[i]]] += sz[b[i]];
}
void pre(){ //预处理倍增
for (int i = 1 ; i <= tot ; i++) jump[0][i] = pnt[i];
for (int i = 1 ; i <= 17 ; i++)
for (int j = 1 ; j <= tot ; j++)
jump[i][j] = jump[i - 1][jump[i - 1][j]];
}
void print(){ //打印
for (int i = 1 ; i <= tot ; i++) cout<<i<<" "<<pnt[i]<<" "<<sz[i]<<endl;
}
int DP(int n,int t,int fl,int cur,int len,int mx){
if ( !n ) return mx;
if ( vis[n][t][fl][cur][len][mx] ) return f[n][t][fl][cur][len][mx];
vis[n][t][fl][cur][len][mx] = 1; int &res = f[n][t][fl][cur][len][mx];
rep(i,0,9){
int t2 = trans[n][t][i];
if ( t2 == -1 ) break;
if ( fl || i ){
int p = cur,cmx = mx,l = len;
while ( p && !nxt[p][i] ) p = pnt[p];
l = min(l,val[p]);
if ( nxt[p][i] ) p = nxt[p][i] , l++;
cmx = max(cmx,l);
res += DP(n - 1,t2,1,p,l,cmx);
}
else res += DP(n - 1,t2,0,0,0,0);
}
return res;
}
void solve(int x){
int cnt = 0,n = x;
while ( n ) a[++cnt] = n % 10 , n /= 10;
rep(i,1,cnt){
rep(t,0,1){
rep(j,0,9){
int x;
if ( t ){
if ( a[i] > j ) x = 0;
else if ( a[i] == j ) x = 1;
else x = -1;
}
else x = 0;
trans[i][t][j] = x;
}
}
}
clear();
repd(i,cnt,1) Add(a[i]);
memset(vis,0,sizeof(vis));
memset(f,0,sizeof(f));
res[x] = DP(cnt,1,0,0,0,0);
// cout<<x<<" "<<res<<endl;
res[x] = res[x] * 2 - cnt;
}
}
void init(){
rep(i,1,99999) SAM::solve(i);
}
int main(){
scanf("%d",&T);
init();
while ( T-- ){
ans = 0;
scanf("%d",&n);
if ( n == 100000 ) n-- , ans += tmp;
rep(i,1,n){
ans += res[i];
}
printf("%lld\n",ans);
}
}