BZOJ 1087:[SCOI2005]互不侵犯King(状压DP)
[SCOI2005]互不侵犯King
【题目描述】
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
【输入】
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
【输出】
方案数。
【样例输入】
3 2
【样例输出】
16
分析:
经典的状压DP题目,可我竟然调了很长时间都没对,后来发现是DP枚举范围错了,直接枚举到最大情况导致答案大得离谱,所以透彻理解算法很重要。
其它懒得写了。
代码1(第一次写的代码很丑,还很多余地在每个二进制首位增添一个1......):
program king; var f:array[0..10,512..1023,1..100]of int64; a:array[512..1023,1..11]of 0..1; sum1,w:array[512..1023]of longint; g:array[512..1023,512..1023]of boolean; h:array[512..1023]of boolean; n,i,m,j,k,u,v,l,num,num1:longint; procedure work(x:longint); var i,c,y:longint; begin i:=x; c:=10; while i>0 do begin a[x,c]:=i mod 2; if a[x,c]=1 then begin inc(sum1[x]); if c>1 then y:=10-c+1; end; i:=i div 2; c:=c-1; end; dec(sum1[x]); w[x]:=y; end; begin readln(n,k);num1:=512;num:=1; for i:=1 to n do num:=num*2; num:=num1+num-1; for i:=num1 to num do begin work(i); end; for u:=num1 to num do for v:=num1 to num do for l:=2 to 10 do begin g[u,v]:=true; if (a[u,l]=1)and(((a[v,l-1]=1)and(l>2))or(a[v,l]=1)or(a[v,l+1]=1)) then begin g[u,v]:=false;break; end; if (a[v,l]=1)and(((a[u,l-1]=1)and(l>2))or(a[u,l]=1)or(a[u,l+1]=1)) then begin g[u,v]:=false;break; end; end; fillchar(f,sizeof(f),0); for i:=num1 to num do begin h[i]:=true; for j:=2 to 10 do if ((a[i,j]=1)and(((a[i,j-1]=1)and(j>2))or(a[i,j+1]=1))) then begin h[i]:=false; break; end; if h[i]=true then f[1,i,sum1[i]]:=1; end; for i:=2 to n+1 do for j:=0 to k do for u:=num1 to num do if (sum1[u]<=j)and(h[u]=true) then for v:=num1 to num do if (sum1[v]+sum1[u]<=j)and(h[v]=true) then begin if g[u,v]=true then f[i,u,j]:=f[i,u,j]+f[i-1,v,j-sum1[u]]; end; writeln(f[n+1,num1,k]); end.
代码2(改进的代码,用了一些位运算,比第一次好看一点):
program king; var f:array[0..10,0..511,1..100]of int64; sum1:array[0..511]of longint; g:array[0..511,0..511]of boolean; h:array[0..511]of boolean; n,i,m,j,k,u,v,l,t,num:longint; begin readln(n,k); fillchar(g,sizeof(g),false); fillchar(h,sizeof(h),false); fillchar(f,sizeof(f),0); num:=1; for i:=1 to n do num:=num*2; num:=num-1; for i:=0 to num do if i and (i shr 1)=0 then begin j:=i;t:=0; while j>0 do begin t:=t+j and 1; j:=j shr 1; end; sum1[i]:=t; h[i]:=true; end; for i:=0 to num do if h[i]=true then for j:=0 to num do if h[j]=true then if (i and j=0)and(i and (j shr 1)=0)and(j and (i shr 1)=0) then g[i,j]:=true; for i:=0 to num do if h[i]=true then f[1,i,sum1[i]]:=1; for i:=2 to n+1 do for j:=0 to k do for u:=0 to num do if (h[u]=true)and(sum1[u]<=j) then for v:=0 to num do if (h[v]=true)and(sum1[u]+sum1[v]<=j) then if g[v,u]=true then inc(f[i,u,j],f[i-1,v,j-sum1[u]]); writeln(f[n+1,0,k]); end.