トップページに戻る
次の競技プログラミングの問題へ
前の競技プログラミングの問題へ
ABC-029-D 1
■■■問題■■■
高橋君は1以上N以下のすべての整数を十進表記で1回ずつ紙に書きました。
この作業で、高橋君は1という数字を何個書いたでしょうか。
■■■入力■■■
N
1行目に整数 N (1 <= N < 10億) が与えられる。
■■■出力■■■
標準出力に、高橋君が書いた1という数字の個数を出力し、最後に改行せよ。
C#のソース (群数列で考える方法)
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("12");
//5
//1,10,11,12の十進表記に合計で5個の1という数字が含まれます
}
else if (InputPattern == "Input2") {
WillReturn.Add("345");
//175
}
else if (InputPattern == "Input3") {
WillReturn.Add("999999999");
//900000000
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
static void Main()
{
List<string> InputList = GetInputList();
int N = int.Parse(InputList[0]);
Console.WriteLine(DervieCnt1(N));
//for (int I = 1; I <= 20000; I++) {
// int Result1 = DervieCnt1(I);
// int Result2 = DervieCnt1Naive(I);
// Console.WriteLine("{0}までの1の数は{1}", I, Result1);
// Console.WriteLine("{0}までの1の数は{1}(ナイーブ版)", I, Result2);
// if (Result1 != Result2) {
// break;
// }
//}
}
//1の数を求める
static int DervieCnt1(int pLimit)
{
int WillReturn = 0;
//上限の桁数を求める
int LimitKetasuu = (int)Math.Truncate(Math.Log10(pLimit)) + 1;
int Kousuu = pLimit + 1;
//桁数のループ
//1桁目は、{0123456789}の群数列
//2桁目は、{0が10個、1が10個、2が10個、以下9まで}の群数列
//3桁目は、{0が100個、1が100個、2が100個、以下9まで}の群数列
for (int I = 1; I <= LimitKetasuu; I++) {
int GunKousuu = (int)Math.Pow(10, I);
int GunKousuuDev10 = GunKousuu / 10;
//完全に含む群の数 * 群の中の1の数
int wkCnt1 = Kousuu / GunKousuu * GunKousuuDev10;
//完全に含まない群の中の1の数
int wkCnt2;
if (Kousuu % GunKousuu <= GunKousuuDev10) wkCnt2 = 0;
else wkCnt2 = Math.Min(GunKousuuDev10, Kousuu % GunKousuu - GunKousuuDev10);
WillReturn += wkCnt1 + wkCnt2;
}
return WillReturn;
}
//1の数を求める(ナイーブ版)
static int DervieCnt1Naive(int pLimit)
{
int WillReturn = 0;
for (int I = 1; I <= pLimit; I++) {
int CopiedVal = I;
while (CopiedVal > 0) {
if (CopiedVal % 10 == 1) WillReturn++;
CopiedVal /= 10;
}
}
return WillReturn;
}
}
C#のソース (桁DPを使う方法)
using System;
using System.Collections.Generic;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("12");
//5
}
else if (InputPattern == "Input2") {
WillReturn.Add("345");
//175
}
else if (InputPattern == "Input3") {
WillReturn.Add("999999999");
//900000000
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
static void Main()
{
List<string> InputList = GetInputList();
string N = InputList[0];
//場合の数[1の数,数字自由フラグ]なDP表
int[,] PrevDP = new int[11, 2];
PrevDP[0, 0] = 1;
for (int I = 0; I <= N.Length - 1; I++) {
int[,] CurrDP = new int[11, 2];
for (int J = 0; J <= PrevDP.GetUpperBound(0); J++) {
for (int K = 0; K <= PrevDP.GetUpperBound(1); K++) {
if (PrevDP[J, K] == 0) continue;
for (char NewChar = '0'; NewChar <= '9'; NewChar++) {
if (K == 0 && N[I] < NewChar) break;
int NewJ = J;
if (NewChar == '1') NewJ++;
int NewK = K;
if (N[I] > NewChar) NewK = 1;
CurrDP[NewJ, NewK] += PrevDP[J, K];
}
}
}
PrevDP = CurrDP;
}
long Answer = 0;
for (int I = 0; I <= PrevDP.GetUpperBound(0); I++) {
for (int J = 0; J <= PrevDP.GetUpperBound(1); J++) {
Answer += PrevDP[I, J] * I;
}
}
Console.WriteLine(Answer);
}
}
解説
下記の昇順に並べた数値を桁ごとに縦方向に見た数列を、
群数列として考えてます。
0000
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
以下略