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;
    }
    
  • 相关阅读:
    图像处理------调整亮度与对比度 分类: 视频图像处理 2015-07-24 09:51 28人阅读 评论(0) 收藏
    图像处理--------应用卷积– 轧花与边缘检测 分类: 视频图像处理 2015-07-24 09:50 24人阅读 评论(0) 收藏
    图像处理------简单数字水印
    图像处理------应用卷积一实现噪声消去 分类: 视频图像处理 2015-07-24 09:36 27人阅读 评论(0) 收藏
    图像处理------光束效果 分类: 视频图像处理 2015-07-24 09:34 30人阅读 评论(0) 收藏
    图像处理------移动模糊 分类: 视频图像处理 2015-07-24 09:33 26人阅读 评论(0) 收藏
    图像处理------高斯模糊 分类: 视频图像处理 2015-07-24 09:32 29人阅读 评论(0) 收藏
    柏林噪声产生火焰等纹理 分类: 视频图像处理 2015-07-24 09:32 29人阅读 评论(0) 收藏
    图像处理------快速均值模糊(Box Blur) 分类: 视频图像处理 2015-07-24 09:28 30人阅读 评论(0) 收藏
    闭包和装饰器
  • 原文地址:https://www.cnblogs.com/ghcred/p/9701131.html
Copyright © 2011-2022 走看看