知识准备:JAVA,贪心算法,动态规划
问题分析
1.使用暴力破解的方法计算的话,极有可能超时以及精度问题(自己写了一下只能写到9,而且精度直接就超了。。),所以只能使用动态规划
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long t1 = System.currentTimeMillis();
long count =0;
long i,pCount =(long) Math.pow(10,n),v4=0,ci=0,startIdx = (long) Math.pow(10,n-1);
System.out.println(startIdx+" TO "+pCount);
for (i = 0; v4<pCount;i++) {
v4=Integer.valueOf(new BigInteger(Long.toString(i), 10).toString(4));
String vn = String.valueOf(v4);
String[] vna = vn.split("");
int[] pres = new int[vna.length];
for (int j = 0; j < vna.length; j++) {
pres[j] = Integer.parseInt(vna[j]);
}
boolean isTrue = judgeData(pres);
if(isTrue && v4 > startIdx) {
count++;
}
}
long t2 = System.currentTimeMillis();
System.out.println("计算次数:"+ i+"\t耗时"+(t2-t1));
System.out.println(count);
}
public static boolean judgeData(int[] tmp) {
if(tmp[0]==0) return false;
int t1 =-1,t3=-1;
boolean b0=false,b1=false,b2=false,b3=false;
boolean f0=true,f1=true,f2=true,f3=true;
for (int i = 0; i < tmp.length; i++) {
if(tmp[i]==1 && f1) {
t1 = i;
b1 =true;
f1 = false;
}
if(tmp[i]==3 && f3) {
t3 = i;
b3 = true;
f3 = false;
}
if(tmp[i]==0 && f0) {
b0 = true;
f0 = false;
}
if(tmp[i]==2 && f2) {
b2 = true;
f2 = false;
}
}
if(t1==-1 || t3 ==-1 || !b0|| !b1|| !b2|| !b3) return false;
for (int i = t1+1; i < tmp.length; i++) {
if(tmp[i]==0) {
return false;
}
}
for (int i = t3+1; i < tmp.length; i++) {
if(tmp[i]==2) {
return false;
}
}
return true;
}
继续分析:只能选取动态规划:
动态规划的思路:前n步所有的最优解=前n-1步所有的最优解+第n步的最优解(先上图容易理解)。
-
前面的数字代表图中状态。
- (参考图右)首先考虑0、1、2、3四位的放置,不考虑四位以上。 状态0:即首位不能是0(题意),不可以放置1(如果放置1,0肯定在1后面,所以不可以),2可以,不可以放置3(如果放置3,2就没有地方放了)所以第一位只能是2。
- 现在我们来接着状态0继续:我们现在成功放了2了,那么剩下0、1、3。接着可以放置0,不可以放置1(因为放置1之后,0就没放放了),得到状态1——放0;可以放置3,所以我们获得了状态2——放0/3
- 接着状态1,我们放置了0,那么剩下1、3,201?可以,203?也可以,得到状态3
- 接着状态1,我们放置了3,那么剩下0、1,230?可以,231?步可以,得到状态4
- 我们回过头再考虑状态4,(状态4是由状态2来的),230?可以,231?不可以
- 最后状态5,可以由状态3添3或者状态4添1得到
总结:第一步骤:2 剩余 0 1 3 第二步骤:2 0 剩余 1 3 第三步骤:2 3 剩余 0 1 第四步骤:2 0 1 剩余 3 第五步骤:2 0 3 剩余 1 第六步骤:2 0 3 1 剩余 无
(参考图左)考虑四位以上,第i位出现第j种状态的情况数表示为 a[i][j]
i位数的来源在于(i-1)位数(已经按顺序排列)+最后一位数字
例如:
a[i][0] = a[i-1][0] ;
使用过2 的情况(状态0)可以由: 1>状态0添2而来
2>前i-1位添加2而来(但是只能写2)
a[i][1] = (a[i-1][0] + a[i-1][1] *2);
使用过 2 0 的情况(状态1)可以由:1> 状态0添加0而来
2> 前i-1位(如当前位为5,i-1位就是4位)使用2 0,第i位使用0或2 (顺序正确)
a[i][2] = (a[i-1][0] + a[i-1][2] );
使用过 2 3 的情况(状态2)可以由:1> 状态0添加3而来
2> 前i-1位使用2 3,第i位使用3 (因为已经有了3了不能用2了,只能用3(因为从4位的时候这个地方就是3,并且只能是3))
a[i][3] = (a[i-1][1] + a[i-1][3] *2);
使用过 2 0 1 的情况(状态3)可以由:1> 状态1添加1而来
2> 前i-1位使用2 0 1,第i位使用1或2 (因为已经有了1了不能用0了,只能用1)
a[i][4] = (a[i-1][1] + a[i-1][2] + a[i-1][4] *2);
使用过 2 0 3 的情况(状态4)可以由: 1> 状态1添加3而来
2> 状态2添加0而来
3> 前i-1位使用2 0 3,第i位使用0或3 (因为已经有了3了不能用2了,只能用3)
a[i][5] = (a[i-1][3] + a[i-1][4] + a[i-1][5] *2);
使用过2 3 0 1的情况(状态5)可以由: 1> 状态3添加3而来
2>状态4添加1而来
3> 前i-1位使用2 3 0 1(状态6),第i位使用1或3
(因为已经有了1了不能用0了,只能用1)
(因为已经有了3了不能用2了,只能用3)
题目要求的答案为a[n][5],即0 1 2 3都使用过。
即JAVA版的代码:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
long count = in.nextLong();
int MOD=1000000007;
long[][] sa = new long[(int) (count+1)][6];
sa[0][0] =0;
//由每一步的最优解求得
for (int i = 1,j=0; i <= count; i++,j++) {
sa[i][0] = 1;
sa[i][1] = (sa[j][0]+sa[j][1]*2)%MOD;
sa[i][2] = (sa[j][0]+sa[j][2])%MOD;
sa[i][3] = (sa[j][1]+sa[j][3]*2)%MOD;
sa[i][4] = (sa[j][1]+sa[j][2]+sa[j][4]*2)%MOD;
sa[i][5] = (sa[j][3]+sa[j][4]+sa[j][5]*2)%MOD;
}
// System.out.println(sa[(int) count][5]);
int location = 1;
for (int i = 0; i < sa.length; i++) {
if(i==0) continue;
System.out.print(location++ +"位:\t");
for (int j = 0; j < sa[i].length; j++) {
System.out.print("【状态:"+j+":\t"+sa[i][j]+"】\t");
}
System.out.println();
}
}