• Codeforces 285 E. Positions in Permutations


    ## [$>Codeforces space 285 E. Positions in Permutations<$](http://codeforces.com/problemset/problem/285/E)

    题目大意 : 定义一个长度为 (n) 的排列中第 (i) 个元素是好的,当且仅当 (i)在排列中的位置 (p_i) 满足 (|i - p_i| = 1), 给出 (n, k) 求长度为 (n) 的排列中恰好有 (k) 个元素是好的方案数

    $1 leq n leq 1000, 0 leq k leq n $

    解题思路 :

    观察发现,直接求出答案貌似十分困难,不妨先 (dp) 出至少 (k) 个元素是好的方案数,然后再通过 容斥/反演 来解决

    至少有 (k) 个元素是好的方案数等价于有 (k) 个位置存在匹配 (|i-p_i| = 1) ,剩余元素随便排列的方案数

    (f[i][j][0/1][0/1]) 表示前 (i) 个元素和前 (i) 个位置产生了 (j) 对匹配,第 (i) 个元素和第 (i) 个位置是否已经被匹配的方案数

    转移很显然,只需要枚举 (i-1) 上的元素和位置是否能和 (i) 上的元素和位置产生匹配即可

    (F(k)) 表示至少有 (k) 个是好的方案数,那么 (k = f[n][k] imes(n-k)!)

    考虑要求 (Ans_k =) 恰好有 (k) 个元素是好的方案数,那么有 (Ans_k = sum_{i = k}^{n} (-1)^{i-k} imes C_i^k imes F(i))

    这个和最基础的容斥又有所不一样,简单的考虑,对于 (i > k) 的每一个 (Ans_i) ,其中任意选 (k) 个好元素都能更新到 (Ans_k) ,所以方案数是 (C_i^k) ,在容斥进行补集转换的时候需要乘上这个系数

    往复杂了讲,这个涉及到容斥原理最本质的在集合上的运算,之前的 (i) 将表示所有大小为 (i) 的集合,那么每一个大小为 (i) 的集合在算到 (Ans_k) 的过程中都产生了 (C_i^k) 个不同的大小为 (k) 的集合

    当中的具体证明可以看近年集训队论文,或者我的好友 cly_none的博客

    考虑到前面算 (F(i)) 的时候,两个不同的匹配方案可能会对应到同一个排列,这里等价于每一个大小为 (i) 的集合,选 (k) 个好元素得到的新集合可能相同,所以两个重复计数恰好抵消了


    /*program by mangoyang*/
    #include<bits/stdc++.h>
    #define inf (0x7f7f7f7f)
    #define Max(a, b) ((a) > (b) ? (a) : (b))
    #define Min(a, b) ((a) < (b) ? (a) : (b))
    typedef long long ll;
    using namespace std;
    template <class T>
    inline void read(T &x){
        int f = 0, ch = 0; x = 0;
        for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
        for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
        if(f) x = -x;
    }
    #define N (2005)
    #define int ll
    const int Mod = 1000000007;
    int js[N], inv[N], f[N][N][2][2], g[N], n, k;
    inline int pow(int a, int b){
    	int ans = 1;
    	for(; b; b >>= 1, a = a * a % Mod)
    		if(b & 1) ans = ans * a % Mod;
    	return ans;
    }
    inline int C(int n, int i){ return js[n] * inv[i] % Mod * inv[n-i] % Mod; }
    main(){
    	read(n), read(k), f[0][0][0][0] = 1, js[0] = inv[0] = 1;
    	for(int i = 1; i <= n; i++){
    		js[i] = js[i-1] * i % Mod;
    		inv[i] = pow(js[i], Mod - 2);
    	}
    	for(int i = 1; i <= n; i++)
    		for(int j = 0; j <= n; j++){
    			(f[i][j][0][0] += f[i-1][j][0][0] + f[i-1][j][1][1]) %= Mod;
    			(f[i][j][0][0] += f[i-1][j][0][1] + f[i-1][j][1][0]) %= Mod;
    			if(j >= 1 && i > 1){ 
    				(f[i][j][1][0] += f[i-1][j-1][1][0] + f[i-1][j-1][0][0]) %= Mod;
    				(f[i][j][0][1] += f[i-1][j-1][0][1] + f[i-1][j-1][0][0]) %= Mod;
    			}
    			if(j >= 2 && i > 1) (f[i][j][1][1] += f[i-1][j-2][0][0]) %= Mod;
    		}
    	for(int i = 0; i <= n; i++){
    		int res = 0;
    		(res += f[n][i][0][0] + f[n][i][1][0]) %= Mod;
    		(res += f[n][i][0][1] + f[n][i][1][1]) %= Mod;
    		g[i] = res * js[n-i] % Mod;
    	}
    	int ans = 0;
    	for(int i = k; i <= n; i++){
    		int p = ((i - k) & 1) ? Mod - 1 : 1; 
    		(ans += C(i, k) * p % Mod * g[i] % Mod) %= Mod;
    	}
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    获取有关控件的坐标
    Android PopupWindow的使用和分析
    Android RecyclerView 使用完全解析 体验艺术般的控件
    TextView中显示价格并且中间直接有一个横线
    Android Studio
    移动开发】Android中三种超实用的滑屏方式汇总(ViewPager、ViewFlipper、ViewFlow)
    Android中visibility属性VISIBLE、INVISIBLE、GONE的区别
    理解Java的IO流 2015年8月6日 21:30:38
    算法导论学习随笔——第七章 快速排序
    oj 1031 random permutation
  • 原文地址:https://www.cnblogs.com/mangoyang/p/9368346.html
Copyright © 2020-2023  润新知