zoukankan      html  css  js  c++  java
  • 动规-数位DP

    某场练习赛中由于没写过数位 DP 板子(OrzOrz),只能分段打表乱搞,心态非常崩。当时想的是二分数位的每一位,这样会非常绕,可不可行不知道,但现在我还没有想出用那种二分的解法。其实是要对数字范围二分,然后 DP 验证合理的数个数就可以了。

    然后补练了一下几道题,感觉数位 DP 不难,主要是状态设计(是否卡边界、最高位是否从1开始、讨论到哪位)的套路吧,不知道套路现场推还是有点危险哒。另外据说正统写法并没有 [最高位是否从1开始] 这一维,而是去讨论前导 0 。我觉得那样有点难想,所以就加了一维。

    放几道例题代码:


    Update 14/10/2018 注意,卡边界的时候不要记忆化搜索,不卡边界的时候才记忆化搜索。如果总是记忆化搜索的话,在有多组数据的情况下,每次清空数组会耗费大量时间,导致超时!不卡边界的状态显然是与数据本身无关的,所以一直记忆化就可以保证在大数据情况下的时间复杂度了。写以下部分代码的时候我没有意识到这一点(QwQ),时间复杂度是错的,不过侥幸过了。千万别再这么写了!

    【ZJOI2010 Day1】数字计数

    题目大意:求 ([L, R]) 中, [0-9] 数码出现的次数。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    //i wei
    //h 是边界
    //g 从1开始
    //x y已经出现的个数 
    //y 求y的个数
    
    ll f[13][2][2][13][10], ans[10];
    int num[13];
    
    ll qwq(int n, bool h, bool g, int x, int y)
    {
    	ll &t = f[n][h][g][x][y];
    	if (~t) return t;
    	if (!n) return x;
    	t = 0;
    	int lll = g, rrr = h ? num[n] : 9;
    	for (int i = lll; i <= rrr; ++i) {
    		t += qwq(n - 1, h && i == rrr, false, x + (i == y), y);
    	}
    	return t;
    }
    
    void getans(ll n, bool t)
    {
    	int top = 0;
    	do num[++top] = n % 10;
    	while (n /= 10);
    	memset(f, -1, sizeof f);
    	for (int i = 1; i <= top; ++i)
    		for (int j = 0; j <= 9; ++j)
    			if (t)
    				ans[j] += qwq(i, i == top, true, 0, j);
    			else
    				ans[j] -= qwq(i, i == top, true, 0, j);
    	return;
    }
    
    int main()
    {
    	ll A, B;
    	scanf("%lld%lld", &A, &B);
    	getans(B, true);
    	if (A > 1)
    		getans(A - 1, false);
    	for (int i = 0; i <= 9; ++i)
    		printf("%lld ", ans[i]);
    	return 0;
    }
    

    【SCOI2009 Day1】windy数

    题目大意:求 ([L, R]) 区间相邻两个数字差的绝对值均 (ge 2) 的数的个数。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    ll f[11][11][2][2];
    int num[11];
    
    //f[i][a][g][h]
    //i
    //a 上一位
    //g 是边界
    //h 从1开始
    
    ll qaq(int n, int a, bool g, bool h)
    {
    	ll &t = f[n][a][g][h];
    	if (~t) return t;
    	t = 0;
    	int lll = h, rrr = g ? num[n] : 9;
    	if (!n)
    		return t = 1;
    	for (int i = lll; i <= rrr; ++i) {
    		if (a != 10 && abs(i - a) < 2) continue;
    		t += qaq(n - 1, i, g && i == rrr, false);
    	}
    	return t;
    }
    
    ll getans(ll n)
    {
    	int top = 0;
    	memset(f, -1, sizeof f);
    	do num[++top] = n % 10;
    	while (n /= 10);
    	ll t = 0;
    	for (int i = 1; i <= top; ++i)
    		t += qaq(i, 10, i == top, true);
    	return t;
    }
    
    int main()
    {
    	ll L, R;
    	scanf("%lld%lld", &L, &R);
    	ll t = getans(R);
    	if (L > 1) t -= getans(L - 1);
    	printf("%lld
    ", t);
    	return 0;
    }
    

    土拨鼠猎人

    题目大意:求第 (k) 小的特殊数。特殊数是指不满足 (left( [有连续两位相等] igwedge [有数字等于右边数字+1] ight) igvee [有连续三位是233]) 的数。二分, DP 计算 (le) 二分答案的合理的数个数即可。

    //f[i][a][b][x][y][z]
    //x 相等
    //y 等于前一位+1
    //z 是边界 
    //g 从1开始 
    
    #include <cstdio>
    #include <ctype.h>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    ll f[20][12][12][2][2][2][2];
    int num[20];
    
    ll qaq(int n, int a, int b, bool x, bool y, bool z, bool g)
    {
    	ll &t = f[n][a][b][x][y][z][g];
    	if (~t) return t;
    	if (x && y) return t = 0;
    	if (!n) return t = 1;
    	t = 0;
    	int lll = g, rrr = z ? num[n] : 9;
    	for (int i = lll; i <= rrr; ++i) {
    		if (i == 3 && a == 3 && b == 2) continue;
    		t += qaq(n - 1, i, a, x || i == a, y || i + 1 == a, z && i == rrr, false);
    	}
    	return t;
    }
    
    ll check(ll n)
    {
    	if (n <= 99) return n;
    	memset(f, -1, sizeof f);
    	int top = 0;
    	do num[++top] = n % 10;
    	while (n /= 10);
    	ll t = 0;
    	for (ll i = top; i >= 1; --i)
    		t += qaq(i, 11, 11, false, false, i == top, true);
    	return t;
    }
    
    int main()
    {
    	ll N;
    	scanf("%lld", &N);
    	ll l = N, r = 1e18 + 5000, ans = 1;
    	while (l <= r) {
    		ll mid = l + r >> 1;
    		if (check(mid) >= N)
    			ans = mid, r = mid - 1;
    		else
    			l = mid + 1;
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }
    

    【ZJOI2010 Day1】数字计数

    题目大意:求 ([L, R]) 区间各数码出现的次数(比如 112 中 1 出现两次, 2 出现一次)。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    //i wei
    //h 是边界
    //g 从1开始
    //x y已经出现的个数 
    //y 求y的个数
    
    ll f[13][2][2][13][10], ans[10];
    int num[13];
    
    ll qwq(int n, bool h, bool g, int x, int y)
    {
    	ll &t = f[n][h][g][x][y];
    	if (~t) return t;
    	if (!n) return x;
    	t = 0;
    	int lll = g, rrr = h ? num[n] : 9;
    	for (int i = lll; i <= rrr; ++i) {
    		t += qwq(n - 1, h && i == rrr, false, x + (i == y), y);
    	}
    	return t;
    }
    
    void getans(ll n, bool t)
    {
    	int top = 0;
    	do num[++top] = n % 10;
    	while (n /= 10);
    	memset(f, -1, sizeof f);
    	for (int i = 1; i <= top; ++i)
    		for (int j = 0; j <= 9; ++j)
    			if (t)
    				ans[j] += qwq(i, i == top, true, 0, j);
    			else
    				ans[j] -= qwq(i, i == top, true, 0, j);
    	return;
    }
    
    int main()
    {
    	ll A, B;
    	scanf("%lld%lld", &A, &B);
    	getans(B, true);
    	if (A > 1)
    		getans(A - 1, false);
    	for (int i = 0; i <= 9; ++i)
    		printf("%lld ", ans[i]);
    	return 0;
    }
    

    完美数

    题目大意:求 ([L, R]) 区间能被自己各数位上非零数字整除的数的个数。

    #include <cstdio>
    #include <cstring>
    
    typedef long long ll;
    
    /*
    n 位数,a lcm,b 当前数字mod2520,g 卡边界,h 从1开始 
    */
    
    int num[30], hs[5500], sh[70], Cnt;
    ll f[19][50][2533][2];
    
    inline int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
    
    inline int lcm(int x, int y) { return x * y / gcd(x, y); }
    
    ll qaq(int n, int a, int b, bool g, bool h)
    {
    	ll was = -1;
    	ll &t = g ? was : f[n][hs[a]][b][h];
    	if (!g && ~t)
    		return t;
    	if (!n) {
    		return t = b % a == 0;
    	}
    	t = 0;
    	int lll = h, rrr = g ? num[n] : 9;
    	for (int i = lll; i <= rrr; ++i) {
    		int tmp = i ? lcm(a, i) : a;
    		t += qaq(n - 1, tmp, (b * 10 % 2520 + i) % 2520, g && i == rrr, false);
    	}
    	return t;
    }
    
    ll getans(ll v)
    {
    	if (!v) return 0;
    	int top = 0;
    	do num[++top] = v % 10;
    	while (v /= 10);
    	ll t = 0;
    	for (int i = top; i >= 1; --i)
    		t += qaq(i, sh[1], 0, i == top, true);
    	return t;
    }
    
    int main()
    {
    	int TTT;
    	scanf("%d", &TTT);
    	memset(f, -1, sizeof f);
    	for (int i = 1; i <= 2520; ++i)
    		if (2520 % i == 0) {
    			hs[i] = ++Cnt;
    			sh[Cnt] = i;
    		}
    	while (TTT--) {
    		ll A, B;
    		scanf("%lld%lld", &A, &B);
    		printf("%lld
    ", getans(B) - getans(A - 1));
    	}
    	return 0;
    }
    
  • 相关阅读:
    OpenCascade Ray Tracing Rendering
    Create New Commands in Tcl
    OpenCascade Modeling Algorithms Fillets and Chamfers
    OpenCascade Modeling Algorithms Boolean Operations
    Construction of Primitives in Open Cascade
    Open Cascade Data Exchange STL
    Tcl Tk Introduction
    Open Cascade DataExchange IGES
    Netgen mesh library : nglib
    Hello Netgen
  • 原文地址:https://www.cnblogs.com/ghcred/p/9701131.html
Copyright © 2011-2022 走看看