F - Diameter set https://atcoder.jp/contests/abc221/tasks/abc221_f
N 点树, 找染色法, 染>=2个点为红色,让红色点两两之间距离为直径
方案数 mod 998244353
范围 N 2e5
2s
1024mb
我的思路 首先直径 是经典算法, 随机点u,找最远点v, 再以v找最远点 便是直径
如果选v
那么可以把v看作根, 相当于做树上dp
因为有深度和直径都知道, 那么对于到叶子距离2倍小于直径的分叉至多选一个,
而2倍大于直径的分叉(不可能都有直径,否则这样能得到更大的长度)
如果直径是偶数, 那么从v到1/2直径点u再到最远点, 这样的点u只有一个,并且这个u可以看成重心
因此可以从重心去找 直径/2 的距离做树上统计 即可
问题来到了奇数长度的直径, 如果直径是奇数, 选了v到最远点的方案 就是最远点的个数
任意两个直径 必有交点, 否则 两个直径上 p1..p2 有一个简单路径 取 p1 — 最远, p2 — 最远, p1-p2, 大于等于 直径/2向上取整 + 1
这样的话
任取一条来看,
v …. x - y…..t
假设,x和y是中间距离的两个点
那么不可能有不经过y的v..x…t1, 否则 t..x..t1 更长
换句话说
一定所有直径有x-y
类似的证明
已经证明了有公共点,
那么假设是y右侧的最近的某个p
同样v ….p…更长的一半, 将会比直径长
x对称同理
所以奇数情况,就是, x 去找距离的方案 乘上 y 去找距离的方案
似乎就推出来了?
代码 https://atcoder.jp/contests/abc221/submissions/33847992
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 <bits/stdc++.h> using namespace std;typedef long long ll;#define MOD 998244353 #define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define pb push_back ll read () {ll r;scanf ("%lld" ,&r);return r;} vector<int > p2[200010 ]; pair<int ,int > dfs (int u,int fa,int d = 0 ) { pair<int ,int > r = {d ,u}; for (auto v:p2[u]) if (v != fa) r = max (r, dfs (v,u,d +1 )); return r; } int D = 0 ;bool dfsxy (int u,int fa,int findv,int &x,int &y,int d = 0 ) { bool ok = u == findv; for (auto v:p2[u]) if (v!=fa) ok = ok || dfsxy (v,u,findv,x,y,d+1 ); if (ok && d == D/2 ) x = u; if (ok && d == D/2 + 1 ) y = u; return ok; } int dfscnt (int u,int fa,int d) { int r = d==0 ; for (auto v:p2[u])if (v!=fa) r += dfscnt (v,u,d-1 ); return r; } int main () { int n = read (); rep (i,1 ,n){ int u = read (); int v = read (); p2[u].pb (v); p2[v].pb (u); } auto [_,u] = dfs (1 ,0 ); auto [d,v] = dfs (u,0 ); D = d; int x,y; dfsxy (u,0 ,v,x,y); if (d % 2 ){ ll cx = dfscnt (x,y,D/2 ); ll cy = dfscnt (y,x,D/2 ); printf ("%lld\n" ,cx*cy % MOD); }else { ll s = 1 ; ll r = 1 ; for (auto p:p2[x]){ ll c = dfscnt (p,x,D/2 -1 ); (r*=(c+1 ))%=MOD; s += c; } printf ("%lld\n" ,(r+MOD-s)%MOD); } return 0 ; }
G - Jumping sequence https://atcoder.jp/contests/abc221/tasks/abc221_g
能否从(0,0) 恰好N次,跳到(A,B)
第i次可以向, x正/x负/y正/y负(4选1),跳恰好Di
如果可以,给一个方案
范围 N 2000
A,B [-3.6e6,3.6e6]
Di 1800
5s
1024
我的思路 顺序其实没有关系,因为是向量加和
那一个角度转化就是
分成4组,
其中两组的差 = X
另外两组的差 = Y
另一个就是
先分两组 组A-组B = X+Y
然后找到组A中部分 = Y的 移出来
但这个只是 充分条件不是必要条件
题解 逆时针转45度坐标轴 长度再乘上 根号2
终点 原坐标系(a,b) -> 新坐标系 (a-b,a+b)
操作 全变成了(+-di,+-di)的形式
什么好处呢, 两个轴单独考虑了
对于一个轴, 要找
S = sum (1/-1) * di
的方案
可以同时加 所有di的和
S + sum di = sum (2/0) * di = 2 * (选一些di)
就是简单的01 背包,需要bitset搞一下 空间
代码 https://atcoder.jp/contests/abc221/submissions/33849395
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) #define pb push_back ll read () {ll r;scanf ("%lld" ,&r);return r;} bitset<3600001> dp[2001 ]; int d[2000 ];const char c[] = { 'L' ,'D' ,'U' ,'R' };char ans[2010 ];bool w () { int n = read (); int x = read (); int y = read (); int m[2 ] = {x-y,x+y}; int s = 0 ; rep (i,0 , n) { d[i] = read (); s += d[i]; } rep (i,0 ,2 ) if (abs (m[i]) > s) return false ; rep (i,0 ,2 ) if ((m[i] + s) % 2 != 0 ) return false ; rep (i,0 ,2 ) m[i] = (m[i] + s) / 2 ; dp[0 ][0 ] = true ; rep (i,0 , 2000 ) dp[i + 1 ] = dp[i] | (dp[i] << d[i]); rep (j,0 ,2 ) if (!dp[n][m[j]]) return false ; per (i,0 ,n){ int bit = 0 ; rep (j,0 ,2 ) if (!dp[i][m[j]]) { m[j] -= d[i]; bit += (1 << j); } ans[i] = c[bit]; } return true ; } int main () { if (!w ()) printf ("No\n" ); else { printf ("Yes\n" ); printf ("%s\n" ,ans); } return 0 ; }
H - Count Multiset https://atcoder.jp/contests/abc221/tasks/abc221_h
输入正整数 N, M
找可重集合,k=1…N个正整数, 和=N, 同一个重复数量不超过M, 这样可重集合的个数
mod 998244353
对于k=1..N 分别输出答案
范围 M <= N <= 5000
2 s
1024 mb
我的思路 5000 很想平方
但是直接之想到3维dp
dp[v][x][c] =
和 = v, 最大值不超过v, 一共用了c个数 的方案数
转移
dp[v][x][c] = sum dp[v-kx][x-1][c-k], k=0..m
这样$N^3$的状态, 转移还要枚举k(可以跳点双指针变成O1均摊), 虽然滚动可以吃掉一维解决空间,还是无法解决时间
但是从加和的角度可以看成二维卷积 一个稀疏的[k][1] * 0..m
题解 显然 等于 非严格递增序列的方案数(其实上面dp就是按照这个来的
令B为其差分数组,其中B1=A1, Bi = Ai-A[i-1]
sum Ai = N
sum Bi * (k-1+i) = N
考虑翻转顺序
sum i Bi = N
然后限制是最多M个Ai, 也就是连续0的个数 <= M-1 < M
这里的好处就是把M限制变成了连续0的限制,不影响总和,而非0的内容是任意的数量,没有限制
f[i][j] =
B中前i个数和为j 且最后一个数不为0的方案数
$f_{i,j}=\sum\limits_{k=\max(0,i-m)}^{i-1}\sum\limits_{l = ik < j} f_{k,j-l}$
初始状态f[0][0] = 1, f[0][>0] = 0
滑窗和/前缀和优化一下就n^2了
注意到颠倒了i以后正好我们要的就是首个不为0,变成末尾不为零
因此f[i][n]
就是要求的k=i的答案
代码 https://atcoder.jp/contests/abc221/submissions/33848661
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define MOD 998244353 #define rep(i,a,n) for (ll i=a;i<(ll)n;i++) ll read () {ll r;scanf ("%lld" ,&r);return r;} const int N = 5000 ;ll f[N+10 ][N+10 ]; ll sum[N+10 ][N+10 ]; int main () { int n = read (); int m = read (); f[0 ][0 ] = sum[0 ][0 ] = 1 ; rep (i,1 ,n+1 ) rep (j,0 ,n+1 ) { for (int k = i; k <= j; k+=i) (f[i][j] += sum[i-1 ][j-k]) %= MOD; sum[i][j] = (sum[i-1 ][j] + f[i][j] - (i-m>=0 ?f[i-m][j]:0 )) % MOD; } rep (i,1 ,n+1 ) printf ("%lld\n" , (f[i][n] + MOD) % MOD); return 0 ; }
总结 F
没啥难的
先知道基本的直径算法, 也是可以自己推的
G
分离相关性
坐标轴转化
据说有黑科技 能55ms Linear Time Algorithms for Knapsack Problems with Bounded Weights
https://www.sciencedirect.com/science/article/abs/pii/S0196677499910349
H
差分转化!!!
这里的好处就是把M限制变成了连续0的限制,不影响总和,而非0的内容是任意的数量,没有个数限制
只有对于首个非零和连续0的个数的限制了
然后是bitset的熟练应用了, 甚至位运算做01背包转移
参考 官方题解