zoukankan      html  css  js  c++  java
  • ARC127A Leading 1s 数位DP

    A.Leading 1s

    题面
    题目大意:
    定义(f(i))(i)的前缀1个数,现给定n求(sum_{i = 1}^{n}f(i))

    题解:
    emmm官方题解貌似也挺精妙的,不过我就直接上数位DP了,复杂度也一样。
    直接DP数组统计前导1个数不太方便,因为前导1最多就15个,所以我们考虑统计前导1个数为l时的方案数。
    考虑到我们不需要知道每个位具体为多少,我们只需要知道它是否为前导1,
    因此我们令f[i][j][k][l]表示DP到i位,是否为前导1,是否在限制上,前导1的个数为l的方案数。
    最后统计答案的时候直接枚举i然后计算ans += f[1][0/1][0/1][i] * i;
    转移的时候注意第二维表示的是是否为前导1,当前位是否为前导1不仅要看当前位是否为1,还要考虑上一位是否也是前导1.
    由此转移f[i][0][0][l]时,需要多分几个部分考虑。
    一类是上一位在限制上,那么当前位就需要注意不能超过lim[i]的限制,同时如果上一位是前导1的话,当前位就不能取1.
    另一类是上一位不在限制上,那么当前位就不用考虑lim[i]的限制,但是仍然要注意如果上一位是前导1的话,当前位也不能取1.
    复杂度(O(log^2n))
    具体方程可以看代码。
    里面有些部分应该是归纳到一起的,但是我懒得想就直接按照lim[i]的值分成几种情况讨论了

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define LL long long
    
    LL n, tot; LL ans;
    LL lim[20]; LL f[17][2][2][17];//f[i][j][k][l],DP到i位,是否为前导1,是否在限制上,前导1的长度 
    
    void getlim(LL x)
    {
    	while(x) lim[++ tot] = x % 10, x /= 10;
    }
    
    void out(int x)
    {
    	printf("DP at ----- %d -----
    ", x);
    	for(R i = 1; i <= tot; i ++)
    	{
    		printf("num = %d
    ", i);
    		printf("%lld %lld %lld %lld
    ", f[x][0][0][i], f[x][0][1][i], f[x][1][0][i], f[x][1][1][i]);
    	}
    }
    
    void work()
    {
    	for(R i = tot; i; i --)//DP到第i位
    	{
    		if(i == tot) f[i][1][lim[i] == 1][1] ++;//当前位为首位且为前导1
    		else f[i][1][0][1] ++;
    		if(i == tot) continue;
    		for(R j = tot - i; j; j --) //枚举之前前导1的个数 (不是首位了) 
    		{
    			if(!lim[i]) 
    			{
    				f[i][0][0][j] += 10 * f[i + 1][0][0][j] + 9 * f[i + 1][1][0][j];//当前位不为前导1且不在限制上, 之前就没有前导1,现在才能取1,否则不能取1
    				f[i][0][1][j] += f[i + 1][0][1][j] + f[i + 1][1][1][j];//当前位为0,刚好在限制上
    				f[i][1][0][j + 1] += f[i + 1][1][0][j];//当前位为1,
    				//f[i][0][0][j] += f[i + 1][0][1][j];//之前就在限制上了,那就对这一位有限制了
    				continue;
    			}
    			if(lim[i] != 1) //此时若为前导1,则必不在限制上
    			{
    				f[i][1][0][j + 1] += f[i + 1][1][0][j] + f[i + 1][1][1][j];//如果lim[i] != 1,说明为1的时候不可能在限制上 
    				f[i][0][0][j] += lim[i] * f[i + 1][0][1][j] + (lim[i] - 1) * f[i + 1][1][1][j];//如果当前位不为前导1且不在限制上(只有之前就在限制上,当前位才可能在限制上,如果不是首位的话!
    				f[i][0][0][j] += 10 * f[i + 1][0][0][j] + 9 * f[i + 1][1][0][j];//之前不在限制上,因此当前位不可能在限制上
    				f[i][0][1][j] += f[i + 1][0][1][j] + f[i + 1][1][1][j];//如果当前位在限制上
    			}
    			else //不然就 可能在限制上 也可能不在
    			{
    				f[i][1][0][j + 1] += f[i + 1][1][0][j]; //如果当前位为前导1但不在限制上
    				f[i][1][1][j + 1] += f[i + 1][1][1][j];
    				//f[i][0][0][j] += f[i + 1][1][1][j] + f[i + 1][1][0][j] + f[i + 1][0][0][j] + f[i + 1][0][1][j];//如果当前位取0
    				//f[i][0][0][j] += f[i + 1][0][0][j];//如果当前位取1但不是前导1
    				f[i][0][0][j] += 9 * f[i + 1][1][0][j] + 10 * f[i + 1][0][0][j];//如果之前不在限制上
    				f[i][0][0][j] += f[i + 1][1][1][j] + f[i + 1][0][1][j];
    				f[i][0][1][j] += f[i + 1][0][1][j];//如果当前位取1但不是前导1 且之前就在限制上
    			}
    		}
    		//out(i);
    	}
    }
    
    void getans()
    {
    	for(R i = 1; i <= tot; i ++) 
    	{
    		LL now = 0;
    		now += f[1][0][0][i] + f[1][0][1][i] + f[1][1][0][i] + f[1][1][1][i];
    		//printf("%lld %lld %lld %lld
    ", f[1][0][0][i], f[1][0][1][i], f[1][1][0][i], f[1][1][1][i]);
    		ans += now * i;
    	}
    	printf("%lld
    ", ans);
    }
    
    int main()
    {
    //	freopen("in.in", "r", stdin);
    	scanf("%lld", &n);
    	getlim(n);	
    	work(); 
    	getans();
    	return 0;
    }
    
    本文不允许商业性使用,个人转载请注明出处! 知识共享许可协议
    本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。
  • 相关阅读:
    [csp-s模拟测试72] 简单的期望
    [csp-s模拟测试72] 简单的序列
    csp-s模拟测试70
    经验积累
    [csp-s模拟测试69] 回滚莫队
    [模板]主席树查前趋后继
    复习及学习计划
    错排公式
    csp-s模拟测试 56~65
    LIS LCS LCIS
  • 原文地址:https://www.cnblogs.com/ww3113306/p/15361147.html
Copyright © 2011-2022 走看看