chy2003 大爷出的题。
本来这套题一眼看上去对我这种蒟蒻挺友好的,但是,反正每次只要是我自我感觉蛮良好的时候,最后成绩都挺惨的。
期望得分 240 ,实际得分 175 。
T1 night
题意
有一些 m ( m ≤ 20 ) m(m\le 20) m(m≤20) 位二进制数 a i a_i ai 和一个 b b b ,假如 b ∗ 2 k b*2^k b∗2k 在二进制下是 1 的位 a i a_i ai 都是 1,那么 a i a_i ai 可以减去 b b b 。
有多组询问,每次问有多少个 a i a_i ai 能够通过一些减 b ∗ 2 k b*2^k b∗2k 的操作得到 x x x 。
思路
首先确定这题是一个状压 DP。 d p [ i ] dp[i] dp[i] 表示 i i i 能由多少个数操作得到。
那么现在问题在于去重,因为有的 i i i 可能可以由 j j j 通过多次减操作得到,但是 j j j 只能对 i i i 有 1 的贡献。
先讲我的
O
(
m
2
∗
2
m
)
O(m^2*2^m)
O(m2∗2m) 的做法,非常 naive。记
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] ,在
d
p
[
i
]
dp[i]
dp[i] 的基础上多记一位
j
j
j 表示这个
i
i
i 是由
i
+
b
∗
2
j
i+b*2^j
i+b∗2j 减去
b
∗
2
j
b*2^j
b∗2j 得到的。然后卡常能卡到 2 秒 真是可惜 。
然后是 O ( m ∗ 2 m ) O(m*2^m) O(m∗2m) 做法。只需要记 d p [ i ] dp[i] dp[i] ,然后改变循环的顺序。先枚举 b ∗ 2 k b*2^k b∗2k 中的 k k k ,也就是强行规定先做 k k k 小的操作。那么操作的顺序问题显然就迎刃而解了。
代码
#include<bits/stdc++.h>
using namespace std;
const int M = 21, S = 1<<20;
int T, n, m, c, b, q;
int f[S];
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
int main()
{
for (read(T); T--; ){
read(n); read(m);
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++ i){
int x; read(x);
f[x]++;
}
b = 1;
for (read(c); c--; ){
int x; read(x);
b |= 1<<x;
}
for (; b < (1<<m); b <<= 1)
for (int i = (1<<m)-1; i >= 1; -- i)
if ((i&b) == b)
f[i^b] += f[i];
for (read(q); q--; ){
int x; read(x);
printf("%d\n", f[x]);
}
}
return 0;
}
T2 dawn
题意
有一棵树,求 k k k 条路径的排列,使这 k k k 条路径的交为 x x x 到 y y y 的简单路径。
注意:假如选两条路径,那么 ( 1 , 2 ) , ( 1 , 3 ) (1,2),(1,3) (1,2),(1,3) 和 ( 1 , 3 ) , ( 1 , 2 ) (1,3),(1,2) (1,3),(1,2) 是两种不同的方案,但是 ( 1 , 2 ) , ( 1 , 3 ) (1,2),(1,3) (1,2),(1,3) 和 ( 2 , 1 ) , ( 3 , 1 ) (2,1),(3,1) (2,1),(3,1) 是两种相同的方案。
思路
相当于在两棵子树各选 k k k 个点,两两对应形成路径,答案为 ( s i z [ x ] ∗ s i z [ y ] ) k (siz[x]*siz[y])^k (siz[x]∗siz[y])k , s i z [ x ] siz[x] siz[x] 为 x x x 的子树大小。
然后发现假如 k k k 个点都在 x x x 的同一个儿子的子树里,那么路径的交会比 ( x , y ) (x,y) (x,y) 要长,那就把这些答案减掉。重复减的容斥一下。
最后只需要对每个点记录子树大小的 k k k 次方,所有儿子子树大小的 k k k 次方和,然后还要记录子树外的那棵子树的信息,用来特判 x , y x,y x,y 某一个是另一个的祖先。
注意
检查取模!!!
检查取模!!!
检查取模!!!
不检查 CSP 爆零活该。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = N<<1, E = 20, K = 100 + 5, mod = 998244353;
int T, n, q;
int h[N], ecnt, nxt[M], v[M];
int f[N][E], dep[N];
int siz[N][K], sum[N][K], tsiz[N][K];
template<class T>inline void add(T &x, T y){x += y; if (x >= mod) x -= mod;}
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
void _add(int x, int y){
nxt[++ecnt] = h[x]; v[ecnt] = y;
h[x] = ecnt;
}
void dfs(int u, int fa){
dep[u] = dep[fa]+1;
f[u][0] = fa;
for (int i = 1; i < E; ++ i)
f[u][i] = f[f[u][i-1]][i-1];
siz[u][1] = 1;
for (int i = 1; i < K; ++ i)
sum[u][i] = 0;
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa){
dfs(v[i], u);
add(siz[u][1], siz[v[i]][1]); // 没有取模!!!
for (int j = 1; j < K; ++ j)
add(sum[u][j], siz[v[i]][j]); // 没有取模!!!
}
for (int i = 2; i < K; ++ i)
siz[u][i] = 1LL * siz[u][i-1] * siz[u][1] % mod;
if (u == 1) tsiz[u][1] = 0;
else tsiz[u][1] = n-siz[u][1];
for (int i = 2; i < K; ++ i)
tsiz[u][i] = 1LL * tsiz[u][i-1] * tsiz[u][1] % mod;
}
int lca(int x, int y){
if (dep[x] < dep[y]) swap(x, y);
for (int i = E-1; i >= 0; -- i)
if (dep[f[x][i]] >= dep[y])
x = f[x][i];
if (x == y) return x;
for (int i = E-1; i >= 0; -- i)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int lca_son(int x, int y){
if (dep[x] < dep[y]) swap(x, y);
for (int i = E-1; i >= 0; -- i)
if (dep[f[x][i]] > dep[y])
x = f[x][i];
return x;
}
int solve1(int x, int y, int k){
LL ans = 0;
add(ans, 1LL * siz[x][k] * siz[y][k] % mod);
add(ans, mod - 1LL * sum[x][k] * siz[y][k] % mod);
add(ans, mod - 1LL * sum[y][k] * siz[x][k] % mod);
add(ans, 1LL * sum[x][k] * sum[y][k] % mod);
return ans;
}
int solve2(int x, int y, int k){
if (dep[x] > dep[y]) swap(x, y);
LL ans = 0;
int z = lca_son(x, y);
int tmp = ((sum[x][k]-siz[z][k]+tsiz[x][k])%mod+mod)%mod;
add(ans, 1LL * siz[y][k] * tsiz[z][k] % mod);
add(ans, mod - 1LL * tmp * siz[y][k] % mod);
add(ans, mod - 1LL * sum[y][k] * tsiz[z][k] % mod);
add(ans, 1LL * tmp * sum[y][k] % mod);
return ans;
}
int main()
{
for (read(T); T--; ){
read(n); read(q);
ecnt = 1; memset(h, 0, sizeof(h));
for (int i = 1; i < n; ++ i){
int x, y;
read(x); read(y);
_add(x, y); _add(y, x);
}
dfs(1, 0);
for (int i = 1; i <= q; ++ i){
int k, x, y, z;
read(k); read(x); read(y);
z = lca(x, y);
if (z == x || z == y) printf("%d\n", solve2(x, y, k));
else printf("%d\n", solve1(x, y, k));
}
}
return 0;
}
T3 light
题意
有 n n n 个格子,标号 1 1 1 到 n n n ,可以花费 i i i 的代价得到 i i i 号格子。有一些格子,在你花钱得到他的时候同时也会得到 [ i − k , i + k ] [i-k,i+k] [i−k,i+k] 的所有格子。
现在给定 k k k 和这些特殊格子,求最小的代价得到所有 n n n 个格子。
思路
先写出 DP 方程:
d
p
[
i
]
=
m
i
n
(
d
p
[
j
]
+
w
)
dp[i]=min(dp[j]+w)
dp[i]=min(dp[j]+w) ,其中
w
w
w 号格子是特殊格子,并且满足
j
≥
w
−
k
−
1
,
i
≤
w
+
k
j \ge w-k-1,i\le w+k
j≥w−k−1,i≤w+k。
贪心一波,对于某个 i i i ,显然会选 w + k ≤ i w+k\le i w+k≤i 的最小的 w w w 。然后用单调队列维护一个区间最小的 d p [ j ] dp[j] dp[j] 就完事了。
然后讲一下我的错误思路:对于每一个位置
i
i
i ,假如他是特殊位置,就拿他和
m
i
n
(
d
p
[
j
]
)
,
(
i
−
k
−
1
≤
j
<
i
)
min(dp[j]),(i-k-1\le j < i)
min(dp[j]),(i−k−1≤j<i) 来更新
d
p
[
i
+
k
]
dp[i+k]
dp[i+k] 。显然这个思路漏了
d
p
[
j
]
(
i
≤
j
<
i
+
k
)
dp[j](i \le j< i+k)
dp[j](i≤j<i+k)的
j
j
j ,也就是没有考虑两个选取的特殊位置相互覆盖的情况。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
const int N = 5e6 + 10;
const LL inf = 1e18 + 7;
int T, n, k;
char s[N];
namespace Solver3
{
LL f[N];
int que[N], h, t;
int que1[N], h1, t1;
void main(){
f[0] = 0;
que[h = t = 1] = 0;
h1 = 1; t1 = 0;
for (int i = 1; i <= n; ++ i){
f[i] = f[i-1] + i;
if (s[i] == '1') que1[++t1] = i;
while (h1 <= t1 && que1[h1]+k < i) h1++;
if (h1 <= t1){
while (h <= t && que[h] < que1[h1]-k-1) h++;
if (h <= t) f[i] = min(f[i], f[que[h]] + que1[h1]);
}
while (h <= t && f[que[t]] >= f[i]) t--;
que[++t] = i;
}
printf("%lld\n", f[n]);
}
}
signed main()
{
for (scanf("%lld", &T); T--; ){
scanf("%lld%lld%s", &n, &k, s+1);
Solver3::main();
}
return 0;
}