T1 tree
首先,谢谢出题人 /wx
要求求出把一棵树分成若干个联通的部分的方案数。
首先我们很容易想到,分出来的 “子树” 的大小 d d d 是满足 d ∣ n d | n d∣n 的。也就是说我们只需要判断每一个 d d d 是否可行的就好了,其中 d d d 的个数是等于 σ 0 ( n ) < n \sigma_0(n) < \sqrt{n} σ0(n)<n 的。
判断可行是挺简单的,你只用写一个 d f s dfs dfs 模拟一下分树的过程就好了,所以 c h e c k check check 就是 O ( n ) O(n) O(n) 的。那么总的复杂度就是小于 O ( n n ) O(n\sqrt{n}) O(nn) 的。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 2002000
#define MAXM MAXN << 2
#define MOD 998244353
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int n = 0;
int cnt = 0;
int d[MAXN] = { 0 };
int tot = 0;
int first[MAXN] = { 0 };
int nxt[MAXM] = { 0 };
int to[MAXM] = { 0 };
inline void add(int x, int y){
nxt[++tot] = first[x];
first[x] = tot; to[tot] = y;
}
int sz[MAXN] = { 0 };
void prework(int x, int fa){
sz[x]++;
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
prework(y, x);
sz[x] += sz[y];
}
}
int check(int x, int fa, int w){
int siz = 1;
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
if(sz[y] >= w){ // 大于先继续向下
int f = check(y, x, w);
if(f == -1) return -1;
siz += f; // 加上下面的贡献
}
else siz += sz[y];
if(siz > w) return -1; // 全部木大
}
if(siz == w) return 0; // 等于直接剪掉 不算贡献
return siz;
}
int main(){
// freopen("a.in", "r", stdin);
n = in; int ans = 0;
for(int i = 1; i < n; i++){
int x = in, y = in;
add(x, y), add(y, x);
} prework(1, 0);
for(int i = 1; i * i <= n; i++)
if(n % i == 0) d[++cnt] = i;
for(int i = cnt; i >= 1; i--)
if(n / d[i] != d[i]) d[++cnt] = n / d[i];
// for(int i = 1; i <= cnt; i++) cout << d[i] << ' '; puts("");
for(int i = 1; i <= cnt; i++)
if(check(1, 0, d[i]) != -1) ans = (ans + 1) % MOD;
cout << ans << '\n';
return 0;
}
但是这样的写法似乎会 T T T 掉 2 , 3 2,3 2,3 个点,所以我们还要继续看看有没有什么性质。
我们考虑找到 s i z e size size 是 d d d 的倍数的关键点(因为我们刚才模拟拆树的时候就是找的这些点拆)。我们考虑找到这些点的数量上限,我们把这些关键点都提取出来,组成的新树中的叶子节点的 s i z e size size 至少是 d d d,而且非叶子节点的 s i z e size size 肯定比它所有儿子的 s i z e size size 和要大,且至少大 d d d。所以显然关键点的个数上限就是 n d \frac nd dn。
如果我们恰好能找到 n d \frac nd dn 个关键点,就说明每个点恰好是一颗大小为 d d d 的子树的根,并且这些子树互不包含。也就是说,如果恰有 n d \frac nd dn 个关键点,题目的要求就能成立。所以我们可以通过这个来重新写一下 c h e c k ( ) check() check() 函数就能 A A A 了。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1001000
#define MAXM MAXN << 2
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int n = 0;
int d[MAXN] = { 0 };
int tot = 0;
int first[MAXN] = { 0 };
int nxt[MAXM] = { 0 };
int to[MAXM] = { 0 };
inline void add(int x, int y){
nxt[++tot] = first[x];
first[x] = tot; to[tot] = y;
}
int siz[MAXN] = { 0 };
int cnt[MAXN] = { 0 };
void prework(int x, int fa){
siz[x]++;
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
prework(y, x);
siz[x] += siz[y];
}
cnt[siz[x]]++;
}
bool check(int d){
int num = 0;
for(int i = d; i <= n; i += d)
num += cnt[i];
return num == (n / d);
}
int main(){
n = in; int ans = 0;
for(int i = 1; i < n; i++){
int x = in, y = in;
add(x, y), add(y, x);
} prework(1, 0);
for(int i = 1; i <= n; i++)
if(n % i == 0) ans += check(i);
cout << ans << '\n';
return 0;
}
T2 seq
30 p t s 30pts 30pts 就是一个简单的 d p dp dp。我们令 f i , j f_{i, j} fi,j 表示前 i i i 个数字,最后一位是 j j j 的最小值,那么有:
f i , j = min j = a i A { min k = a i − 1 A { f i − 1 , k + c ∣ j − k ∣ + ( a i − j ) 2 } } f_{i, j} = \min_{j = a_i}^A \{\min_{k = a_{i - 1}}^A\{ f_{i - 1, k} + c|j - k| + (a_i - j)^2 \}\} fi,j=j=aiminA{k=ai−1minA{fi−1,k+c∣j−k∣+(ai−j)2}}
初始化就是 f 1 , j = ( j − a 1 ) 2 j ∈ [ a i , A ] f_{1, j} = (j - a_1)^2 \quad j \in[a_i, A] f1,j=(j−a1)2j∈[ai,A],其中 A A A 是 a i a_i ai 的值域。这样做的时间复杂度是 O ( n A 2 ) O(nA^2) O(nA2) 的。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1010
#define INFI 1 << 30
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int n = 0; int c = 0;
int f[MAXN][MAXN] = { 0 };
int main(){
n = in; c = in; int A = 0, ans = INFI;
for(int i = 1; i <= n; i++) a[i] = in, A = 101;
memset(f, 0x3f, sizeof(f));
for(int j = a[1]; j <= A; j++) f[1][j] = (j - a[1]) * (j - a[1]);
for(int i = 2; i <= n; i++)
for(int j = a[i]; j <= A; j++)
for(int k = a[i - 1]; k <= A; k++)
f[i][j] = min(f[i][j], f[i - 1][k] + c * abs(j - k) + (j - a[i]) * (j - a[i]));
for(int i = 1; i <= A; i++) ans = min(ans, f[n][i]);
cout << ans << '\n';
return 0;
}
我们考虑优化这个做法。我们把上面的式子稍微变一下形:
f i , j = min j = a i A { min k = a i − 1 A { f i , k + c ∣ j − k ∣ } + ( a i − j ) 2 } f_{i, j} = \min_{j = a_i}^A \{ \min_{k = a_{i - 1}}^A \{ f_{i, k} + c|j - k| \} + (a_i - j)^2 \} fi,j=j=aiminA{k=ai−1minA{fi,k+c∣j−k∣}+(ai−j)2}
那么我们如果令 s i , j = min k = a i − 1 A { f i , k + c ∣ j − k ∣ } s_{i, j} = \min\limits_{k = a_{i - 1}}^A \{ f_{i, k} + c|j - k| \} si,j=k=ai−1minA{fi,k+c∣j−k∣},并且把 s i , j s_{i, j} si,j 预处理出来,那么我们就可以少枚举一次 A A A,方程就可以写成这样:
f i , j = min j = a i A { s i , j + ( a i − j ) 2 } f_{i, j} = \min_{j = a_i}^A \{ s_{i, j} + (a_i - j)^2 \} fi,j=j=aiminA{si,j+(ai−j)2}
现在我们考虑如何预处理出 s i , j s_{i, j} si,j。我们考虑如果把绝对值拆开就很好转移了,也就是我们的 s i , j s_{i, j} si,j 如果等于 min k = a i − 1 A { f i , k + c ( j − k ) } \min\limits_{k = a_{i - 1}}^A \{ f_{i, k} + c(j - k) \} k=ai−1minA{fi,k+c(j−k)} 这样就好处理多了,因为我们可以:
min { f i , k + c ( j − k ) } = min { min { f i , k + c ( j − 1 − k ) } + c , f i − 1 , j } \min \{ f_{i, k} + c(j - k) \} = \min \{ \min \{f_{i, k} + c(j - 1 - k) \} + c, f_{i - 1, j} \} min{fi,k+c(j−k)}=min{min{fi,k+c(j−1−k)}+c,fi−1,j}
也就是说:
s i , j = min { s i , j − 1 + c , f i − 1 , j } s_{i, j} = \min \{ s_{i, j - 1} + c, f_{i - 1, j} \} si,j=min{si,j−1+c,fi−1,j}
但是这里是有绝对值的,所以我们考虑正着扫一遍之后再反着扫一遍,这样就能解决绝对值的问题了(注意这里要用滚动数组来避免空间炸掉)。这样搞的时间复杂度就变成 O ( n A ) O(nA) O(nA) 的了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
#define MAXA 101
#define INFI 1e18
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int n = 0; int c = 0;
int f[MAXN][MAXA] = { 0 };
int g1[MAXN] = { 0 };
int g2[MAXN] = { 0 };
signed main(){
n = in; c = in; int A = 0, ans = INFI;
for(int i = 1; i <= n; i++) a[i] = in, A = max(A, a[i]);
memset(f, 0x3f, sizeof f);
for(int j = a[1]; j <= A; j++) f[1][j] = 1ll * (j - a[1]) * (j - a[1]);
for(int i = 2; i <= n; i++){
for(int j = 0; j <= A + 1; j++) g1[j] = g2[j] = INFI;
for(int j = A; j >= a[i]; j--)
g1[j] = min(g1[j], min(g1[j + 1] + c, f[i - 1][j]));
for(int j = a[i - 1]; j <= A; j++)
g2[j] = min(g2[j], min(g2[j - 1] + c, f[i - 1][j]));
for(int j = a[i]; j <= A; j++)
f[i][j] = min(f[i][j], min(g1[j], g2[j]) + 1ll * (j - a[i]) * (j - a[i]));
}
for(int i = 1; i <= A; i++) ans = min(ans, f[n][i]);
cout << ans << '\n';
return 0;
}
T3 color
感觉 T 3 T3 T3 才是最简单的一道题(好像是因为把原来的 T 3 T3 T3 给换掉了)。
考场上脑抽,思路一直不对,推式子:
a n s = ∑ S ⊂ U ∣ ∑ i ∈ S a i − ∑ i ∈ C U S a i ∣ = ∑ S ⊂ U ∣ 2 ∑ i ∈ S a i − X ∣ X = ∑ i = 1 n a i \begin{aligned} ans = & \sum_{S \subset U} \bigg| \sum_{i \in S}a_i - \sum_{i \in C_US}a_i \bigg| \\ = & \sum_{S \subset U} \bigg| 2\sum_{i \in S}a_i - X \bigg| \\ X = & \sum_{i = 1}^na_i \end{aligned} ans==X=S⊂U∑∣ ∣i∈S∑ai−i∈CUS∑ai∣ ∣S⊂U∑∣ ∣2i∈S∑ai−X∣ ∣i=1∑nai
本来到这一步了,解法马上就出来了,然后我就开始发病:
X = ∑ i = 1 n a i a n s = ∑ S ⊂ U ∣ ∑ i ∈ S a i − ∑ i ∈ C U S a i ∣ = ∑ S ⊂ U ∣ 2 ∑ i ∈ S a i − X ∣ = ∑ S ⊂ U ( 2 ∑ i ∈ S a i − X ) [ 2 ∑ i ∈ S a i ≥ X ] − ∑ S ⊂ U ( 2 ∑ i ∈ S a i − X ) [ 2 ∑ i ∈ S a i < X ] = ( 2 ∑ S ⊂ U ∑ i ∈ S a i − ∑ S ⊂ U X ) [ 2 ∑ i ∈ S a i ≥ X ] − ( 2 ∑ S ⊂ U ∑ i ∈ S a i − ∑ S ⊂ U X ) [ 2 ∑ i ∈ S a i < X ] \begin{aligned} X = & \sum_{i = 1}^na_i \\\\ ans = & \sum_{S \subset U} \bigg| \sum_{i \in S}a_i - \sum_{i \in C_US}a_i \bigg| \\ = & \sum_{S \subset U} \bigg| 2\sum_{i \in S}a_i - X \bigg| \\ = & \sum_{S \subset U}\left( 2\sum_{i \in S}a_i - X \right)[2\sum_{i \in S}a_i \geq X] - \sum_{S\subset U}\left( 2\sum_{i \in S}a_i - X \right)[2\sum_{i \in S}a_i < X] \\\\ = & \left(2\sum_{S \subset U}\sum_{i \in S}a_i - \sum_{S\subset U}X\right)[2\sum_{i \in S}a_i \geq X] - \left(2\sum_{S \subset U}\sum_{i \in S}a_i - \sum_{S\subset U}X\right)[2\sum_{i \in S}a_i < X] \end{aligned} X=ans====i=1∑naiS⊂U∑∣ ∣i∈S∑ai−i∈CUS∑ai∣ ∣S⊂U∑∣ ∣2i∈S∑ai−X∣ ∣S⊂U∑(2i∈S∑ai−X)[2i∈S∑ai≥X]−S⊂U∑(2i∈S∑ai−X)[2i∈S∑ai<X](2S⊂U∑i∈S∑ai−S⊂U∑X)[2i∈S∑ai≥X]−(2S⊂U∑i∈S∑ai−S⊂U∑X)[2i∈S∑ai<X]
然后就推不动了。
实际上在上面那个式子就已经可以做了。我们令 f j f_j fj 表示选出的子集 S S S 中 ∑ i ∈ S a i = j \sum\limits_{i \in S}a_i = j i∈S∑ai=j 的集合个数。那么显然现在这个就是一个背包了。我们就有:
f j = ∑ a k ≤ j f j − a k f_j = \sum_{a_k \leq j}f_{j - a_k} fj=ak≤j∑fj−ak
就直接背包转移,复杂度是 O ( n S ) O(nS) O(nS)。
然后答案就很显然了:
a n s = ∑ i = 1 X f i ∣ 2 i − X ∣ ans = \sum_{i = 1}^Xf_i|2i - X| ans=i=1∑Xfi∣2i−X∣
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define MOD 998244353
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int T = 0;
int x = 0; int n = 0;
int w[MAXN] = { 0 };
int f[MAXN] = { 0 };
int main(){
T = in;
while(T--){
x = in; n = in; int ans = 0;
f[0] = 1;
for(int i = 1; i <= x; i++) f[i] = 0;
for(int i = 1; i <= n; i++) w[i] = in;
for(int i = 1; i <= n; i++)
for(int j = x; j >= w[i]; j--)
f[j] = (f[j] + f[j - w[i]]) % MOD;
for(int i = 0; i <= x; i++) ans = (ans + 1ll * f[i] * abs(2 * i - x) % MOD) % MOD;
cout << ans << '\n';
}
return 0;
}
T4 mex
之前似乎做过一道一模一样的题…
可惜考场上没想起来,也没时间写了。
T J TJ TJ 我之前就写过一份,现在直接搬过来吧。
求区间最小的不能被子集合表示出的数。
考虑一个区间 [ l , r ] [l, r] [l,r],对这一段进行一个升序排序,设排序之后的数分别为 a l ≤ a l + 1 ≤ ⋯ ≤ a r a_l \leq a_{l+1} \leq \cdots \leq a_r al≤al+1≤⋯≤ar,现在对于 a l a_l al 如果不等于 1 1 1,那就可以直接输出 1 1 1。若等于 1 1 1 我们考虑一个简单的 d p dp dp。
现在用 p o s pos pos 表示当前能表示出的数属于的区间为 [ 1 , p o s ] [1, pos] [1,pos](很显然这个区间包括了 1 1 1),对于下一个数 a i a_i ai,现在有两种情况:
- 如果 a i ≤ p o s + 1 a_i \leq pos + 1 ai≤pos+1,那么现在能表示出的数的集合就是 [ 1 , p o s ] ⋃ [ 1 + a i , p o s + a i ] = [ 1 , p o s + a i ] [1, pos] \bigcup [1 + a_i, pos + a_i] = [1, pos + a_i] [1,pos]⋃[1+ai,pos+ai]=[1,pos+ai]。
- 如果 a i > p o s + 1 a_i> pos + 1 ai>pos+1,那么现在能表示出的数的集合就是 [ 1 , p o s ] ⋃ [ 1 + a i , p o s + a i ] [1, pos] \bigcup [1 + a_i, pos + a_i] [1,pos]⋃[1+ai,pos+ai],显然这个集合中不包含 p o s + 1 pos + 1 pos+1,所以答案就是 p o s + 1 pos + 1 pos+1。
这样做对于每次询问的时间复杂度是 O ( n log n ) O(n\log n) O(nlogn) 的,所以要考虑优化这个做法。
如果现在的值域是 [ 1 , p o s ] [1, pos] [1,pos],那么现在的答案就应该是 p o s + 1 pos + 1 pos+1。记小于等于 a n s ans ans 的数的和为 r e s res res,如果 r e s ≥ a n s res \geq ans res≥ans,那么就说明一定有没有选过(选一些数的和为 a n s ans ans)的且小于等于 a n s ans ans 的数存在,那么现在令 a n s = r e s + 1 ans = res + 1 ans=res+1。如果不满足 r e s ≥ a n s res \geq ans res≥ans 就说明答案就是 a n s ans ans,直接 b r e a k break break 就好了,注意到 ∑ a i < 1 e 9 \sum a_i < 1e9 ∑ai<1e9,所以可以考虑用主席树维护区间值域和。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define INFI 1 << 30
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int n = 0; int m = 0;
struct Tnode{
int dat;
int ls, rs;
}t[MAXN << 5];
int tot = 0;
int root[MAXN] = { 0 };
inline void up(int p) { t[p].dat = t[t[p].ls].dat + t[t[p].rs].dat; }
int update(int now, int l, int r, int x, int val){
int p = ++tot; t[p] = t[now];
if(l == r) { t[p].dat += val; return p; }
int mid = (l + r) >> 1;
if(x <= mid) t[p].ls = update(t[p].ls, l, mid, x, val);
else t[p].rs = update(t[p].rs, mid + 1, r, x, val);
up(p); return p;
}
int query(int p, int q, int l, int r, int ql, int qr){
if(ql <= l and r <= qr) return t[p].dat - t[q].dat;
int mid = (l + r) >> 1, ans = 0;
if(ql <= mid) ans += query(t[p].ls, t[q].ls, l, mid, ql, qr);
if(qr > mid) ans += query(t[p].rs, t[q].rs, mid + 1, r, ql, qr);
return ans;
}
int main(){
n = in;
for(int i = 1; i <= n; i++) a[i] = in;
for(int i = 1; i <= n; i++) root[i] = update(root[i - 1], 1, INFI, a[i], a[i]);
m = in;
while(m--){
int l = in, r = in, ans = 1;
while(true){
int res = query(root[r], root[l - 1], 1, INFI, 1, ans);
if(res >= ans) ans = res + 1;
else break;
}
cout << ans << '\n';
}
return 0;
}