【题解】Winter 2019 - Problems - 1030 Mixture Magic 数位DP

20 篇文章 0 订阅
2 篇文章 0 订阅
题意

求和 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);
	}
}
	

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值