数位DP专题
CF55D
这个题的核心要素是:某个数n被它的所有非零数位整除的同时,也是整除了所有非零数位的最小公倍数。所以在分析数位的同时需要照顾已经分析的所有非零数位的最小公倍数,和这些数位组成的这个数取余它的模。
所以dfs过程就有了三维数据:当前数位,余数,最小公倍数。然而余数和最小公倍数可能很大,在记忆化过程中较难实现,所以需要优化一下。可以发现1~9的最小公倍数为2520,如果一个数被2520整除,那么就可以断定无论如何都可以被它的每一位整除。但是数位的所有数字并不全是1~9,还可能有别的数字,所以只要取余数位最小公倍数为0的都可以认为满足条件。
至此,空间可以优化到20*2520*2520,但是这样仍然会空间超限。可以发现最后一维,也就是最小公倍数只有48种组成情况(因式分解),所以可以把它压成48个大小,再利用一个哈希数组使1~48各自都对应一个因数组成方案。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll dp[50][2530][50];
ll digits[500],pts=0;
map<ll, ll>mp;
namespace DigitsDp
{
void preparation()
{
pts = 0;
reset(digits, 0);
}
void div(ll x)
{
while (x)
{
digits[++pts] = x % 10;
x /= 10;
}
}
void getFactors()
{
ll cnt = 0;
for (int i = 1; i <=2520; i++)
{
if (2520 % i == 0)
mp[i] = ++cnt;
}
}
ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a%b);
}
ll Lcm(ll a, ll b)
{
return a * b / gcd(a, b);
}
ll dfs(ll current, ll mod, ll lcm, bool boundary)
{
if (current == 0)
{
//cout << current << " " << mod << " " << lcm << " " << boundary << endl;
return mod % lcm == 0 ? 1 : 0;
}
if (boundary==false&&dp[current][mod][mp[lcm]]!=-1)
return dp[current][mod][mp[lcm]];
else
{
//cout << current << " " << mod << " " << lcm << " "<<boundary << endl;
ll &tmp = dp[current][mod][mp[lcm]];
ll limit = boundary == true ? digits[current]:9;
ll ans = 0;
for (int i = 0; i <= limit; i++)
{
ll nextNum = (mod*10+i) % 2520;
ll nextlcm = lcm;
if (i != 0)
nextlcm = Lcm(lcm, i);
ans += dfs(current - 1, nextNum, nextlcm, boundary&&i == limit);
}
if (boundary == false)
tmp = ans;
return ans;
}
}
};
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
//cout << 2520 % 1 << endl;
cin >> t;
reset(dp, -1);
mp.clear();
DigitsDp::getFactors();
for (int j = 1; j <= t; j++)
{
ll lower, upper;
cin >> lower >> upper;
DigitsDp::preparation();
DigitsDp::div(upper);
ll ans1=DigitsDp::dfs(pts, 0, 1, true);
DigitsDp::preparation();
DigitsDp::div(lower-1);
ll ans2 = DigitsDp::dfs(pts, 0, 1, true);
cout << ans1 - ans2 << endl;
}
return 0;
}
HDU4352
本题意思是求出单个数内的数位即构成K个长度的LIS的数的个数。如果要求出这么个LIS,绕不开那两种求LIS的办法:动态规划或者贪心+二分,在这里使用的是基于后者思想的变种方法。
因为这是一个根据数位来讨论LIS的题目,单纯利用数组来标记LIS是行不通的,所以就有一种方法来快速标记现在已经形成的LIS,也就是使用01字符串来标记这个LIS,比如说167,就可以表示为0100001100,即长度为3的LIS:1,6,7.
更新LIS的方法自然是从当前位x开始,找到第一个比它大的位,然后替换它。比如说1675的LIS由167变为157,相应的01串也要有这样的变更,除去01串内的一个1可以直接使用异或,相应的添加一个1可直接使用按位或。
值得注意的是本题的时间复杂度不允许T次初始化,所以需要在DP数组内再加一维K,来实现对多种K进行答案储存的功能。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll dp[20][1 << 11][12],digits[599];
ll cnt = 0;
namespace solution
{
void division(ll x)
{
cnt = 0;
while (x > 0)
{
digits[++cnt] = x % 10;
x /= 10;
}
}
ll CheckAns(ll x)
{
bitset<23>bt(x);
return bt.count();
}
ll UpdateLIS(ll Original, ll CurrentPos)
{
for (int i = CurrentPos; i <= 9; i++)
{
if ((Original&(1 << i)) > 0)
return (Original ^ (1 << i)) | 1 << CurrentPos;
}
return Original | 1 << CurrentPos;
}
ll dfs(ll current, ll LIS, bool LeadingZeroes, bool Bound,ll request)
{
if (current == 0)
{
if (CheckAns(LIS) == request)
return 1;
else
return 0;
}
if (Bound == false && dp[current][LIS][request] != -1)
return dp[current][LIS][request];
else
{
ll ans = 0;
ll limit = Bound == true ? digits[current] : 9;
for (int i = 0; i <= limit; i++)
{
ll nextLIS = (LeadingZeroes&&i == 0) ? 0 : UpdateLIS(LIS, i);
ans += dfs(current - 1, nextLIS,LeadingZeroes&&i==0,Bound&& i == limit, request);
}
if (!Bound)
dp[current][LIS][request] = ans;
return ans;
}
}
}
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
cin >> t;
reset(dp, -1);
for (int y = 1; y <= t; y++)
{
ll lower, upper, length;
cin >> lower >> upper >> length;
cout << "Case #" << y << ": ";
solution::division(lower-1);
ll ans1 = solution::dfs(cnt, 0, true, true, length);
solution::division(upper);
ll ans2 = solution::dfs(cnt, 0, true,true, length);
cout << ans2 - ans1 << endl;
}
return 0;
}
POJ3252
数位DP模板题,但是新颖的地方在于这是对一个十进制数字的二进制表达式进行分析,相应的,只要把数字拆分成二进制形式就可以解决这个问题。
HDU3709
对数字的平衡点进行枚举,然后依据当前平衡点利用数位DP进行分析即可。值得一说的是在数位DP中会出现00000这种数字,这是因为dfs的过程中多次选择了0,造出了这么一个情况,实际上只有0需要被统计,00,000等都是完全多余的。这样的可以使用判断前导零的方式消除它的影响,或者直接在答案出减去(n-1)即可。这是因为对于n位数,0,00,000...00(n个0)有n个,被重复计算的有n-1个。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll dp[50][50][2550];
ll digits[50],cnt;
namespace solution
{
void division(ll x)
{
cnt = 0;
while (x > 0)
{
digits[++cnt] = x % 10;
x /= 10;
}
}
ll dfs(ll current, ll pivot,ll SideSum,bool bound)
{
if (current == 0)
{
if (SideSum == 0)
return 1;
else
return 0;
}
if (!bound&&dp[current][pivot][SideSum] != -1)
return dp[current][pivot][SideSum];
else
{
ll ans = 0;
ll limit = bound == true ? digits[current] : 9;
for (int i = 0; i <= limit; i++)
{
ll nextSum = ((SideSum + (pivot - current)*i));
ans += dfs(current-1, pivot, nextSum, bound&&i == limit);
}
if (!bound)
dp[current][pivot][SideSum] = ans;
return ans;
}
}
ll ReturnAns()
{
ll ans = 0;
for (int i = 1; i <= cnt; i++)
ans += dfs(cnt, i, 0, true);
return ans-cnt+1;
}
}
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
cin >> t;
for (int y = 1; y <= t; y++)
{
ll lower, upper;
cin >> lower >> upper;
ll ans1, ans2;
reset(dp, -1);
solution::division(upper);
ans1 = solution::ReturnAns();
reset(dp, -1);
if (lower == 0)
ans2 = 0;
else
{
solution::division(lower - 1);
ans2 = solution::ReturnAns();
}
//cout << ans1 << " " << ans2 << endl;
cout << ans1 - ans2 << endl;
}
return 0;
}
HDU4507
本题的主要难点是求平方和。
一般来说,知道一个数字求平方和是很简单的,但是知道一个当前数位就不是那么简单了。这需要如下两个公式:
(从后往前计算)
合并起来就是:
可以看出,要求解一个状态的平方和,就需要有两个变量记录一次方和,平方和。但是这两个变量还不够,因为对于每一个状态而言,例如第i个数位(从前往后),它的数据代表着i+1,i+2...Length的状态和。什么意思呢?假如我们用数位DP去求1~20的和,那么对于第2位来说,它的和就是这一位本身(0~9),但是对于第一位来说,它在求和的时候需要考虑在这个数位的下位有多少个数字,即对于1~20而言:0的下位有9个数字(0~9),1的下位有10个数字(10~19),2的下位有1个数字(0),在求和的时候就需要再乘下位数字的个数,即(0*10*9+45)+(1*10*10+45)+(2*10+0)=210,45在这里的是个位数和。
所以还需要第三个变量来记录下位数字个数。
对于数位DP中的每位数来说,就会有如下的操作:
ans.cnt += tmp.cnt%mod;
ans.cnt %= mod;
ans.sum += (power[current - 1]%mod * i%mod*tmp.cnt%mod + tmp.sum%mod)%mod;
//cout << "ans.sum: " << power[current - 1] << " " << i << " " << tmp.cnt << endl;
ans.sum %= mod;
ans.sqrtsum += (tmp.sqrtsum%mod + 2 * i%mod*power[current - 1] % mod* tmp.sum%mod)%mod;
ans.sqrtsum %= mod;
ans.sqrtsum += (tmp.cnt%mod*i%mod*i%mod*power[current - 1]%mod * power[current - 1] % mod)%mod;
ans.sqrtsum %= mod;
- 先更新当前位的下位数字数。
- 然后更新当前位所得到的总和。
- 最后更新平方和(注意,这里是回溯,所以对于公式
,在这里是已知了(b+c)的平方和以及一次方和,来求原式的值)。
(由于每个数都可能很大,建议多次取模或者用防溢出乘法重载运算符)
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
struct node
{
ll cnt, sum, sqrtsum;
}dp[30][30][30];
ll digits[59], cnt2 = 0;
ll power[123];
namespace solution
{
void division(ll x)
{
cnt2 = 0;
while (x > 0)
{
digits[++cnt2] = x % 10;
x /= 10;
}
}
node dfs(ll current, ll digitMod, ll numberMod, bool bound)
{
if (current == 0)
{
node tmpans = { 0,0,0 };
if (digitMod == 0 || numberMod == 0)
tmpans.cnt = 0;
else
tmpans.cnt = 1;
tmpans.sum = tmpans.sqrtsum = 0;
return tmpans;
}
if (!bound&&dp[current][digitMod][numberMod].cnt != -1)
return dp[current][digitMod][numberMod];
else
{
node ans = { 0,0,0 };
ll limit = bound == true ? digits[current] : 9;
//cout << "power :" << power[current-1] <<" "<<current-1<< endl;
for (int i = 0; i <= limit; i++)
{
if (i == 7)
continue;
else
{
node tmp = dfs(current - 1, (digitMod + i) % 7, (numberMod * 10 + i) % 7, bound&&i == limit);
ans.cnt += tmp.cnt%mod;
ans.cnt %= mod;
ans.sum += (power[current - 1]%mod * i%mod*tmp.cnt%mod + tmp.sum%mod)%mod;
//cout << "ans.sum: " << power[current - 1] << " " << i << " " << tmp.cnt << endl;
ans.sum %= mod;
ans.sqrtsum += (tmp.sqrtsum%mod + 2 * i%mod*power[current - 1] % mod* tmp.sum%mod)%mod;
ans.sqrtsum %= mod;
ans.sqrtsum += (tmp.cnt%mod*i%mod*i%mod*power[current - 1]%mod * power[current - 1] % mod)%mod;
ans.sqrtsum %= mod;
//cout << ans.cnt << " " << ans.sum << " " << ans.sqrtsum << " " << tmp.cnt << " " << tmp.sum << " " << tmp.sqrtsum << endl;
}
}
if (!bound)
dp[current][digitMod][numberMod] = ans;
return ans;
}
}
}
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
reset(dp, 0);
power[0] = 1;
for (int i = 1; i <= 23; i++)
power[i] = (power[i - 1] * 10)%mod;
for (int i = 0; i <= 25; i++)
for (int k = 0; k <= 13; k++)
for (int z = 0; z <= 13; z++)
dp[i][k][z].cnt = -1;
//cout << power[0] << endl;
ll t;
cin >> t;
for (int y = 1; y <= t; y++)
{
ll lower, upper;
cin >> lower >> upper;
solution::division(upper);
ll ans1 = solution::dfs(cnt2, 0, 0, true).sqrtsum;
solution::division(lower - 1);
ll ans2 = solution::dfs(cnt2, 0, 0, true).sqrtsum;
//cout << ans1 << " " << ans2 << endl;
cout << ((ans1 - ans2) % mod + mod) % mod << endl;
}
return 0;
}