• P2602 [ZJOI2010]数字计数(数位DP/好题)


    题目描述

    给定两个正整数 a 和 b,求在 [a,b]中的所有整数中,每个数码(digit)各出现了多少次。

    输入格式

    仅包含一行两个整数 a,b,含义如上所述。

    输出格式

    包含一行十个整数,分别表示 0∼9 在 [a,b]中出现了多少次。

    输入输出样例

    输入 #1复制

    1 99
    

    输出 #1复制

    9 20 20 20 20 20 20 20 20 20
    

    说明/提示

    数据规模与约定

    • 对于 30%30% 的数据,保证 a≤b≤10^6;
    • 对于 100%100% 的数据,保证 1≤a≤b≤10^12。

    大概就是魔改过的数位DP板题。设四维状态分别是剩余的长度(或者说当前的位置(从右到左))len,上一个字符last,之前部分是否紧贴上界的判定标志flag,之前部分是否全0的判定标志zero。每个状态的值为一个pair,first是这个状态下一共有多少符合的数,second是这些数里当前要计算的digit(作为dfs的参数)出现的次数。

    特别注意有一个地方需要判前导0,详情见代码注释QAQ。
    写完翻了翻题解发现有简洁一万倍的写法https://www.luogu.com.cn/blog/bestFy0731/solution-p2602

    #include <bits/stdc++.h>
    //#define int long long
    #define ll long long
    using namespace std;
    ll a, b, num[65], len;
    pair<ll, ll> dp[65][65][2][2];
    ll ans[2][10];
    pair<ll, ll> dfs(int len, int last, bool flag, bool zero, int digit) {
    	if(len == 0) return make_pair(1, 0);//注意second是0,一开始写的是last == digit 但是可能和后面的total_digit += sum1;冲突 
    	if(dp[len][last][flag][zero].first != -1) return dp[len][last][flag][zero];//记忆化
    	ll sum = 0, total_digit = 0;//计算当前状态dp pair用的变量 sum是当前状态的数的个数 total_digit是当前状态中digit出现的总次数
    	for(int i = 0; i <= 9; i++) {
    		ll sum1 = 0, total_digit1 = 0;
    		pair<ll, ll> tmp;
    		if(((flag && i <= num[len]) || !flag) ) {//当前是可行的(指不超出上界)
    			tmp = dfs(len - 1, i, flag && (num[len] == i), zero && (i == 0), digit);//搜索下一个状态的套路写法
    			sum1 = tmp.first;
    			total_digit1 = tmp.second;
    		}
    		sum += sum1;//这个更新是显然的
    		total_digit += total_digit1;//这个更新也是显然的
    		if(i == digit && !(zero && i == 0)) total_digit += sum1;//很重要!不能漏掉条件
    		//这里是说当前遍历的数字正好是digit 这时候需要total_digit += sum1;因为这种状态有sum1个数,每个数前面加上一个digit,对total_digit的贡献就是sum1(这种状态的数的数量)
    		//注意这里要加上判定条件,即排除前导0的情况
    		//可能会问为什么前面两条更新语句不需要这个条件,因为前两条当前为0且之前全为0但后继状态的贡献与此无关
    		//但是下一个状态每个数前面加上一个digit得到的应该是一个实际存在的合法的数,且与当前状态有关,因此需要判定
    	}
    	pair<ll, ll> ret;
    	ret.first = sum;
    	ret.second = total_digit;
    	return (dp[len][last][flag][zero] = ret);
    }
    ll solve(ll x, int digit) {
    	len = 0;
    	ll xx = x;
    	while(xx) {
    		ll now = xx % 10;
    		xx /= 10;
    		len++;
    		num[len] = now;
    	}
    	for(int i = 0; i < 65; i++) {
    		for(int j = 0; j < 65; j++) {
    			for(int k = 0; k < 2; k++) {
    				for(int w = 0; w < 2; w++) {
    					dp[i][j][k][w].first = -1;
    					dp[i][j][k][w].second = 0;
    				}
    			}
    		}
    	}
    	pair<ll, ll> tmp = dfs(len, 11, 1, 1, digit);
    	return tmp.second;
    } 
    signed main() {
    	cin >> a >> b;
    	for(int i = 0; i < 10; i++) {//对于每个数单独算
    		cout << solve(b, i) - solve(a - 1, i) << " ";
    	}
    	return 0;
    }
    
  • 相关阅读:
    【算法总结】搜索算法(上)
    New Beginning
    好想退役啊【笑
    【NOIP2012】DAY1+DAY2题解
    【NOIP2013】Day2不完全题解+代码
    【NOIP2013】DAY1题解+代码
    【NOIP2014】DAY2题解+代码
    【游记】NOIP2015造纸记
    【ACM-ICPC 2018 徐州赛区网络预赛】E. End Fantasy VIX 血辣 (矩阵运算的推广)
    【ACM-ICPC 2018 沈阳赛区网络预赛】不太敢自称官方的出题人题解
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/14716387.html
Copyright © 2020-2023  润新知