几道计数题

版权声明:蒟蒻写的文章,能看就行了,同时欢迎大佬们指点错误 https://blog.csdn.net/Algor_pro_king_John/article/details/84391615

5921. 【NOIP2018模拟10.22】种花

  • 题意:

    • 给定一个 n n 排列 p p .

    • 所有的排列 a a ,满足 i [ 1 , n ] , a i p i \forall i\in[1,n],a_i\neq p_i

    • a i < j , a i > a j ( j i ) ( a i a j ) \sum_{a}\sum_{i<j,a_i>a_j}(j-i)(a_i-a_j)

  • 数据范围:

    • n 5000 n\le 5000
  • 解题思路:

    • 其实是很好做的.

    • 考虑单独计算两个位置的贡献.

    • 不妨现在求 i , j i,j 这两个位置贡献.

    • 可以分四种情况讨论,具体是:

      • b[j] == a[i], b[i] == a[j]
      • b[j] == a[i], b[i] != a[j]
      • b[j] != a[i], b[i] == a[j]
      • b[j] != a[i], b[i] != a[j]
    • 因为这四种情况下,其他数的排列方案是不一样的,因为标号不一样

    • 不妨以第一个为例,很明显,其他数乱排的方案就是经典的错排公式.

    • 一直以为这是一个很显然的公式,但真正自己去推一次后才发现其中的妙处.

    • 错排公式:
      f [ i ] = ( i 1 ) ( f [ i 1 ] + f [ i 2 ] ) f[i] = (i - 1) * (f[i - 1] + f[i - 2])

    • 错排公式是在 123... n , 123... n 123...n,123...n 标号基础上进行的,假如变成 123... n 1 , n 123...n-1,n 123... n 1 , n + 1 123...n-1,n+1

    • 那么对应方案数是:
      g [ i ] = f [ i 1 ] + ( i 1 ) g [ i 1 ] g[i] = f[i - 1] + (i - 1) * g[i - 1]

    • 不知道是否能意会?

    • 那么还可以求的就是 123.. n 2 , n 1 , n 123..n-2,n-1,n 123... n 2 , n + 1 , n + 2 123...n-2,n+1,n+2

    • 对应方案数:
      h [ n ] = 2 g [ i 1 ] + ( i 2 ) h [ i 1 ] h[n] = 2 * g[i - 1] + (i - 2) * h[i - 1]

    • 然后就去愉快的分类讨论吧:

  • 参考代码:

#include <cstdio>

#define ll long long
#define F(i, a, b) for (ll i = a; i <= b; i ++)
#define add(a, b) ((a) = (a + b) % Mo)
#define max(a, b) ((a) > (b) ? (a) : (b))

const ll N = 5e3 + 10, Mo = 1e9 + 9;

ll n, Ans, S, tot;
ll a[N], f[N], g[N], h[N];

int main() {
	freopen("derangement.in", "r", stdin);
	freopen("derangement.out", "w", stdout);

	scanf("%d", &n), f[2] = 1, g[1] = 1;
	F(i, 1, n) scanf("%d", &a[i]);
	F(i, 3, n) f[i] = ((i - 1) * (f[i - 1] + f[i - 2])) % Mo;
	F(i, 2, n) g[i] = (f[i - 1] + (i - 1) * g[i - 1]) % Mo;
	F(i, 2, n) h[i] = (2 * g[i - 1] + (i - 2) * h[i - 1]) % Mo;
	F(j, 2, n) {
		// 1.1 b[j] == a[i] && b[i] == a[j]
		F(i, 1, j - 1)
			if (a[j] > a[i])
				add(Ans, (j - i) * (a[j] - a[i]) * f[n - 2]);
		
		// 1.2 b[j] == a[i] && b[i] != a[j]
		F(i, 1, j - 1) {
			if (a[i] == n) continue;
			S = ((n + a[i] + 1) * (n - a[i]) / 2) % Mo, tot = n - a[i];
			if (a[i] < a[j]) S -= a[j], tot --;
			if (S) add(Ans, g[n - 2] * (j - i) % Mo * (S - tot * a[i]));
		}

		// 1.3 b[j] != a[i] && b[i] == a[j]
		F(i, 1, j - 1) {
			if (a[j] == 1) continue;
			S = (a[j] * (a[j] - 1) / 2) % Mo, tot = a[j] - 1;
			if (a[i] < a[j]) S -= a[i], tot --;
			add(Ans, g[n - 2] * (j - i) % Mo * (tot * a[j] - S));
		}

		// 1.4 b[j] != a[i] && b[i] != a[j]
		F(i, 1, j - 1) {
			S = n * (n+1) * (2*n+1) / 6 - ((n + 1) * n >> 1) >> 1;
			S -= a[j] * (a[j] - 1) >> 1; // b[i] == a[j]
			S -= a[i] * (a[i] - 1) >> 1; // b[i] == a[i]
			S -= (n - a[j] + 1) * (n - a[j]) >> 1; // b[j] == a[j]
			S -= (n - a[i] + 1) * (n - a[i]) >> 1; // b[j] == a[i]
			S += max(a[i], a[j]) - (a[i] + a[j] - max(a[i], a[j])); // 容斥一下
			add(Ans, S * (j - i) % Mo * h[n - 2]);
		}
	}
	printf("%d\n", (Ans + Mo) % Mo);
}

5803. 【2018.8.12省选模拟】girls

  • 题意:

    • i j k A i + B j + C k \sum_{i}\sum_{j}\sum_{k}A·i+B·j+C·k

    • 其中 m m 对矛盾关系,要求上式 i , j , k i,j,k 中任何一对没有矛盾.

  • 数据范围:

    • n , m min ( 2 1 0 5 , n ( n 1 ) 2 ) n,m\le \min(2*10^5,\frac{n*(n-1)}{2})
  • 解题思路:

    • 要求没有任何一对有矛盾,那很容易就想到容斥.

    • 先考虑所有合法的,减去一对矛盾的,加上两对矛盾的,减去三对矛盾的.

    • 不难发现前三种的方案数都异常的好求.

    • 我们需要的实质上是第四种,即三对矛盾的方案.

    • 按照题意构图,即如果 x , y x,y 有矛盾关系,那么就在这两个点之间连一条无向边.

    • 构完图后,图中三元环的个数即为三对矛盾的方案数.

    • 这样构图显然是不行的,那么这里有一个很经典的套路就是:

      • 度数大的连向度数小的,度数相同就随意连.

      • 之后,枚举一个点 x x ,把其所有出边对应的点 y y 标记,再从所有出边对应点 y y 出发,继续看这些对应点有向连接的点 z z ,如果被标记过,就构成了一个三元环 ( x , y , z ) (x,y,z)

    • 很显然一个三元环只会由其中度数最大的点枚举 x x 时枚举到,然后计数.

    • 那么三元环肯定会不重不漏的算到。

    • 关键是如何分析时间复杂度.

    • 先考虑枚举到的点 x x ,满足 deg x m \deg_x\ge \sqrt{m} ,那么其 deg y \sum\deg_y 总是 O ( m ) O(m) 级别(因为这是个有向图),而这样的点绝不超过 O ( m ) O(\sqrt{m}) 个,所以这一类点的复杂度是 O ( m m ) O(m\sqrt{m}) .

    • 然后考虑假设 deg x &lt; m \deg_x\lt \sqrt{m} ,则 deg y &lt; m \deg_y\lt \sqrt{m} ,因为 ( x , y ) (x,y) 这条边只会被计算一次,且计算一次之后最多再带个 m \sqrt{m} 的贡献( y y 的度数),所以 m m 条边,总复杂度依然是 O ( m m ) O(m\sqrt{m}) .

    • 是不是很神奇?

    • 这种思想常常也叫作平衡规划.

    • 一般是设定一个阈值,然后高于阈值的一种情况,低于阈值的另一种情况,使总时间复杂度最小,就是要求这个最恰到好处的阈值.

  • jzojz5968. 电竞选手

  • 题意:

    • 给定你一个长度为 n n 的序列,每次选择一个二元组 ( x , y ) x &lt; y , v i s [ x ] = v i s [ y ] = 0 (x,y)|x&lt;y,vis[x]=vis[y]=0 ,把其中权值较大的计入贡献里,较小的则给标记.

    • 求使贡献最小的方案数.

    • n 1 0 5 n\le 10^5

  • 题解:

    • 其实还是很好做的。好好说题解。

    • 不难发现题意其实就是给你一些相同数构成的块,然后让你进行题意操作。

    • 不妨假设现在计算的是第 i i 个块,长度为 L L .

    • 稍加思考可以发现,计算时一定是把第 i i 个块先自行匹配了,然后剩下一个数,并且与后面的块进行匹配.

    • 稍加思考还可以发现,剩下的这个数一定是与下一个块进行匹配。

    • 然后一个我经常想不到思路就是,单独考虑一个块贡献,用以前已有的方案数去匹配.

    • 就像这道题,你可以把它想象成当前枚举的这个块有一些会放到以前的空隙中.

    • 那不妨就枚举一下,假设以前有 L L 个空隙,当前要放 i i 个数到前面去,那就是个挡板.

    • 总之随意搞一搞就好了,计数题真的不便于说细节。

    • 总结一下就是,对付这种题,要先关注性质(当前一个块的最后一个数只可能与相邻块进行匹配),然后注意计数套路(考虑当前块*以前的贡献)

  • 参考代码:

#include<cstdio>
#include<algorithm>
#define ll long long
#define F(i,a,b) for (ll i=a;i<=b;i++)

using namespace std;
const ll N=1e5+10, Mo=1e9+7;

ll n,cnt,Ans,L,sum;
ll a[N],d[N],f[N],jc[N],ny[N];

ll C(ll x,ll y) { return jc[x]*ny[y]%Mo*ny[x-y]%Mo; }

ll ksm(ll x,ll y) {
	ll ans=1;
	for (; y; y>>=1, x=x*x%Mo)
		if (y&1) ans=ans*x%Mo;
	return ans;
}

int main() {
	scanf("%d",&n), f[1]=f[2]=jc[0]=ny[0]=1;
	F(i,1,n) scanf("%d",&a[i]);
	F(i,1,n) jc[i] = jc[i-1]*i%Mo, ny[i] = ksm(jc[i],Mo-2);
	sort(a+1,a+n+1),a[0]=a[1]-1;
	F(i,1,n)
		if(a[i]^a[i-1]) d[++cnt]=1; else ++d[cnt];
	F(i,3,n)
		f[i]=f[i-1]*(i*(i-1)/2%Mo)%Mo;
	L=d[1],Ans=f[d[1]];
	F(i,2,cnt) {
		Ans=Ans*f[d[i]]%Mo,sum=d[i];
		F(j,2,d[i])
			sum=(sum+C(j+L-2,L-1)*(d[i]-j+1))%Mo;
		Ans=Ans*sum%Mo, L+=d[i];
	}
	printf("%d\n",Ans);
}
  • 51nod1362搬箱子

  • 题意

    • 一个 n m n*m 的棋盘,每一步可从 ( x , y ) (x,y) 走到 ( x , y + 1 ) (x,y+1) ( x + 1 , y ) (x+1,y) ( x + 1 , y + 1 ) (x+1,y+1) .

    • 求从 ( 0 , 0 ) (0,0) ( n , m ) (n,m) 的方案数.

    • n &lt; = 800 m , p &lt; = 1 0 9 n&lt;=800,m,p&lt;=10^9

  • 题解

  • 不妨假设走到第 n n 行的 i i 格,然后一直向右走到达 ( n , m ) (n,m) ,可以列

    A n s = i = 0 m j = 0 m i n ( n , i ) ( n + i j n j ) ( i i j ) Ans = \sum_{i=0}^m\sum_{j=0}^{min(n,i)}\binom{n+i-j}{n-j}\binom{i}{i-j}
    = i = 0 m j = 0 m i n ( n , i ) ( n + i j ) ! i ! ( n j ) ! i ! ( i j ) ! j ! = \sum_{i=0}^m\sum_{j=0}^{min(n,i)}\frac{(n+i-j)!i!}{(n-j)!i!(i-j)!j!}
    = i = 0 m j = 0 m i n ( n , i ) ( n + i j ) ! ( n j ) ! ( i j ) ! j ! = \sum_{i=0}^m\sum_{j=0}^{min(n,i)}\frac{(n+i-j)!}{(n-j)!(i-j)!j!}

  • 因为 ( a + b + c ) ! a ! b ! c ! = ( a + b ) ! ( a + b + c ) ! a ! b ! c ! ( a + b ) ! = ( a + b b ) ( a + b + c c ) \frac{(a+b+c)!}{a!b!c!}=\frac{(a+b)!(a+b+c)!}{a!b!c!(a+b)!}=\binom{a+b}{b}\binom{a+b+c}{c}

  • 所以上式可以继续化简:

  • a = n j , b = j , c = i j a=n-j, b = j, c = i - j ,则
    A n s = i = 0 m j = 0 m i n ( n , i ) ( n j ) ( n + i j n ) Ans = \sum_{i=0}^m\sum_{j=0}^{min(n,i)}\binom{n}{j}\binom{n+i-j}{n}
    = j = 0 m i n ( n , m ) i = j m ( n j ) ( n + i j n ) = \sum_{j=0}^{min(n,m)}\sum_{i=j}^m\binom{n}{j}\binom{n+i-j}{n}
    = j = 0 m i n ( n , m ) ( n j ) i = j m ( n + i j n ) = \sum_{j=0}^{min(n,m)}\binom{n}{j}\sum_{i=j}^m\binom{n+i-j}{n}
    = j = 0 m i n ( n , m ) ( n j ) i = 0 m j ( n + i n ) = \sum_{j=0}^{min(n,m)}\binom{n}{j}\sum_{i=0}^{m-j}\binom{n+i}{n}

  • 这时,根据杨辉三角,我们可以得到
    i = 0 n ( i k ) = ( n + 1 k + 1 ) \sum_{i=0}^{n}\binom{i}{k}=\binom{n+1}{k+1}

  • 所以可把上式写成:

A n s = j = 0 m i n ( n , m ) ( n j ) i = 0 m j ( n + i n ) Ans = \sum_{j=0}^{min(n,m)}\binom{n}{j}\sum_{i=0}^{m-j}\binom{n+i}{n}
= j = 0 m i n ( n , m ) ( n j ) i = 0 m j ( n + i n ) = \sum_{j=0}^{min(n,m)}\binom{n}{j}\sum_{i=0}^{m-j}\binom{n+i}{n}
= j = 0 m i n ( n , m ) ( n j ) ( n + m j + 1 n + 1 ) = \sum_{j=0}^{min(n,m)}\binom{n}{j}\binom{n+m-j+1}{n+1}

  • 后面这个组合数只有 n n 项,暴力即可.

  • 模数不一定为质数,所以质因子相减即可.

  • 参考代码

#include <cstdio>
#include <cstring>
#include <iostream>

#define ll long long
#define F(i, a, b) for (ll i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)

using namespace std;

const ll N = 800, T = 150;

ll n, m, p, t, Ans, Sum;
ll d[N + 10], bz[N + 10], cnt[T], G[4][T], flag[N + 10];

void Doit(ll x, ll w, ll add) { t = w;
	F(k, 1, d[0]) {
		if (d[k] * d[k] > t) break;
		while (t % d[k] == 0)
			t /= d[k], G[x][k] += add;
	}
	if (t > N) Ans = (Ans * t) % p; else
	if (t > 1) G[x][flag[t]] += add;
}

ll ksm(ll x, ll y) {
	ll ans = 1;
	for (; y ; y >>= 1, x = (x * x) % p)
		if (y & 1)
			ans = (ans * x) % p;
	return ans;
}

int main() {
	F(i, 2, N) {
		if (!bz[i]) d[++ d[0]] = i, flag[i] = d[0];
		F(j, 1, d[0]) {
			if (d[j] * i > N) break;
			bz[d[j] * i] = 1;
			if (i % d[j] == 0) break;
		}
	}
	while (~scanf("%d%d%d", &n, &m, &p)) {
		mem(G, 0), Sum = 0;
		F(j, 2, n) Doit(2, j, 1);
		Doit(3, n + 1, 1);
		F(j, 0, min(n, m)) {
			mem(cnt, 0), mem(G[0], 0), Ans = 1;
			F(i, m - j + 1, m - j + 1 + n) Doit(0, i, 1);
			Doit(1, j, 1);
			if (j) Doit(2, n - j + 1, - 1);
			F(k, 1, T - 1) {
				F(i, 1, 3) G[0][k] -= G[i][k];
				Ans = (Ans * ksm(d[k], G[0][k])) % p;
			}
			Sum = (Sum + Ans) % p;
		}
		printf("%d\n", Sum);
	}
}

猜你喜欢

转载自blog.csdn.net/Algor_pro_king_John/article/details/84391615