您的位置:首页 > 其它

[转]数位dp小记

2015-06-09 16:14 260 查看
转载自:http://blog.csdn.net/guognib/article/details/25472879

参考:

http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html

kuangbin :http://www.cnblogs.com/kuangbin/category/476047.html

http://blog.csdn.net/cmonkey_cfj/article/details/7798809

http://blog.csdn.net/liuqiyao_01/article/details/9109419

数位dp有递推和记忆化搜索的方法,比较来说记忆化搜索方法更好。通过博客一的一个好的模板,数位dp就几乎变成一个线性dp问题了。

下为博客一内容:

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

偷看了下7k+大牛的数位统计dp写法,通常的数位dp可以写成如下形式:

int dfs(int i, int s, bool e) {
if (i==-1) return s==target_s;
if (!e && ~f[i][s]) return f[i][s];
int res = 0;
int u = e?num[i]:9;
for (int d = first?1:0; d <= u; ++d)
res += dfs(i-1, new_s(s, d), e&&d==u);
return e?res:f[i][s]=res;
}


其中:

f为记忆化数组;

i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。It depends.

于是关键就在怎么设计状态。当然做多了之后状态一眼就可以瞄出来。

注意:

不满足区间减法性质的话(如hdu 4376),不能用solve(r)-solve(l-1),状态设计会更加诡异。

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

正如上面说的要注意:

前导零是否有影响。

是否满足区间减的性质。(如hdu4376,还没做,先记上)

几乎就是模板题:

模板:

UESTC 1307相邻的数差大于等于2

(不允许有前导0,前导0对计算有影响,注意前导0的处理)

LL dp[25][1 << 11][11];
int bit[25];
int k;
int getnews(int s, int x)
{
for(int i=x;i<10;i++)
if(s&(1<<i))return (s^(1<<i))|(1<<x);
return s|(1<<x);
}
int getnum(int s)
{
int ret = 0;
while (s)
{
if (s & 1) ret++;
s >>= 1;
}
return ret;
}
LL dfs(int pos, int s, int e, int z)
{
if (pos == -1) return getnum(s) == k;
if (!e && ~dp[pos][s][k]) return dp[pos][s][k];
int end = e ? bit[pos] : 9;
LL ans = 0;

for (int i = 0; i <= end; i++)
{
int news = getnews(s, i);
if (z && !i) news = 0;
ans += dfs(pos - 1, news, e && (i == end), z && (!i));
}
if (!e) dp[pos][s][k] = ans;
return ans;
}

LL calc(LL n)
{
int len = 0;
while (n)
{
bit[len++] = n % 10;
n /= 10;
}
return dfs(len - 1, 0, 1, 1);
}

int main ()
{
LL n, m;
memset(dp, -1, sizeof(dp));
int t;
scanf("%d", &t);
int nc = 1;
while (t--)
{
cin >> n >> m >> k;
printf("Case #%d: ", nc++);
cout << calc(m) - calc(n - 1) << endl;
}
return 0;
}


View Code

!!!是比较不错,待看的题

比较还好的处理的题目:

Codeforces 55D Beautiful numbers!!!

spoj 10606 Balanced Numbers

ac自动机和数位dp结合(!!!):

hdu4376!!!(区间不可减???)
ZOJ3494 BCD Code(AC自动机+数位DP)!!!

整除和简单统计:

HDU4507 和7无关数的平方和!!!

HDU 3652 出现13,而且能被13整除

LightOJ 1068 能被K整数且各位数字之和也能被K整除的数

light OJ 1140两个数之间的所有数中零的个数。

lightoj 1032 二进制数中连续两个‘1’出现次数的和

其它:
LightOJ1205求区间[a,b]的回文数个数。
ural 1057 数位统计
codeforces215E周期数
codeforces258B在1-m中任选7个数,要使前六个数字中的“4”,"7"之和小于第七个的,
Zoj2599 数位统计(见题意)
zoj3162分形、自相似
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: