hdu5898(数位dp)

题目链接:点击打开链接

大致题意就是给你一个区间[l,r],让你统计在这个区间中共有多少个数字,对于每段连续的奇数数码,这段中数码个数都是偶数个,对于每段连续的偶数数码,该段中数码个数都是奇数个(1<=L<=R<= 9*10^18)

思路:对于这种对数码进行约束同时又进行个数统计的题目,很快就应该想到是用数位dp去做。先贴上代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

class Reader{
	static BufferedReader reader;
	static StringTokenizer tokenizer;
	static void init(InputStream input)
	{
		reader=new BufferedReader(new InputStreamReader(input));
		tokenizer=new StringTokenizer("");
	}
	static String next() throws IOException{
		while (!tokenizer.hasMoreTokens())
			tokenizer=new StringTokenizer(reader.readLine());
		return tokenizer.nextToken();
	}
	static int nextInt() throws IOException{
		return Integer.parseInt(next());
	}
	static long nextLong() throws IOException{
		return Long.parseLong(next());
	}
}
public class Main {
	static int t;
	static String str1,str2;
	static char ch1[],ch2[];
	static long l,r,ans1,ans2;
	static long f[][][];
	static long dfs(char ch[],int front,int nown,boolean flag,boolean lead,boolean islimit)
	{
		int type=flag?1:0;
		if (nown==ch.length)
		{
			if ((flag)&&(front==0)&&(!lead)) return 1;
			return 0;
		}
		if ((!lead)&&(!islimit))
		{
			if (f[front][nown][type]!=-1)
				return f[front][nown][type];
		}
		int end=islimit?(ch[nown]-'0'):9;
		boolean bo,bo2,bo3;
		long num1,num2,res;
		num1=num2=0;
		for (int i=0;i<=end;i++)
		{
			bo=lead&&(i==0);
			bo2=islimit&&(i==end);
			if (front==0) {
				if (i%2==1)
				{
					if (flag) 
						num1+=dfs(ch, front, nown+1, false,false,bo2);
					else 
					{
						num1+=dfs(ch, front, nown+1, true, false, bo2);
						num2+=dfs(ch, 1-front, nown+1, false, false, bo2);
					}
				}
			}
			else {
				if (i%2==0)
				{
					if (flag) 
						num1+=dfs(ch, front, nown+1, false,false,bo2);
					else 
					{
						if (bo) 
							num1+=dfs(ch, front, nown+1, false, bo, bo2);
						else 
						    num1+=dfs(ch, front, nown+1, true, bo, bo2);
						num2+=dfs(ch, 1-front, nown+1, true, bo, bo2);
					}
				}
			}
		}
		res=num1+num2;
		if ((!lead)&&(!islimit)) f[front][nown][type]=res;
		return res;
	}
	static void initarr() {
		f=new long[2][20][2];
		for (int i=0;i<=1;i++)
			for (int j=0;j<=19;j++)
				for (int k=0;k<=1;k++)
					f[i][j][k]=-1;
	}
	static void deal() {
		l--;
		ch1=String.valueOf(l).toCharArray();
		ch2=String.valueOf(r).toCharArray();
		initarr();
		ans1=dfs(ch1, 0, 0, true, true, true);
		initarr();
		ans1+=dfs(ch1,1,0,false,true,true);
		initarr();
		ans2=dfs(ch2, 0, 0, true, true, true);
		initarr();
		ans2+=dfs(ch2,1,0,false,true,true);
		ans2=ans2-ans1;
		System.out.println(ans2);
	}
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		Reader.init(System.in);
		t=Reader.nextInt();
		for (int casenum=1;casenum<=t;casenum++)
		{
			l=Reader.nextLong();
			r=Reader.nextLong();
			System.out.print("Case #"+casenum+": ");
			deal();
		}
	}

}

先想到[l,r]中这样的数的个数,是[1,r]中此种数个数减去[1,l-1]中此种数的个数,这样简化后继续考虑,数位dp部分主要如下(之前没怎么写过数位dp,代码有点又长又丑。。。)

static long dfs(char ch[],int front,int nown,boolean flag,boolean lead,boolean islimit)
	{
		int type=flag?1:0;
		if (nown==ch.length)
		{
			if ((flag)&&(front==0)&&(!lead)) return 1;
			return 0;
		}
		if ((!lead)&&(!islimit))
		{
			if (f[front][nown][type]!=-1)
				return f[front][nown][type];
		}
		int end=islimit?(ch[nown]-'0'):9;
		boolean bo,bo2,bo3;
		long num1,num2,res;
		num1=num2=0;
		for (int i=0;i<=end;i++)
		{
			bo=lead&&(i==0);
			bo2=islimit&&(i==end);
			if (front==0) {
				if (i%2==1)
				{
					if (flag) 
						num1+=dfs(ch, front, nown+1, false,false,bo2);
					else 
					{
						num1+=dfs(ch, front, nown+1, true, false, bo2);
						num2+=dfs(ch, 1-front, nown+1, false, false, bo2);
					}
				}
			}
			else {
				if (i%2==0)
				{
					if (flag) 
						num1+=dfs(ch, front, nown+1, false,false,bo2);
					else 
					{
						if (bo) 
							num1+=dfs(ch, front, nown+1, false, bo, bo2);
						else 
						    num1+=dfs(ch, front, nown+1, true, bo, bo2);
						num2+=dfs(ch, 1-front, nown+1, true, bo, bo2);
					}
				}
			}
		}
		res=num1+num2;
		if ((!lead)&&(!islimit)) f[front][nown][type]=res;
		return res;
	}

之前数位dp看的这里:https://blog.csdn.net/wust_zzwh/article/details/52100392,不过还是有点浮躁,看过后也没怎么进行练习,所以这道基本就是我写的第一道比较标准点的数位dp了。。。记忆化搜索这种东西我还是用的比较少,然后数位dp又有点自己的标准,所以大家初涉数位dp还是希望琢磨透了多去上手练练。

这里nown是表示当前考虑第(nown+1)位的数(这里最高位是第一位)front的0和1分别代码当前位选奇数还是偶数,flag表示之前奇数数码或者偶数数码是否已经满足题目所要求性质,lead和islimit的含义,上面的链接里有讲解,然后相应的bo和bo2的含义也可得知。

这里做法主要是枚举当前位数码i,然后针对front进行讨论

front=0,即当前位选奇数数码,判断i后,内部代码如下

if (flag) 
	num1+=dfs(ch, front, nown+1, false,false,bo2);
else 
{
	num1+=dfs(ch, front, nown+1, true, false, bo2);
	num2+=dfs(ch, 1-front, nown+1, false, false, bo2);
}

如果前面的奇数数码已经有偶数个,满足了性质,由于当前位又选了奇数数码,所以接下来只能再选奇数数码;

而如果前面的奇数数码有奇数个,未满足题目要求性质,当前位选了奇数数码后自然就满足了,接下来就有两种策略:继续选奇数数码,或者直接结束该连续段然后转而选择偶数数码(此时注意,连续偶数数码9段要有奇数个偶数,所以要出现该偶数段但还没选偶数,也就是连续偶数数码个数是0个时,肯定是不满足性质的(*))


front=1,即当前位选偶数数码,判断完i后,内部代码如下

if (flag) 
	num1+=dfs(ch, front, nown+1, false,false,bo2);
else 
{
	if (bo) 
	    num1+=dfs(ch, front, nown+1, false, bo, bo2);
	else 
	    num1+=dfs(ch, front, nown+1, true, bo, bo2);
	num2+=dfs(ch, 1-front, nown+1, true, bo, bo2);
}
这里跟front=0的情形大体还是比较像的,主要注意接下来转移选奇数数码时,由于连续奇数数码段要有偶数个奇数,连续奇数数码个数是0个,是满足性质的;还有一点,如果当前位选完了还处于前导0状态的话,那么之后基本还是(*)的情形



猜你喜欢

转载自blog.csdn.net/lixiaomu2/article/details/80218926