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 -----\n", x);
	for(R i = 1; i <= tot; i ++)
	{
		printf("num = %d\n", i);
		printf("%lld %lld %lld %lld\n", 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\n", 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\n", ans);
}

int main()
{
//	freopen("in.in", "r", stdin);
	scanf("%lld", &n);
	getlim(n);	
	work(); 
	getans();
	return 0;
}
posted @ 2021-10-02 00:13  ww3113306  阅读(110)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。