习题课3-2(动态规划2、计算可行方案)

习题课3-2

刷油漆

  • n辆车排成1排,还有m种涂料,第i中涂料可以涂ai辆车,恰好可以涂n辆车,求有多少种涂油漆的方案,要保证相邻车辆油漆不同,

  • 最后输出答案,除以23333的余数(答案非常大)

  • (a+b) mod c = [(a mod c) + (b mod c)] mod c

  • (aXb) mod c = [(a mod c) X (b mod c)] mod c

问题分析

  • 排成一排->暗示我们顺序
  • 恰好涂完->油漆不会剩余
  • 相邻车的油漆种类不同

解法1(动态规划)(遍历涂完x辆车)

  • 令f(x,p,c1,c2,…,cm)表示第x辆车到第n辆车,第x-1辆车涂的油漆种类是p,第i种油漆还剩下ci次,得到的方案数
  • f(n+1,p,0,…0) = 1,(1<=p<=m)
  • 到此截止,返回方案数1,动态规划必须考虑边界情况
  • f(x,p,c1,c2,…,cm) = f(x+1,q,c1,c2,…,cq-1,…,cm)求和 (q不等于p)
  • 其中n等于ai求和
  • 最后的答案形式是f(1,0,a1,a2,…,am)
  • 1是第一辆车开始,0是第0辆车涂的颜料种类,a1,a2,…,am是m种燃料还剩下的数量
  • 可以循环计算也可以记忆化搜索
  • 对x,p,c1,…,cm遍历循环,同时要保证没有后效性
  • m不确定,不知道是几维数组,不好定义,状态数量有O(nm X (a1,…an累乘))
演示案例—记忆化搜索(假设是4种油漆)
  • // 假设是四维
    // f(x,p,c1,c2,...,cm)
    int f[N][N][N][N][N][N],n;
    
    int dp(int x,int p,int c1,int c2,int c3,int c4){
          
          
    	if(x == n+1)
    		return 1;
        // 把ans作为后边数组那一串的新名称
        // 取地址符
    	int &ans = f[x][p][c1][c2][c3][c4];
        // 记忆化搜索
        // 已经计算过了,直接返回
    	if(ans !=-1)
    		return ans;
        // ans=0后,f[x][p][c1][c2][c3][c4]也就等于0了
    	ans = 0;
    	if(p!=1 && c1>0)
    		ans += dp(x+1,1,c1-1,c2,c3,c4);
    	if(p!=2 && c2>0)
    		ans += dp(x+1,2,c1,c2-1,c3,c4);
    	if(p!=3 && c3>0)
    		ans += dp(x+1,3,c1,c2,c3-1,c4);
    	if(p!=4 && c4>0)
    		ans += dp(x+1,4,c1,c2,c3,c4-1);
    	return ans;
    }
    
  • 两层,外侧是dp,内侧是f(x,p,c1,c2,c3,c4)

  • // n辆车,m种油漆,第i种油漆够涂ai辆车,同时所有油漆恰好能涂完n辆车,若任意两辆相邻的车颜色不能相同,有多少种涂油漆的方案
    // 返回值:方案数
    int getAnswer(int m,vector<int> a){
          
          
    	// 一开始f初始化为-1,表示没有计算过
    	memset(f,-1,sizeof f);
    	n = 0;
    	for(int i=0;i<m;i++)
    		n += a[i];
    	return dp(1,0,a[0],a[1],a[2],a[3]);
    }
    

解法1+

  • x的状态是多余的,x是可以通过ci来算出来的,即

  • x = 1 + ∑ i = 1 m ( a i − c i ) x = 1 + \sum_{i=1}^m(a_i-c_i) x=1+i=1m(aici)

  • 同时c_i全是0的时候涂油漆这个过程就结束了,因为

  • n = ∑ i = 1 n a i n = \sum_{i=1}^na_i n=i=1nai

  • 而不需要判断x == n+1

  • 即令

  • f ( p , c 1 , c 2 , . . . , c m ) f(p,c1,c2,...,cm) f(p,c1,c2,...,cm)

  • 表示

  • x = 1 + ∑ i = 1 m ( a i − c i ) x = 1 + \sum_{i=1}^m(a_i-c_i) x=1+i=1m(aici)

  • 辆车到第

  • n = ∑ i = 1 m a i n = \sum_{i=1}^ma_i n=i=1mai

  • 辆车,第x-1辆车涂的油漆种类是p,第i中油漆还剩下c_i次,得到的方案数

  • f ( p , 0 , . . . , 0 ) = 1 , ( 1 ≤ p ≤ m ) f(p,0,...,0)=1,(1\leq p \leq m) f(p,0,...,0)=1,(1pm)

  • f ( p , c 1 , c 2 , . . . , c m ) = ∑ q / p f ( q , c 1 , c 2 , . . . , c q − 1 , . . . , c m ) f(p,c_1,c_2,...,c_m) = \sum_{q/p}f(q,c_1,c_2,...,c_q-1,...,c_m) f(p,c1,c2,...,cm)=q/pf(q,c1,c2,...,cq1,...,cm)

  • 最后答案是

  • f ( 0 , a 1 , a 2 , . . . , a m ) f(0,a_1,a_2,...,a_m) f(0,a1,a2,...,am)

解法2(遍历用完c1,…,cm种油漆)

  • 同样是动态规划

  • 上一种解法状态太多,并且数组维度无法确定,尝试减少状态数

  • 发现a_i<=5 (看题目的输出限制)

  • 相同次数的油漆是不是可以作为一种

  • f(a,b,c,d,e,last)

    • 够涂1辆车的种类有a中
    • 够涂5辆车的油漆还有e中
    • 前一辆车的颜色是从上面5中取出来的,last==2表示前一辆车的油漆种类是从b中取出来的
  • f(0,0,0,0,last)==1表示一种方案

  • f ( 0 , 0 , 0 , 0 , l a s t ) = 1 , ( 1 ≤ l a s t ≤ 5 ) f(0,0,0,0,last) = 1,(1\leq last \leq 5) f(0,0,0,0,last)=1,(1last5)

  • f(a,b,c,d,e,last) = [a>0](a-[last==2])f(a-1,b,c,d,e,1)+
    					[b>0](b-[last==3])f(a+1,b-1,c,d,e,2)+
    					[c>0](c-[last==4])f(a,b+1,c-1,d,e,3)+
    					[d>0](d-[last==5])f(a,b,c+1,d-1,e,4)+
    					[e>0)ef(a,b,c,d+1,e-1,5)
    
  • 其中[d>0]这种写法表示的是若d>0则为1,否则为0

  • 时间复杂度O(m^max(a_i)*max(a_i)),用记忆化搜索更快

  • 考虑第x辆车涂的颜色

  • 若从第a类(第1类)取出一种颜色给x染色,a类里就会少1种颜色

  • last表示的是前一辆车涂的是哪一类(a,b,c,d,e或者叫1,2,3,4,5)油漆

  • 之后是要涂第x+1辆车,因此是

  • x可以由a种选择上色(下式表示之前一辆车涂的油漆不来自于b类)

  • a * f(a-1,b,c,d,e,last=1)

  • 或者是(下式表示之前一辆车涂的油漆来自于b类,那么a类里目前有一种和前一辆车相同的颜色的油漆)

  • (a-1) * f(a-1,b,c,d,e,last=1)

  • 若从第b类(第2类)取出一种颜色给x染色,b类里就会少一种颜色,a类就会多一种颜色

  • (前一辆车的颜色不在c类里)

  • b * f(a+1,b-1,c,d,e,2)

  • (前一辆车涂的颜色在c类里)

  • (b-1) * f(a+1,b-1,c,d,e,2)

  • 说明(c-[last==4)是要删除上一辆车是第4种燃料,避免相邻两辆车颜色相同

  • const int N = 21,mo = 23333;
    int f(N)(N)(N)(N)(N)(6);
    
    int dp(int a,int b,int c,int d,int e,int last){
          
          
        // n==0,返回1,即空也表示1种方案
    	if(a|b|c|d|e==0){
          
          
    		return 1;
    	}
        // 如果之前算过答案,直接返回
    	if(f(a)(b)(c)(d)(e)(last)!=-1){
          
          
    		return f(a)(b)(c)(d)(e)(last);
    	}
    	long long ret = 0;
    	// 以下(last==2)等表达式的意思是:若这个表达式成立得到的是1,否则是0
    	if(a)
            // 若last==2,则表示上一辆车是从b里取出来的放到了a里,所以a中可以选择的油漆种类数要少1个
    		ret += dp(a-1,b,c,d,e,1) * (a - (last==2));
    	if(b)
    		ret += dp(a+1,b-1,c,d,e,2) * (b - (last==3));
    	if(c)
    		ret += dp(a,b+1,c-1,d,e,3) * (c - (last==4));
    	if(d)
    		ret += dp(a,b,c+1,d-1,e,4) * (d - (last==5));
    	if(e)
    		ret += dp(a,b,c,d+1,e-1,5) * e;
    	return f(a)(b)(c)(d)(e)(last) = ret % mo;
    }
    

n皇后

  • 一个nXn的棋盘,在棋盘上摆n个皇后,满足任意两个皇后不能在同一行、同一列或同一斜线上的方案有多少种
  • 二进制优化?
  • 考察搜索算法的剪枝水平,避免不必要的分叉口

解法1

  • 搜索

  • 枚举n个位置,判断是否满足条件,任两个皇后都不满足同行、同列、同斜线

解法2

  • 搜索+回溯

  • 枚举n个位置,判断是否满足条件,任两个皇后都不满足同行、同列、同斜线

  • 搜索每一个位置的时候都判断一下是否满足条件,不满足就不往下搜索了

解法3

  • 搜索+回溯+二进制优化

  • 我们用一个整数、布尔来表示0和1是不是太浪费

  • 一个二进制数字01010101,能表达的信息很多

  • 搜索过程中我们三个二进制数字记录三个信息

    • 列的分布情况a
    • 右上到左下方向的对角线情况b
    • 左上到右下方向的对角线情况c
  • 若二进制位置上是1,则表示该位置已经放过皇后

  • 在一行中,能放皇后的位置是(a|b|c)为0的位置

  • 对一个二进制找0,等价于取反后找1

  • 如何快速找到二进制里的一个1?

  • lowbit(x) = x & -x

  • 原数与补码做与运算

  • -x是补码(取反再加1)

  • 比如

  • lowbit(111010) = 000010, lowbit(11100) = 00100,

  • 也就是说lowbit(x)得到的是二进制位最低的1

  • 计算机补码表示的,所以-x表示先将每一位取反,然后加1得到的数,然后再与x取个并,自然就得到了最低位的1

  •  0 1 0 0 0,放了以后a=01000,b=10000,c=00100
     0 0 0 1 0,放了以后a=01010,b=00100,c=00011
     1 0 0 0 0,放了以后a=11010,b=01000,c=01001
     0 0 1 0 0,放了以后a=11110,b=10000,c=00110
     0 0 0 0 1,最后只能放这一个位置
    
  • // allOne是二进制全为1的排列
    int ans,allOne;
    // 搜索,用二进制优化
    // pos:其二进制上的某个位置的1表示当前所在行的的相应的位置(列)已经放过一个皇后
    // left:其二进制上的某个位置的1表示当前所在行的相应的位置(是由于右对角线上已有皇后)不能放置皇后
    // right:其二进制上的某个位置的1表示当前所在行的相应的位置(是由于左对角线上已经有皇后)不能放置皇后
    void dfs(int pos,int left,int right){
          
          
    	// 当且仅当每一列都放了一个皇后那么整个棋盘已经放了n个皇后,故要终止
    	if(pos == allOne) {
          
          
    		++ans;
    		return;
    	}
    	// t还有1存在,找出所有t中的1
    	// t代表可以放的集合对应的二进制数
        // allOne在这里是起截断作用的,放置越界
    	for(int t=allOne & (~(pos|left|right));t>0;){
          
          
    		// low bit,二进制中最右边的1
    		int p = t & -t;
    		dfs(pos|p,(left|p)<<1,(right|p)>>1);
    		// 消掉low bit
    		t ^= p;
    	}
    }
    
    int getAnswer(int n){
          
          
    	ans = 0;
        allOne = (1<<n)-1;
        dfs(0,0,0);
        return ans;
    }
    

解法4

  • 提交答案
  • OEIS,http://oeis.org

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/109173777