-
题意:
-
给定一个 n n n排列 p p p.
-
求所有的排列 a a a,满足 ∀ i ∈ [ 1 , n ] , a i ≠ p i \forall i\in[1,n],a_i\neq p_i ∀i∈[1,n],ai̸=pi
-
求 ∑ 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) a∑i<j,ai>aj∑(j−i)(ai−aj)
-
-
数据范围:
- n ≤ 5000 n\le 5000 n≤5000
-
解题思路:
-
其实是很好做的.
-
考虑单独计算两个位置的贡献.
-
不妨现在求 i , j 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]) f[i]=(i−1)∗(f[i−1]+f[i−2]) -
错排公式是在 123... n , 123... n 123...n,123...n 123...n,123...n的标号基础上进行的,假如变成 123... n − 1 , n 123...n-1,n 123...n−1,n 123... n − 1 , n + 1 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] 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 123... n − 2 , n + 1 , n + 2 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] 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);
}
-
题意:
-
求 ∑ i ∑ j ∑ k A ⋅ i + B ⋅ j + C ⋅ k \sum_{i}\sum_{j}\sum_{k}A·i+B·j+C·k i∑j∑k∑A⋅i+B⋅j+C⋅k
-
其中 m m m对矛盾关系,要求上式 i , j , k 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}) n,m≤min(2∗105,2n∗(n−1))
-
解题思路:
-
要求没有任何一对有矛盾,那很容易就想到容斥.
-
先考虑所有合法的,减去一对矛盾的,加上两对矛盾的,减去三对矛盾的.
-
不难发现前三种的方案数都异常的好求.
-
我们需要的实质上是第四种,即三对矛盾的方案.
-
按照题意构图,即如果 x , y x,y x,y有矛盾关系,那么就在这两个点之间连一条无向边.
-
构完图后,图中三元环的个数即为三对矛盾的方案数.
-
这样构图显然是不行的,那么这里有一个很经典的套路就是:
-
度数大的连向度数小的,度数相同就随意连.
-
之后,枚举一个点 x x x,把其所有出边对应的点 y y y标记,再从所有出边对应点 y y y出发,继续看这些对应点有向连接的点 z z z,如果被标记过,就构成了一个三元环 ( x , y , z ) (x,y,z) (x,y,z)
-
-
很显然一个三元环只会由其中度数最大的点枚举 x x x时枚举到,然后计数.
-
那么三元环肯定会不重不漏的算到。
-
关键是如何分析时间复杂度.
-
先考虑枚举到的点 x x x,满足 deg x ≥ m \deg_x\ge \sqrt{m} degx≥m,那么其 ∑ deg y \sum\deg_y ∑degy总是 O ( m ) O(m) O(m)级别(因为这是个有向图),而这样的点绝不超过 O ( m ) O(\sqrt{m}) O(m)个,所以这一类点的复杂度是 O ( m m ) O(m\sqrt{m}) O(mm).
-
然后考虑假设 deg x < m \deg_x\lt \sqrt{m} degx<m,则 deg y < m \deg_y\lt \sqrt{m} degy<m,因为 ( x , y ) (x,y) (x,y)这条边只会被计算一次,且计算一次之后最多再带个 m \sqrt{m} m的贡献( y y y的度数),所以 m m m条边,总复杂度依然是 O ( m m ) O(m\sqrt{m}) O(mm).
-
是不是很神奇?
-
这种思想常常也叫作平衡规划.
-
一般是设定一个阈值,然后高于阈值的一种情况,低于阈值的另一种情况,使总时间复杂度最小,就是要求这个最恰到好处的阈值.
-
-
题意:
-
给定你一个长度为 n n n的序列,每次选择一个二元组 ( x , y ) ∣ x < y , v i s [ x ] = v i s [ y ] = 0 (x,y)|x<y,vis[x]=vis[y]=0 (x,y)∣x<y,vis[x]=vis[y]=0,把其中权值较大的计入贡献里,较小的则给标记.
-
求使贡献最小的方案数.
-
n ≤ 1 0 5 n\le 10^5 n≤105
-
-
题解:
-
其实还是很好做的。好好说题解。
-
不难发现题意其实就是给你一些相同数构成的块,然后让你进行题意操作。
-
不妨假设现在计算的是第 i i i个块,长度为 L L L.
-
稍加思考可以发现,计算时一定是把第 i i i个块先自行匹配了,然后剩下一个数,并且与后面的块进行匹配.
-
稍加思考还可以发现,剩下的这个数一定是与下一个块进行匹配。
-
然后一个我经常想不到思路就是,单独考虑一个块贡献,用以前已有的方案数去匹配.
-
就像这道题,你可以把它想象成当前枚举的这个块有一些会放到以前的空隙中.
-
那不妨就枚举一下,假设以前有 L L L个空隙,当前要放 i 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);
}
-
题意
-
一个 n ∗ m n*m n∗m的棋盘,每一步可从 ( x , y ) (x,y) (x,y)走到 ( x , y + 1 ) (x,y+1) (x,y+1)或 ( x + 1 , y ) (x+1,y) (x+1,y)或 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1).
-
求从 ( 0 , 0 ) (0,0) (0,0)到 ( n , m ) (n,m) (n,m)的方案数.
-
n < = 800 , m , p < = 1 0 9 n<=800,m,p<=10^9 n<=800,m,p<=109
-
-
题解
-
不妨假设走到第 n n n行的 i i i格,然后一直向右走到达 ( n , m ) (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} Ans=i=0∑mj=0∑min(n,i)(n−jn+i−j)(i−ji)
= ∑ 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∑mj=0∑min(n,i)(n−j)!i!(i−j)!j!(n+i−j)!i!
= ∑ 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!} =i=0∑mj=0∑min(n,i)(n−j)!(i−j)!j!(n+i−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!b!c!(a+b+c)!=a!b!c!(a+b)!(a+b)!(a+b+c)!=(ba+b)(ca+b+c)
-
所以上式可以继续化简:
-
令 a = n − j , b = j , c = i − j 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} Ans=i=0∑mj=0∑min(n,i)(jn)(nn+i−j)
= ∑ 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∑min(n,m)i=j∑m(jn)(nn+i−j)
= ∑ 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∑min(n,m)(jn)i=j∑m(nn+i−j)
= ∑ 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∑min(n,m)(jn)i=0∑m−j(nn+i) -
这时,根据杨辉三角,我们可以得到
∑ i = 0 n ( i k ) = ( n + 1 k + 1 ) \sum_{i=0}^{n}\binom{i}{k}=\binom{n+1}{k+1} i=0∑n(ki)=(k+1n+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}
Ans=j=0∑min(n,m)(jn)i=0∑m−j(nn+i)
=
∑
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∑min(n,m)(jn)i=0∑m−j(nn+i)
=
∑
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}
=j=0∑min(n,m)(jn)(n+1n+m−j+1)
-
后面这个组合数只有 n 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);
}
}