-
题意:
-
给定一个 排列 .
-
求所有的排列 ,满足
-
求
-
-
数据范围:
-
解题思路:
-
其实是很好做的.
-
考虑单独计算两个位置的贡献.
-
不妨现在求 这两个位置贡献.
-
可以分四种情况讨论,具体是:
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]
-
因为这四种情况下,其他数的排列方案是不一样的,因为标号不一样
-
不妨以第一个为例,很明显,其他数乱排的方案就是经典的错排公式.
-
一直以为这是一个很显然的公式,但真正自己去推一次后才发现其中的妙处.
-
错排公式:
-
错排公式是在 的标号基础上进行的,假如变成
-
那么对应方案数是:
-
不知道是否能意会?
-
那么还可以求的就是
-
对应方案数:
-
然后就去愉快的分类讨论吧:
-
-
参考代码:
#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);
}
-
题意:
-
求
-
其中 对矛盾关系,要求上式 中任何一对没有矛盾.
-
-
数据范围:
-
解题思路:
-
要求没有任何一对有矛盾,那很容易就想到容斥.
-
先考虑所有合法的,减去一对矛盾的,加上两对矛盾的,减去三对矛盾的.
-
不难发现前三种的方案数都异常的好求.
-
我们需要的实质上是第四种,即三对矛盾的方案.
-
按照题意构图,即如果 有矛盾关系,那么就在这两个点之间连一条无向边.
-
构完图后,图中三元环的个数即为三对矛盾的方案数.
-
这样构图显然是不行的,那么这里有一个很经典的套路就是:
-
度数大的连向度数小的,度数相同就随意连.
-
之后,枚举一个点 ,把其所有出边对应的点 标记,再从所有出边对应点 出发,继续看这些对应点有向连接的点 ,如果被标记过,就构成了一个三元环
-
-
很显然一个三元环只会由其中度数最大的点枚举 时枚举到,然后计数.
-
那么三元环肯定会不重不漏的算到。
-
关键是如何分析时间复杂度.
-
先考虑枚举到的点 ,满足 ,那么其 总是 级别(因为这是个有向图),而这样的点绝不超过 个,所以这一类点的复杂度是 .
-
然后考虑假设 ,则 ,因为 这条边只会被计算一次,且计算一次之后最多再带个 的贡献( 的度数),所以 条边,总复杂度依然是 .
-
是不是很神奇?
-
这种思想常常也叫作平衡规划.
-
一般是设定一个阈值,然后高于阈值的一种情况,低于阈值的另一种情况,使总时间复杂度最小,就是要求这个最恰到好处的阈值.
-
-
题意:
-
给定你一个长度为 的序列,每次选择一个二元组 ,把其中权值较大的计入贡献里,较小的则给标记.
-
求使贡献最小的方案数.
-
-
-
题解:
-
其实还是很好做的。好好说题解。
-
不难发现题意其实就是给你一些相同数构成的块,然后让你进行题意操作。
-
不妨假设现在计算的是第 个块,长度为 .
-
稍加思考可以发现,计算时一定是把第 个块先自行匹配了,然后剩下一个数,并且与后面的块进行匹配.
-
稍加思考还可以发现,剩下的这个数一定是与下一个块进行匹配。
-
然后一个我经常想不到思路就是,单独考虑一个块贡献,用以前已有的方案数去匹配.
-
就像这道题,你可以把它想象成当前枚举的这个块有一些会放到以前的空隙中.
-
那不妨就枚举一下,假设以前有 个空隙,当前要放 个数到前面去,那就是个挡板.
-
总之随意搞一搞就好了,计数题真的不便于说细节。
-
总结一下就是,对付这种题,要先关注性质(当前一个块的最后一个数只可能与相邻块进行匹配),然后注意计数套路(考虑当前块*以前的贡献)
-
-
参考代码:
#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);
}
-
题意
-
一个 的棋盘,每一步可从 走到 或 或 .
-
求从 到 的方案数.
-
-
-
题解
-
不妨假设走到第 行的 格,然后一直向右走到达 ,可以列
-
因为
-
所以上式可以继续化简:
-
令 ,则
-
这时,根据杨辉三角,我们可以得到
-
所以可把上式写成:
-
后面这个组合数只有 项,暴力即可.
-
模数不一定为质数,所以质因子相减即可.
-
参考代码
#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);
}
}