set(并查集 + 时光倒流),树形dp + 数据分治,dp
# A. 向日葵代沟
直接用 维护每个断点。
每次新加一个断点时直接二分前后断点位置计算贡献即可。
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
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 1e5 + 10;
int n, m, ans;
int a[N];
set <int> s;
bool vis[N];
signed main(){
n = read(), m = read();
s.insert(0), s.insert(n);
vis[n] = 1;
while(m--){
int x = read();
if(vis[x]) {puts("0"); continue;}
vis[x] = 1;
auto l = s.lower_bound(x), r = s.lower_bound(x);
l--;
ans = (x - (*l)) * ((*r) - x);
write(ans), puts("");
s.insert(x);
}
return 0;
}
# B. 树上的僵尸
这道题就复杂多了,考虑 。
设 表示两个端点都在 子树内且经过 的合法路径数, 表示一端在子树内另一端在子树外的合法路径数。
通过容斥思想,我们用总数减去不合法的来计算答案,不合法的即为所有路径都经过同一个点的情况。
直接枚举点肯定会超时,我们枚举所有路径经过深度最浅的点,那么不合法方案数就变为了总数减去不是最浅的方案数。
总数为 ,不是最浅的方案数为 ,答案为:
再来看 和 如何计算:
-
:暴力计算,转移的时候就是求编号小于当前点且距离当前点 以内的点的个数,用树状数组维护前缀和即可,复杂度 。
-
: 为 子树内任意两点配对个数减去所有儿子自己配对个数, 为 。
-
链:因为是一条链,且要经过 ,相当于有一个端点必须在 , 为 , 为 向上 步内 的和减去 到 的 级祖先内两两配对的个数。
的情况答案就是 。
然后这题就解决了。
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// #define int long long
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 1e6 + 10;
const int mod = 998244353;
int n, m, k, ans;
vector <int> G[N], vec;
inline int add(int x) {return x >= mod ? x - mod : x;}
inline int sub(int x) {return x < 0 ? x + mod : x;}
inline int mul(int x, int y) {return 1ll * x * y % mod;}
inline int qpow(int a, int b){
int res = 1;
while(b){
if(b & 1) res = mul(res, a);
a = mul(a, a), b >>= 1;
}
return res;
}
int dep[N], sum[N];
int f[N], g[N];
int c[N];
inline void add(int x, int y){
for(; x <= k; x += x & (-x)) c[x] = add(c[x] + y);
}
inline int qry(int x){
int res = 0;
for(; x; x -= x & (-x)) res = add(res + c[x]);
return res;
}
inline void dfs1(int x, int fa){
if(!x) return;
if((dep[x] = dep[fa] + 1) > k) return;
vec.pb(x);
for(auto y : G[x]) if(y != fa) dfs1(y, x);
}
inline void dfs2(int x, int fa){
dep[x] = 0, dfs1(fa, x);
for(int i = 1; i <= k; ++i) sum[i] = c[i] = 0;
for(auto v : vec) sum[dep[v]]++;
vec.clear();
for(int i = 1; i <= k; ++i) sum[i] += sum[i - 1];
f[x] = 1, g[x] = sum[k];
for(auto y : G[x]){
if(y == fa) continue;
dfs1(y, x);
for(auto v : vec){
f[x] = add(f[x] + 1 + qry(k - dep[v]));
g[x] = add(g[x] + sum[k - dep[v]]);
}
for(auto v : vec) add(dep[v], 1);
vec.clear();
}
for(auto y : G[x]) if(y != fa) dfs2(y, x);
}
inline void solve_pow(){
dfs2(1, 0);
}
int siz[N];
inline int Sum(int n) {return 1ll * n * (n + 1) / 2 % mod;}
inline void dfs(int x, int fa){
siz[x] = 1;
for(auto y : G[x]){
if(y == fa) continue;
dfs(y, x), siz[x] += siz[y];
f[x] = sub(f[x] - Sum(siz[y]));
}
f[x] = add(f[x] + Sum(siz[x])), g[x] = mul(siz[x], n - siz[x]);
}
inline void solve_all(){
dfs(1, 0);
}
inline void solve_chain(){
for(int i = 1; i <= n; ++i){
f[i] = min(k + 1, n - i + 1), sum[i] = add(sum[i - 1] + f[i]);
int j = max(i - k, 1);
g[i] = sub(sub(sum[i - 1] - sum[j - 1]) - Sum(i - j));
}
}
signed main(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
n = read(), m = read(), k = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
G[u].pb(v), G[v].pb(u);
}
if(!k) return write(sub(qpow(n, m) - n)), puts(""), 0;
if(n <= 1000) solve_pow();
else if(k == n - 1) solve_all();
else solve_chain();
int res1 = 0, res2 = 0;
for(int i = 1; i <= n; ++i){
res1 = add(res1 + f[i]);
res2 = add(res2 + sub(qpow(f[i] + g[i], m) - qpow(g[i], m)));
}
write(sub(qpow(res1, m) - res2)), puts("");
return 0;
}
# C. 豌豆射手
考场上一直再调假 ,心态没了/kk
先想一个比较暴力的做法,枚举豌豆射手的排列,然后尽可能压缩空间的放置。
假设占用的长度为 ,考虑把后面 格的空位置插到前面 个位置中,可以为空,方案数为 。
但是枚举排列显然是过不了的,考虑如何通过 直接计算出占用 的草坪时的方案数。
设 表示计算了前 个豌豆射手,形成了 段(每段内无法插入新的豌豆射手,段与段之间还有空位置),使用长度为 草坪时的方案数。
先把豌豆射手按照 从小到大排序,方便转移。
转移分为 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
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
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 45;
const int S = 1600;
const int M = 1e5 + 10;
const int mod = 1e9 + 7;
int n, d, ans;
int r[N], f[N][N][S + 10];
inline int add(int x) {return x >= mod ? x - mod : x;}
inline void add(int &x, int y) {x = add(x + y);}
inline int sub(int x) {return x < 0 ? x + mod : x;}
inline int mul(int x, int y) {return 1ll * x * y % mod;}
int fac[M], ifac[M];
inline int qpow(int a, int b){
int res = 1;
for(; b; a = mul(a, a), b >>= 1)
if(b & 1) res = mul(res, a);
return res;
}
inline void init(int n){
fac[0] = 1;
for(int i = 1; i <= n; ++i) fac[i] = mul(fac[i - 1], i);
ifac[n] = qpow(fac[n], mod - 2);
for(int i = n - 1; i >= 0; --i) ifac[i] = mul(ifac[i + 1], i + 1);
}
inline int C(int n, int m){
return mul(fac[n], mul(ifac[m], ifac[n - m]));
}
inline void solve(){
f[0][0][0] = 1;
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= i; ++j)
for(int k = 0; k <= S; ++k){
int t = r[i + 1];
if(j >= 1) add(f[i + 1][j][k + t], mul(f[i][j][k], 2 * j));
if(j >= 2) add(f[i + 1][j - 1][k + 2 * t - 1], mul(f[i][j][k], j * (j - 1)));
add(f[i + 1][j + 1][k + 1], f[i][j][k]);
}
}
signed main(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
n = read(), d = read(), init(M - 10);
for(int i = 1; i <= n; ++i) r[i] = read();
sort(r + 1, r + 1 + n);
solve();
for(int i = 0; i <= min(d, S); ++i)
ans = add(ans + mul(C(d - i + n, n), f[n][1][i]));
write(ans), puts("");
return 0;
}