[您有新的未分配科技点]数位DP:从板子到基础

只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用

数位dp中常用的思想是“分类讨论”思想。下面我们就看一道典型的分类讨论例题

1026: [SCOI2009]windy数

Time Limit: 1 Sec  Memory Limit: 162 MB

Description

  windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,
在A和B之间,包括A和B,总共有多少个windy数?

  输入包含两个整数,A和B。

  输出一个整数,代表windy数个数

Sample Input

【输入样例一】

1 10

【输入样例二】

25 50

Sample Output

【输出样例一】

9

【输出样例二】

20

【数据规模和约定】

100%的数据,满足 1 <= A <= B <= 2000000000 。

题解:

首先转化为典型的work(B+1)-work(A)数数模型(区间左闭右开),下面我们的目标就是求[1,X)内windy数的个数了

设f[i][j]为i位数,第i位是j时windy数的个数。

从样例中我们注意到,个位数都是windy数,那么我们先处理出来,然后从十位数开始预处理,

显然,当abs(j-k)>=2时,f[i][j]+=f[i-1][k];

然后,我们设待处理的数位X=abcdefd(每一位的数字我们用字母表示),设其长度为l

那么对于小于等于x的数(设为t),我们分下面3种情况讨论:

for(int i=1;i<bit[l];i++)ans+=f[l][i];
//1° t的位数与x相同,但t的最高位小于x的最高位:直接加上f[l]["t的最高位"]
for(int i=1;i<b;i++)
  for(int j=1;j<10;j++)
    ans+=f[i][j];
//2°t的位数比x小,那么任意f[t][j]都是合法的
for(int i=b-1;i;i--)
{
	for(int j=0;j<bit[i];j++)
		if(abs(j-bit[i+1])>=2)ans+=f[i][j];
	if(abs(bit[i]-bit[i+1])<2)break;
}
//3°t的位数与x相同,且t的最高位等于x的最高位
//此时我们就和之前一样,for循环枚举判断即可,注意及时跳出

  

那么这道题就没有什么大问题了。完整代码见下:

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 typedef long long LL;
 5 LL l,r,f[15][15];int bit[15];
 6 inline int abs(int a){return a>0?a:-a;}
 7 inline void intn()
 8 {
 9     memset(f,0,sizeof(f));
10     for(int i=0;i<10;i++)f[1][i]=1;
11     for(int i=2;i<=10;i++)
12         for(int j=0;j<10;j++)
13             for(int k=0;k<10;k++)
14                 if(abs(j-k)>=2)
15                     f[i][j]+=f[i-1][k];
16 }
17 inline LL work(LL x)
18 {
19     if(x==0)return 0;
20     int b=0;LL ans=0;
21     memset(bit,0,sizeof(bit));
22     while(x)bit[++b]=x%10,x/=10;
23     for(int i=1;i<b;i++)
24         for(int j=1;j<10;j++)
25             ans+=f[i][j];
26     for(int i=1;i<bit[b];i++)
27         ans+=f[b][i];
28     for(int i=b-1;i;i--)
29     {
30         for(int j=0;j<bit[i];j++)
31             if(abs(j-bit[i+1])>=2)ans+=f[i][j];
32         if(abs(bit[i]-bit[i+1])<2)break;
33     }
34     return ans;
35 }
36 int main()
37 {
38     intn();
39     scanf("%lld%lld",&l,&r);
40     printf("%lld",work(r+1)-work(l));
41 }

接下来这道题可就没有那么简单了……这道题用到了另外一个套路:搜索+找规律

3131: [Sdoi2013]淘金

Time Limit: 30 Sec  Memory Limit: 256 MB
Submit: 660  Solved: 330
[Submit][Status][Discuss]

Description

小Z在玩一个叫做《淘金者》的游戏。游戏的世界是一个二维坐标。X轴、Y轴坐标范围均为1..N。初始的时候,所有的整数坐标点上均有一块金子,共N*N块。
 一阵风吹过,金子的位置发生了一些变化。细心的小Z发现,初始在(i,j)坐标处的金子会变到(f(i),fIj))坐标处。其中f(x)表示x各位数字的乘积,例如f(99)=81,f(12)=2,f(10)=0。如果金子变化后的坐标不在1..N的范围内,我们认为这块金子已经被移出游戏。同时可以发现,对于变化之后的游戏局面,某些坐标上的金子数量可能不止一块,而另外一些坐标上可能已经没有金子。这次变化之后,游戏将不会再对金子的位置和数量进行改变,玩家可以开始进行采集工作。
    小Z很懒,打算只进行K次采集。每次采集可以得到某一个坐标上的所有金子,采集之后,该坐标上的金子数变为0。
    现在小Z希望知道,对于变化之后的游戏局面,在采集次数为K的前提下,最多可以采集到多少块金子?
    答案可能很大,小Z希望得到对1000000007(10^9+7)取模之后的答案。

输入共一行,包含两介正整数N,K。输出为一个整数,表示最多可以采集到的金子数量。

Sample Input

1 2 5

Sample Output

18

[数据范围和约定]

N < = 10^12 ,K < = 100000
对于100%的测试数据:K < = N^2

题解:不难发现,按前几道题的想法完全无法解决本题。

(先回宿舍明天补完……)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 #include<algorithm>
 5 using namespace std;
 6 typedef long long LL;
 7 const int N=401000;
 8 const LL mod=1000000007;
 9 LL n;int bit[15],b,k,tot;
10 LL f[15][N][2],base[N],ans[N];
11 struct node
12 {
13     int x,y;LL val;
14     node(int a,int b){x=a,y=b,val=ans[a]*ans[b];}
15     bool operator > (const node &b)const{return val>b.val;}
16     bool operator < (const node &b)const{return val<b.val;}
17 };
18 priority_queue<node>q;
19 void dfs(int start,int len,LL multi)
20 {
21     if(len==b)base[tot++]=multi;
22     else
23     {
24         if(!multi)return;
25         for(int j=start;j<10;j++)
26             dfs(j,len+1,multi*j);
27     }
28 }
29 inline bool mt(const LL &a,const LL &b){return a>b; }
30 int main()
31 {
32     scanf("%lld%d",&n,&k);
33     b=0;memset(bit,0,sizeof(bit));
34     while(n)bit[++b]=n%10,n/=10;
35     base[++tot]=0;dfs(0,0,1);
36     sort(base+1,base+tot+1);
37     tot=unique(base+1,base+tot+1)-base-1;
38     base[tot+1]=0x7fffffff;
39     f[0][2][0]=1;
40     for(int i=0;i<=b;i++)
41         for(int j=1;j<=tot;j++)
42             for(int k=0;k<=1;k++)
43                 if(f[i][j][k])
44                     for(int x=(i==0)?0:1;x<10;x++)
45                     {
46                         int next=lower_bound(base+1,base+tot+1,base[j]*x)-base;
47                         f[i+1][next][(k+x)>bit[i+1]]+=f[i][j][k];
48                     }
49     for(int i=1;i<=tot;i++)
50     {
51         for(int j=1;j<b;j++)
52             ans[i]+=f[j][i][0]+f[j][i][1];
53         ans[i]+=f[b][i][0];
54     }
55     sort(ans+1,ans+tot+1,mt);
56     q.push(node(2,2));
57     LL ans=0;
58     while(!q.empty()&&k)
59     {
60         node t=q.top();q.pop();
61         ans=(ans+t.val)%mod;
62         if(!(--k))break;
63         if(t.x!=t.y)
64         {
65             ans=(ans+t.val)%mod;//再加一遍(y,x);
66             if(!(--k))break;
67             q.push(node(t.x+1,t.y));
68         }
69         if(t.x==2)q.push(node(t.x,t.y+1));
70     }
71     printf("%lld",ans);
72 }

111

时间: 2024-10-11 10:39:31

[您有新的未分配科技点]数位DP:从板子到基础的相关文章

[您有新的未分配科技点]数位dp:从懵X到板子

数位dp主要用来处理一系列需要数数的问题,一般套路为"求[l,r]区间内满足要求的数/数位的个数" 要求五花八门--比如"不出现某个数字序列","某种数的出现次数"等等-- 面对这种数数题,暴力的想法是枚举每个数,判断是否满足条件 比如这样: #include<cstdio> using namespace std; typedef long long LL; LL l,r,cnt; int main() { scanf("

[您有新的未分配科技点]博弈论进阶:似乎不那么恐惧了…… (SJ定理,简单的基础模型)

这次,我们来继续学习博弈论的知识.今天我们会学习更多的基础模型,以及SJ定理的应用. 首先,我们来看博弈论在DAG上的应用.首先来看一个小例子:在一个有向无环图中,有一个棋子从某一个点开始一直向它的出点移动,双方轮流操作,无法操作者输,问是否先手必胜. 考虑一下我们之前的Nim游戏,如果我们把后继状态看成后继点的话,不难发现Nim游戏的互相转移也是一个DAG.因此,DAG上出度为0的点的sg值为0,再用上一篇博客提到的mex操作来求每个点的值就可以了(注意,这并不是一个"大"子图,不能

[您有新的未分配科技点]可,可,可持久化!?------0-1Trie和可持久化Trie普及版讲解

这一次,我们来了解普通Trie树的变种:0-1Trie以及在其基础上产生的可持久化Trie(其实,普通的Trie也可以可持久化,只是不太常见) 先简单介绍一下0-1Trie:一个0-1Trie节点只有两个子节点,分别代表0和1:从根节点开始,第一层代表限制的最高位,依次往下直到最底层,代表二进制第0位. 0-1Trie上的一条链所表示的数字,就是Trie树中的一个数字.0-1Trie除了节点和插入方式与普通的Trie树略有不同之外,其他操作都是和Trie树完全一样的.在维护这个节点插入过的siz

[您有新的未分配科技点]可,可,可持久化!?------可持久化线段树普及版讲解

最近跑来打数据结构,于是我决定搞一发可持久化,然后发现--一发不可收啊-- 对于可持久化数据结构,其最大的特征是"历史版本查询",即可以回到某一次修改之前的状态,并继续操作:而这种"历史版本查询"会衍生出其他一些强大的操作. 今天,我们主要讲解可持久化线段树.其实,它的另外一个名字"主席树"似乎更加为人所知(主席%%%). 主席树与普通的线段树相比,多出来的操作是在修改时复制修改的一条链,这个操作的过程大概长下面这样. 至于为什么要这样做-- 对

[您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我们还是用rand()来实现平衡 而无旋treap与treap不同的地方,也是其核心,就是它不旋转用两个新的核心函数:merge函数(合并两棵子树)和split函数(分裂出某棵树的前k个节点,并且作为一棵树返回) 首先看merge函数,它是一个递归实现的过程,先看代码: 1 Treap *merge(

[您有新的未分配科技点][BZOJ3545&amp;BZOJ3551]克鲁斯卡尔重构树

这次我们来搞一个很新奇的知识点:克鲁斯卡尔重构树.它也是一种图,是克鲁斯卡尔算法求最小生成树的升级版首先看下面一个问题:BZOJ3545 Peaks. 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走. 现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1.N<=1e5,M,Q<=5*1e5 上面这个题没有要求在线,因此我们可以离线构造

[您有新的未分配科技点]计算几何入门(1):点,向量以及向量的简单应用

在打了一阵数据结构之后,老师表示"今天晚上让学长给你们讲一下计算几何"--然后就死了.jpg 昨天晚上一直在推数学的式子以及回顾讲课的笔记--计算几何特点就是多而杂,即使是入门部分也是如此-- 首先,我们从二维的几何问题开始处理. 我们知道,高中解析几何计算几何的基础是向量(Vector)和点(Point),所以我们先来表示这两个概念: 在计算几何中,点和向量一般用结构体来存储,像这样: 1 struct Point 2 { 3 double x,y,rad; 4 Point(doub

【您有新的未分配天赋点】计算几何:从被纸笔支配的恐怖到达被代码支配的恐怖

开坑时间:2017/8/5 21:25 今天呢$lgl$神犇终于打开了坑害了无数英雄好汉的新陷阱的盖子新世界的大门:计算几何.尽管再不愿意但该来的还是要来.于是我就这么踏上了这条贼船--(缓更,勿催) 本文将对现阶段所需的四项知识做出一些主观的感性分析,希望大家能够就此感性理解一下. 一.凸包 凸包,顾名思义就是把给定点围在里面的最小凸多边形.这个东西一般采用扫描方法进行处理,时间瓶颈为排序的$O(nlogn)$. 链接:http://www.cnblogs.com/Loser-of-Life/

【您有新的未分配天赋点】网络流:从懵逼到完全懵逼

今天呢@assassain julao讲了一个在OI中极其重要,极其有趣,把无数人坑退役的知识点:网络流. 网络流呢顾名思义,就是在一个图中边有流量的限制,并根据这些流量限制做一些跟这个有关的事(ti)情(mu).什么,范围?按zzh神犇的话来说,就是考试中那些看上去像是dp却又推不出式子的问题的通用解法. 按照问题的倾向,我们将问题分为三类:最大流.最小割.最小费用流. 大家看好我要开始口胡了 首先我们研究最大流,介绍最大流大部分解法原理,增广路定理:只要存在增广路,流就可以继续增大.证明显而