组合计数·图形统计
先来看相关题目:
1. Uva 10884 Persephone
题意:用$n(n \leq 100)$根长度为$1$的木条拼出一个周长为$n$的,各边与坐标轴平行的多边形,并要求其最小外接矩形周长也是$n$,如下图所示。求满足条件的方案数。
分析:容易看出满足条件的一定是凸多边形,并且如果只考虑其在垂直方式向单侧轮廓的变化,具有单峰性。并且该多边形需要在四个方向上与外界多边形有一段且仅有一段公共边,因此我们可以利用这些特征对图形分类,即dp。用$dp(u, l, r, o)$表示当前行(两侧轮廓在该行位置已确定)为$u$,且左右位置分别为$l, r$,$o$是两位的二进制数,高位为$0$表示左侧边仍在$\leftarrow$状态,即峰值之前,为$1$表示经过峰值且严格小于峰值,低位对应右侧边的情况。于是根据$o$的取值和$l, r$的位置可以计算出下一步可达的所有合法状态,最终当我们到达最后一行时,如某一侧经过峰值或者在最左(最优)并且另一侧也满足该条件说明构造的轮廓是合法的,否则不合法。
这样做对于给定高度$r$和宽度$c$的外界矩形共有$O(r^2c)$个状态,状态转移复杂度为$O(c^2)$,对应时间复杂度为$O(r^2c^3)$,而一共需要计算的矩形有$O(n^2)$个,这样会超时,需要一点优化,固定宽度,计算最宽的矩形,那么同宽度的其余矩形的方案可由其$dp$数组得出。尽管如此,下面的java代码还是跑了800多ms。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.io.*; 2 import java.math.BigInteger; 3 import java.util.Arrays; 4 import java.util.Iterator; 5 import java.util.Scanner; 6 import java.util.TreeSet; 7 8 public class Main { 9 private final static int maxn = 55; 10 BigInteger[][] ans = new BigInteger[maxn][maxn]; 11 BigInteger[][][][] dp = new BigInteger[maxn][maxn][maxn][4]; 12 private int row, col; 13 private final static BigInteger neg = BigInteger.valueOf(-1); 14 BigInteger dfs(int u, int l, int r, int o){ 15 if(dp[u][l][r][o].compareTo(neg) != 0) return dp[u][l][r][o]; 16 if(u == row){ 17 boolean lhs = ((o >> 1) & 1) == 1 || l == 1; 18 boolean rhs = (o & 1) == 1 || r == col; 19 BigInteger tans = lhs && rhs ? BigInteger.ONE : BigInteger.ZERO; 20 return dp[u][l][r][o] = tans; 21 } 22 BigInteger tem = BigInteger.ZERO; 23 if(o == 0){ 24 for(int tl = l; tl >= 1; tl--) for(int tr = r; tr <= col; tr++){ 25 tem = tem.add(dfs(u + 1, tl, tr, 0)); 26 } 27 if(l == 1) for(int tl = l + 1; tl < r; tl++) for(int tr = r; tr <= col; tr++){ 28 tem = tem.add(dfs(u + 1, tl, tr, 2)); 29 } 30 if(r == col) for(int tr = r - 1; tr > l; tr--) for(int tl = l; tl >= 1; tl--){ 31 tem = tem.add(dfs(u + 1, tl, tr, 1)); 32 } 33 if(l == 1 && r == col) for(int tl = l + 1; tl < r; tl++) for(int tr = r - 1; tr > tl; tr--){ 34 tem = tem.add(dfs(u + 1, tl, tr, 3)); 35 } 36 }else if(o == 1){ 37 for(int tr = r; tr > l; tr--) for(int tl = l; tl >= 1; tl--){ 38 tem = tem.add(dfs(u + 1, tl, tr, 1)); 39 } 40 if(l == 1) for(int tl = l + 1; tl < r; tl++) for(int tr = r; tr > tl; tr--){ 41 tem = tem.add(dfs(u + 1, tl, tr, 3)); 42 } 43 }else if(o == 2){ 44 for(int tl = l; tl < r; tl++) for(int tr = r; tr <= col; tr++){ 45 tem = tem.add(dfs(u + 1, tl, tr, 2)); 46 } 47 if(r == col) for(int tr = r - 1; tr > l; tr--) for(int tl = l; tl < tr; tl++){ 48 tem = tem.add(dfs(u + 1, tl, tr, 3)); 49 } 50 }else if(o == 3){ 51 for(int tl = l; tl < r; tl++) for(int tr = r; tr > tl; tr--){ 52 tem = tem.add(dfs(u + 1, tl, tr, 3)); 53 } 54 } 55 return dp[u][l][r][o] = tem; 56 } 57 58 void init(){ 59 int lim = 53; 60 for(int j = 2; j <= lim; j++){ 61 col = j; 62 row = lim - j; 63 if(col > row) continue; 64 for(int u = 0; u < maxn; u++) for(int v = 0; v < maxn; v++) for(int w = 0; w < maxn; w++){ 65 for(int id = 0; id < 4; id++) dp[u][v][w][id] = neg; 66 } 67 for(int l = 1; l <= col; l++) for(int r = l + 1; r <= col; r++) dfs(2, l, r, 0); 68 for(int i = j; i + j <= lim; i++){ 69 BigInteger tans = BigInteger.ZERO; 70 int pos = row - i + 2; 71 for(int l = 1; l <= col; l++) for(int r = l + 1; r <= col; r++){ 72 tans = tans.add(dp[pos][l][r][0]); 73 } 74 ans[i][j] = ans[j][i] = tans; 75 } 76 } 77 //System.out.println("ok"); 78 } 79 BigInteger getAns(int num){ 80 if((num & 1) == 1) return BigInteger.ZERO; 81 num /= 2; 82 BigInteger tans = BigInteger.ZERO; 83 for(int i = 1; i < num; i++) tans = tans.add(ans[i + 1][num - i + 1]); 84 return tans; 85 } 86 87 public void solve(){ 88 init(); 89 //System.out.println("ok"); 90 int T, kase = 0; 91 Scanner cin = new Scanner(System.in); 92 while(cin.hasNextInt()){ 93 T = cin.nextInt(); 94 while(T-- > 0){ 95 int num = cin.nextInt(); 96 BigInteger tans = getAns(num); 97 System.out.println("Case #" + (++kase) + ": " + tans); 98 } 99 } 100 } 101 public static void main(String []argc){ 102 Main e = new Main(); 103 e.solve(); 104 } 105 }