0%

1月6日 Educational DP Contest 题目链接:https://atcoder.jp/contests/dp

大部分水题就不讲了,记录几道有趣的题目。

J - Sushi

思路: 期望要倒着推!!!

设 $f_{i,j,k}$ 为有 $i$ 个 $3$ 个的寿司碟子,有 $j$ 个 $2$ 个的寿司碟子,有 $k$ 个 $1$ 个的寿司碟子的期望,那么就有 $n-i-j-k$ 个空着的碟子。然后倒着推期望!!!!

这样我们就得到了递推的关系。因为转移的顺序问题,我们不能简单地按照 $i, j, k$ 的顺序 DP。但发现转移是从寿司总数少的状态到寿司总数多到状态,因此我们要按照寿司的总数进行 DP。总时间复杂度 $O(n^3)$。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 305
#define INF 0x3f3f3f3f
#define p 1000000007
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, sum, a[MAXN], num[4];
LD f[MAXN][MAXN][MAXN];

int main()
{
scanf("%d", &n);
for(rint i=1; i<=n; ++i)
{
scanf("%d", &a[i]);
num[a[i]]++; sum+=a[i];
}
for(rint x=1; x<=sum; ++x)
{
for(rint i=0; i<=x/3; ++i)
{
for(rint j=0; j<=(x-i*3)/2; ++j)
{
int k=x-i*3-j*2;
if(i+j+k>n) continue;
//printf("%d %d %d!!!\n", i, j, k);
f[i][j][k]=n;
if(i>0) f[i][j][k]+=i*f[i-1][j+1][k];
if(j>0) f[i][j][k]+=j*f[i][j-1][k+1];
if(k>0) f[i][j][k]+=k*f[i][j][k-1];
f[i][j][k]/=(i+j+k);
//printf("%d %d %d %Lf!!!\n", i, j, k, f[i][j][k]);
}
}
}
printf("%.10Lf\n", f[num[3]][num[2]][num[1]]);
return 0;
}

O - Matching

思路: 一个简单的容斥计数,感觉和 DP 关系不大。

枚举女生集合 $S$,计算所有男生和集合中女生配对的方案数,记为 $f[S]$。既然题目要求一一配对的方案,那么如果 $n-|S|$ 为奇数,那么答案就应该减去 $f[S]$,否则就加上 $f[S]$。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 21
#define INF 0x3f3f3f3f
#define p 1000000007
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, ans, s[MAXN];

int main()
{
scanf("%d", &n);
for(rint i=0; i<n; ++i)
for(rint j=0, x; j<n; ++j)
{
scanf("%d", &x);
s[i]|=(x<<j);
}
for(rint sta=0; sta<(1<<n); ++sta)
{
int siz=0, val=1;
for(rint i=0; i<n; ++i)
siz+=((sta>>i)&1);
for(rint i=0; i<n; ++i)
{
int temp=s[i]&sta, num=0;
for(rint j=0; j<n; ++j)
num+=((temp>>j)&1);
val=1LL*val*num%p;
}
if((n-siz)&1) ans=(ans+p-val)%p;
else ans=(ans+val)%p;
}
printf("%d\n", ans);
return 0;
}

U - Grouping

思路: 一个简单的子集 DP。

枚举所有集合 $S$,记 $f[S]$ 为选取 $S$ 集合的最大答案。先算出把 $S$ 集合作为一整组的分数作为 $f[S]$ 的初值,然后枚举 $S$ 的子集 $T$,得到转移方程 $f[S]=max(f[S], f[T]+f[S xor T])$,直接转移就行。总复杂度 $O(3^nn^2)$ 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 20
#define INF 0x3f3f3f3f
#define p 1000000007
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, mp[MAXN][MAXN];
LL f[1 << 17];

int main()
{
scanf("%d", &n);
for(rint i = 0; i < n; ++i)
for(rint j = 0; j < n; ++j)
scanf("%d", &mp[i][j]);

for(rint sta = 0; sta < (1 << n); ++sta)
{
for(rint i = 0; i < n; ++i)
if((sta >> i) & 1)
for(rint j = i + 1; j < n; ++j)
if((sta >> j)&1) f[sta] += mp[i][j];
for(rint i = sta; i; i = (i-1) & sta)
{
int j = sta ^ i;
f[sta] = max(f[sta], f[i] + f[j]);
//printf("%d %d %d %lld %lld!!\n", sta, i, j, f[i], f[j]);
}
}
printf("%lld\n", f[(1 << n) - 1]);
}

V - Subtree

思路: 一个简单的树形 DP 加换根操作,但是有一个很有趣的细节。

设 $f_i$ 为以 $i$ 为根的子树的方案数,那么就有 DP 方程:$f_i=\prod (f_{to}+1)$。考虑换根操作,设 $val_i$ 为节点 $i$ 的父亲所在子树的贡献,那么节点 $i$ 的答案就是 $(val_i+1)*f[i]$。$val$ 的求法一般有两种,第一种是通过式子:$val_{to}=(val_x+1)*f_x/f_{to}$ 得到。这种方法看似很简单,但是其中有除法,对于模意义下的除法表面上看可以用扩展欧几里得求逆元实现。但是,有一些数是没有逆元的,也就是说对于任意除数的除法我们不能够简单地实现。

这时候我们就要考虑第二种求 $val$ 的方法,那就是给子树一个固定的顺序,然后求出子树的前缀和后缀贡献,从而推出 $val_{to}$,虽然这种方法更为复杂,但可以很好地避免除法。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 100005
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, m, cnt, head[MAXN], f[MAXN], ans[MAXN];
vector<int> vec[MAXN], l[MAXN], r[MAXN];

void dfs1(int x, int fa)
{
f[x] = 1;
for(rint i = 0; i < vec[x].size(); ++i)
{
int to = vec[x][i];
if(to == fa) continue;
dfs1(to, x);
f[x]= 1LL * f[x] * (f[to]+1) % m;
}
}

void dfs2(int x, int fa, int val)
{
ans[x] = 1LL * f[x] * (val + 1) % m;
l[x].resize(vec[x].size());
r[x].resize(vec[x].size());
for(rint i = 0; i < vec[x].size(); ++i)
{
if(i == 0) l[x][i] = 1;
else
{
if(vec[x][i - 1] == fa) l[x][i] = l[x][i - 1];
else l[x][i] = 1LL * l[x][i - 1] * (f[vec[x][i - 1]] + 1) % m;
}
}
for(rint i=vec[x].size()-1; i >= 0; --i)
{
if(i == vec[x].size() - 1) r[x][i] = 1;
else
{
if(vec[x][i + 1] == fa) r[x][i] = r[x][i + 1];
else r[x][i] = 1LL * r[x][i + 1] * (f[vec[x][i + 1]] + 1) % m;
}
}
//printf("%d %d %d!!\n", x, fa, val);
for(rint i = 0; i < vec[x].size(); ++i)
{
int to = vec[x][i];
if(to == fa) continue;
//printf("%d %d %d!!!\n", to, l[x][i], r[x][i]);
dfs2(to, x, 1LL * (val + 1) * l[x][i] % m * r[x][i] % m);
}
}

int main()
{
scanf("%d%d", &n, &m);
for(rint i = 1, x, y; i < n; ++i)
{
scanf("%d%d", &x, &y);
vec[x].push_back(y);
vec[y].push_back(x);
}
dfs1(1, 0);
dfs2(1, 0, 0);
for(rint i = 1; i <= n; ++i) printf("%d\n", ans[i]);
return 0;
}

W - Intervals

思路:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 200005
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
#define pii pair<int, int>
#define ls (root<<1)
#define rs (root<<1|1)
#define mid ((l+r)>>1)
using namespace std;

int n, m;
LL t[MAXN*4], tag[MAXN*4];
vector<pii> vec[MAXN];

void up(int root)
{
t[root]=max(t[ls], t[rs])+tag[root];
}

void update(int root, int l, int r, int x, int y, LL k)
{
//printf("%d %d %d %d!!!\n", root, l, r, k);
if(l>y || r<x) return;
if(l>=x && r<=y)
{
t[root]+=k, tag[root]+=k;
return ;
}
update(ls, l, mid, x, y, k);
update(rs, mid+1, r, x, y, k);
up(root);
}

int main()
{
scanf("%d%d", &n, &m);
for(rint i=1; i<=m; ++i)
{
int l, r, a;
scanf("%d%d%d", &l, &r, &a);
vec[r].push_back(make_pair(l, a));
}
for(rint i=1; i<=n; ++i)
{
update(1, 1, n, i, i, t[1]);
for(rint j=0; j<vec[i].size(); ++j)
{
pii temp=vec[i][j];
update(1, 1, n, temp.first, i, temp.second);
}
}
printf("%lld\n", max(0LL, t[1]));
}

问题描述

设 $T$ 为一棵有根树,我们做如下的定义:

• 设 $a$ 和 $b$ 为 $T$ 中的两个不同节点。如果 $a$ 是 $b$ 的祖先,那么称「$a$ 比 $b$ 不知道高明到哪里去了」。

• 设 $a$ 和 $b$ 为 $T$ 中的两个不同节点。如果 $a$ 与 $b$ 在树上的距离不超过某个给定常数 $x$,那么称「$a$ 与 $b$ 谈笑风生」。

给定一棵 $n$ 个节点的有根树 $T$,节点的编号为 $1∼n$,根节点为 $1$ 号节点。你需要回答 $q$ 个询问,询问给定两个整数 $p $ 和 $k$,问有多少个有序三元组 $(a, b, c)$ 满足:

• $a$,$b$ 和 $c$ 为 $T$ 中三个不同的点,且 $a$ 为 $p$ 号节点;

• $a$ 和 $b$ 都比 $c$ 不知道高明到哪里去了;

• $a$ 和 $b$ 谈笑风生。这里谈笑风生中的常数为给定的 $k$。

输入

输入文件的第一行含有两个正整数 $n$ 和 $q$,分别代表有根树的点数与询问的个数。

接下来 $n−1$ 行,每行描述一条树上的边。每行含有两个整数 $u$ 和 $v$,代表在节点 $u$ 和 $v$ 之间有一条边。

接下来 $q$ 行,每行描述一个操作。第 $i$ 行含有两个整数,分别表示第 $i$ 个询问的 $p$ 和 $k$。

输出

输出 $q$ 行,每行对应一个询问,代表询问的答案。

思路

特别经典的一道题,大部分树上的数据结构都能够在这题使用。除了下面讲到都主席树和线段树合并的做法之外,还可以用树上启发式合并,长链剖分等做法去做。

我们将答案分成两个部分。第一部分是 $b$ 是 $a$ 的祖先,答案就是:

第二部分是 $a$ 是 $b$ 的祖先,答案就是:

于是我们不难想到以 $dep[x]$ 为关键字,$siz[x]-1$ 为权值建立权值线段树。对于每一个询问的答案就是在对应线段树上询问区间 $[dep[x]+1, dep[x]+k]$。实现时就可以用主席树或线段树合并来节省空间。

代码

主席树:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 300005
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, q, cnt, id, tot, head[MAXN], dep[MAXN], siz[MAXN], in[MAXN], out[MAXN], ver[MAXN], root[MAXN];

struct Edge {int next, to;} edge[MAXN*2];
struct Node {int ls, rs; LL val;} t[MAXN*40];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}

void dfs(int x, int fa)
{
siz[x]=1; in[x]=++id; ver[id]=x;
for(rint i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
dep[to]=dep[x]+1;
dfs(to, x);
siz[x]+=siz[to];
}
out[x]=id;
}

void update(int &rt1, int rt2, int l, int r, int x, int k)
{
rt1=++tot; t[rt1]=t[rt2]; t[rt1].val+=k;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) update(t[rt1].ls, t[rt2].ls, l, mid, x, k);
else update(t[rt1].rs, t[rt2].rs, mid+1, r, x, k);
}

LL query(int rt, int l, int r, int x, int y)
{
if(l>y || r<x) return 0;
if(l>=x && r<=y) return t[rt].val;
int mid=(l+r)>>1;
return query(t[rt].ls, l, mid, x, y)+query(t[rt].rs, mid+1, r, x, y);
}

int main()
{
scanf("%d%d", &n, &q);
for(rint i=1, x, y; i<n; ++i)
{
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
dfs(1, 0);
for(rint i=1; i<=n; ++i)
update(root[i], root[i-1], 1, n, dep[ver[i]], siz[ver[i]]-1);
while(q--)
{
int x, k;
scanf("%d%d", &x, &k);
LL a=1LL*min(dep[x], k)*(siz[x]-1);
LL b=query(root[in[x]], 1, n, dep[x], dep[x]+k);
LL c=query(root[out[x]], 1, n, dep[x], dep[x]+k);
printf("%lld\n", a+c-b);
}
return 0;
}

线段树合并:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 300005
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, q, cnt, tot, head[MAXN], dep[MAXN], siz[MAXN], root[MAXN];
LL ans[MAXN];

struct Edge {int next, to;} edge[MAXN*2];
struct Node {int ls, rs; LL val;} t[MAXN*40];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}

void update(int &rt, int l, int r, int x, int k)
{
if(!rt) rt=++tot; t[rt].val+=k;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) update(t[rt].ls, l, mid, x, k);
else update(t[rt].rs, mid+1, r, x, k);
}

int merge(int x, int y, int l, int r)
{
if((!x) || (!y)) return x+y;
int mid=(l+r)>>1, rt=++tot;
t[rt].val=t[x].val+t[y].val;
t[rt].ls=merge(t[x].ls, t[y].ls, l, mid);
t[rt].rs=merge(t[x].rs, t[y].rs, mid+1, r);
return rt;
}

LL query(int rt, int l, int r, int x, int y)
{
if(l>y || r<x || !rt) return 0;
if(l>=x && r<=y) return t[rt].val;
int mid=(l+r)>>1;
return query(t[rt].ls, l, mid, x, y)+query(t[rt].rs, mid+1, r, x, y);
}

void dfs(int x, int fa)
{
siz[x]=1; dep[x]=dep[fa]+1;
for(rint i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
dfs(to, x);
siz[x]+=siz[to];
root[x]=merge(root[x], root[to], 1, n);
}
update(root[x], 1, n, dep[x], siz[x]-1);
}

int main()
{
scanf("%d%d", &n, &q);
for(rint i=1, x, y; i<n; ++i)
{
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
dfs(1, 0);
for(rint i=1; i<=q; ++i)
{
int x, k;
scanf("%d%d", &x, &k);
LL a=1LL*min(dep[x]-1, k)*(siz[x]-1);
LL b=query(root[x], 1, n, dep[x]+1, dep[x]+k);
printf("%lld\n", a+b);
}
return 0;
}

问题描述

给定一棵 $n$ 个结点的树,你从点 $x$ 出发,每次等概率随机选择一条与所在点相邻的边走过去。

有 $q$ 次询问,每次询问给定一个集合 $S$,求如果从 $x$ 出发一直随机游走,直到点集 $S$ 中所有点都至少经过一次的话,期望游走几步。

特别地,点 $x$(即起点)视为一开始就被经过了一次。

答案对 $998244353$ 取模。

输入

第一行三个正整数 $n,q,x$。

接下来 $n-1$ 行,每行两个正整数 $(u,v)$ 描述一条树边。

接下来 $q$ 行,每行第一个数 $k$ 表示集合大小,接下来 $k$ 个互不相同的数表示集合 $S$。

输出

输出 $q$ 行,每行一个非负整数表示答案。

思路

首先要知道 $min-max$ 容斥,然后根据 $min-max$ 容斥的常规套路就可以把原问题转换为求 $f(root)$,表示从 $root$ 开始到集合 $T$ 中元素的最小期望步数。

考虑求 $f(x)$。路径上概率期望的套路一般是根据期望的递推关系,对于每一个点列一个方程,最后解方程组得出期望。这题也可以按照这种方法,得出方程: 用 $O(n^3)$ 解这个方程组,然后套 $min-max$ 容斥的复杂度是 $O(n^32^n+n^2q)$,但这个复杂度是满足不了要求的。我们可以考虑优化如何解这个方程组。下面将式子进行化简: 那么就可以将上式写成:

考虑: 将 $\sum f[to]$ 代入原先式子,得到:

得到:

然后我们设属于集合 $T$ 的点 $A_x=0, B_x=0$,这样我们就可以从下往上递推出 $A_x, B_x$,然后因为 $root$ 没有父亲,所以我们要求的 $f[root]$ 就是 $B_{root}$。

先预处理出所有集合 $T$ 的 $f[root]$,然后对于每一个询问,枚举子集,用 $min-max$ 容斥得出答案。这样总复杂度就是 $O(n*2^n+qn^2)$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN
#define p 998244353
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, q, x, cnt, head[20], deg[20], a[20], b[20], f[1<<19], siz[1<<19];

struct Edge {int next, to;} edge[40];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
deg[to]++;
head[from]=cnt;
}

int ksm(int x, int y)
{
int sum=1;
while(y)
{
if(y&1) sum=(1LL*x*sum)%p;
x=(1LL*x*x)%p; y>>=1;
}
return sum;
}

void dp(int sta, int x, int fa)
{

int suma=0, sumb=0;
if((sta>>(x-1))&1) {a[x]=b[x]=0; return;}
for(rint i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
dp(sta, to, x);
suma=(suma+a[to])%p;
sumb=(sumb+b[to])%p;
}
int temp=ksm((deg[x]+p-suma)%p, p-2);
a[x]=1LL*temp;
b[x]=1LL*(sumb+deg[x])%p*temp%p;
}

int main()
{
scanf("%d%d%d", &n, &q, &x);
for(rint i=1, x, y; i<n; ++i)
{
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
for(rint sta=0; sta<(1<<n); ++sta)
{
dp(sta, x, 0);
f[sta]=b[x];
siz[sta]=siz[sta>>1]+(sta&1);
}
while(q--)
{
int sta=0, ans=0, k;
scanf("%d", &k);
for(rint i=1, x; i<=k; ++i)
{
scanf("%d", &x);
sta|=(1<<(x-1));
}
for(rint i=sta; i; i=(i-1)&sta)
{
if(siz[i]&1) ans=(ans+f[i])%p;
else ans=(ans+p-f[i])%p;
}
printf("%d\n", ans);
}
return 0;
}

反演的原理

反演一般是解决这样一个问题:有两个数列 $\lbrace f_n\rbrace,\lbrace g_n\rbrace$,满足关系: 题目要你求 $f_n$,但你却知道数列 $\lbrace g_n\rbrace$ 怎么求。这时就可以利用反演,用 $g_0, g_1, \cdots, g_n$ 求出 $f_n$,即:

我们就考虑,如果 $\lbrace f_n\rbrace,\lbrace g_n\rbrace$ 能够反演,系数 $a,b$ 应该满足什么关系。

我们假设它们能够反演,将上面两个式子合并:

如果能够反演,那无论 $\lbrace g_n\rbrace$ 为多少,上式都应成立,因此反演的充要条件就是:

满足这个条件的式子有很多,而下面会提到其中的一部分。

普通容斥(子集反演)

形式一: 形式二:

1.BZOJ4455 小星星

代码链接

2.CF451 Devu and Flowers

代码链接

二项式反演

1.BZOJ3622 已经没有什么好害怕的了

代码链接

2.BZOJ1879 Bill的挑战

代码链接

斯特林反演

莫比乌斯反演

形式一:

形式二:

1.BZOJ2301 Problem b

代码链接

2.BZOJ2820 YY的GCD

代码链接

最值反演

两种普通形式:

期望意义下:

1.LOJ2542 随机游走

代码链接

12 月 29 日 AtCoder Grand Contest 030 比赛链接:https://atcoder.jp/contests/agc030

C - Coloring Torus

思路: 如果当 $k$ 为 $4$ 的倍数时,有一种简单粗暴的方法就是奇数列填 $1 \sim k/2$ 偶数列填 $k/2+1 \sim k$,这显然是满足要求的,但因为这种方法每个数它左右两边都一样,可扩展性不强。我们可以在这个的基础上稍加变化,就是把第 $i$ 列向后移 $i$ 个,形式化的说就是:

对于奇数行 $col_{i,j} = (i+j)\mod k/2$ 对于偶数行 $col_{i,j} = (i+j)\mod k/2 +k/2$

这样构造出来的矩阵就有很好性质,对于每个数不光它上下两个数是它的 $+1$ 和 $-1$,而且它左右两个数是它的 $k/2+1$ 和 $k/2-1$。因此很自然的可以想到如果 $k$ 不是 $4$ 的倍数时,设 $n$ 为 $\lceil \frac{k}{4} \rceil * 4$,然后将多出来的 $n-k$ 个数都减去 $n/2$。因为上面提到的性质,减去 $n/2$ 后可以看成将它上下两个数与左右两个数交换,这样依旧可以保证满足题目要求

代码链接

D - Inversion Sum

思路: 先把计数转化为求期望,我们设 $f_{i,j}$ 为 $A_i>A_j$ 的概率。可以发现每一次操作会改变 $O(n)$ 个 $f_{i,j}$ 的值,因此可以直接在对应的值上面修改即可。

代码链接

E - Less than 3

思路: 如果在 $01$ 之间画上蓝线,在 $10$ 之间画上蓝线,可以发现我们的每一次操作就是将一根线向左或向右移。且红线和蓝线之间的距离不超过 $3$,红线蓝线交替出现,串的左右两段有无数条线。而我们要做的就是把第一个串的线与第二个串对应起来,因此就枚举第二个串的第一根线对应于第一个串的那一根线,然后这种情况的答案就是确定的且可以 $O(n)$ 计算出来。

代码链接

F - Permutation and Minimum

思路: 有一个脑洞很大的 $DP$,从大到小考虑每一个数,设 $f_{i,j,k}$ 为考虑数 $i$ 时,有 $j$ 没有配对的确定的数,有 $k$ 个没有配对的 $-1$ 且。接下来考虑转移:

如果数 $i$ 确定且和它一对的另一个数也确定,那么它不会对答案产生影响,有 $f_{i,j,k}=f_{i+1,j,k}$ 如果数 $i$ 确定且和它一对的另一个数是 $-1$,那么它可以不配对,有 $f_{i,j,k}=f_{i+1,j-1,k}$;这个它可以和一个 $-1$ 配对,有 $f_{i+1,j,k+1}$ 如果数 $i$ 不确定,那么它可以不配对,有 $f_{i,j,k}=f_{i+1,j,k+1}$;它也可以和一个 $-1$ 配对,有 $f_{i,j,k}=f_{i+1,j,k+1}$;它还可以和一个确定的数配对,有 $f_{i,j,k}=(j+1)*f_{i+1,j+1,k}$

我们想这个 DP 的系数是怎么来的,如果两个 $-1$ 配对我们可以先把所有的 $-1$ 看成没有区别的,系数就为 $1$,最后乘上这样配对数的阶乘;如果一个 $-1$ 和一个确定的数配对,那么它可以有 $j+1$ 种选择,系数就是 $j+1$。

代码链接