LGJOI-20230808
大概是赢了。
A
题意:
给定 \(n\),\(m\),将 \(m\) 分解为不超过 \(n\) 个 \(n!\) 的因数的和。
\(n \leq 20\),\(m \leq n!\)
solution:
考虑如何能做到选最少的数分解 \(m\)。贪心的思路一定是从大到小选取 \(n!\) 的因数。直接枚举 \(n!\) 的因数非常困难,因此考虑优化枚举。
对于 \(n!\) 的一个因数 \(x\) ,一定是由若干个不重复的 \(y(y \le n)\) 相乘而成的。
假设 \(x = \large\prod^n_{i=1}i^{a_i}\), 它可以分解为一段极长的 \(a_i\) 不为 \(0\) 的后缀与剩余部分相乘。那么先将原数拆解为若干个后缀相加的形式再进行调整。
具体而言,定义数列 \(b_i = \large \frac{n!}{i!}\),先将 \(m\) 分解为若干个 \(b_i\) 的和,然后如果有重复的 \(b_i\) 则合并即可。
容易证明最多会把 \(m\) 分解为 \(n - 1\) 个数相加。
#include<iostream>
#include<fstream>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
int t, n, m;
int frac[35];
int st[305], top;
signed main(){
cin >> t;
frac[0] = 1;
for(int i = 1; i <= 20; i ++)
frac[i] = frac[i - 1] * i;
while(t --){
cin >> n >> m;
for(int i = 0; i <= n; i ++)
while(m >= frac[n] / frac[i])
st[++ top] = frac[n] / frac[i], m -= frac[n] / frac[i];
int nw = 2, less = 0, ns = 1, cnt = 1;
st[++ top] = 998244353;
while(nw <= top + 1){
if(st[nw] == st[ns])
cnt ++, st[nw] = 0;
else{
st[ns] *= cnt;
less += cnt - 1;
cnt = 0, ns = nw;
cnt ++;
}
nw ++;
}
cout << top - less - 1 << "\n";
for(int i = 1; i <= top - 1; i ++)
if(st[i]) cout << st[i] << " ";
cout << "\n";
top = 0;
}
return 0;
}
B
题意:
初始有 \(cnt = ans = 0\),对一个排列 \(T_n\) 执行如下操作:
- 定义 \(T_i\) 的价值为 \(2^{n-T_i}\)。
- 从左到右遍历排列 \(T\) 找到最小的 \(i\) 满足\(\operatorname{val}(T_i) \not= 0\)。 令 \(cnt \leftarrow cnt + 1\),\(ans \leftarrow ans + \operatorname{val}(T_i)\),$ \operatorname{val}(T_i) = 0$。
- 同时从左往右遍历所有 \(a_j = cnt\) 的 \(j(j \le m)\) ,在排列 \(P_j\) 里查找最小的 \(k\) 满足 $\operatorname{val}(P_{j,k}) \not= 0 $,令 $ \operatorname{val}(P_{j,k}) \leftarrow 0$
试找到一个 \(T\) 能够最大化 \(ans\),输出 \(ans\) 对 \(998244353\) 取模的值。
\(n \leq 600\)
solution:
显然如果最高位能产生贡献,后面即使都不选都比不选最高位好。
设答案集合为 \(S=\{i|\operatorname{val}(i)\not=0\}\)。
如果可以判定是否有集合 \(S' \subseteq S\) ,即可从小到大将 \(i\) 加入集合 \(S'\),如果有 \(S' \not\subseteq S\) 则删除当前 \(i\) 即可。
依次考虑每一个 \(P_i\),从小到大按顺序枚举没有考虑过的 \(x\),如果 \(x \in S\) 则必须在 \(i\) 之前选取 \(x\)。否则直接令 \(\operatorname{val}(x) = 0\) 即可。
如果必须在 \(i\) 前选取的 \(x\) 数量超过了 \(i\),则 有\(S' \not\subseteq S\)。
时间复杂度 \(O(n^3)\)。
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int modd = 998244353;
int ksm(int u, int v){
if(v == 0) return 1;
int ans = ksm(u, v >> 1);
ans = ans * ans % modd;
if(v & 1) ans = ans * u % modd;
return ans;
}
int n, m, ans;
bool s[605];
int c[605][605];
bool flg[605];
bool del[605];
bool check(){
memset(del, 0, sizeof(del));
int res = 0;
for(int i = 1; i <= n; i ++){
if(!flg[i]) continue;
bool flag = false;
for(int j = 1; j <= n; j ++){
if(!s[c[i][j]] && !flag && !del[c[i][j]])
flag = true, del[c[i][j]] = 1;
else if(!flag && !del[c[i][j]])
res ++, del[c[i][j]] = 1;
}
if(res > i) return false;
}
return true;
}
signed main(){
cin >> n >> m;
for(int i = 1, v; i <= m; i ++)
cin >> v, flg[v] = 1;
for(int i = 1; i <= n; i ++)
if(flg[i])
for(int j = 1; j <= n; j ++)
cin >> c[i][j];
for(int i = 1; i <= n; i ++){
s[i] = true;
if(!check())
s[i] = false;
}
for(int i = 1; i <= n; i ++)
if(s[i]) (ans += ksm(2, n - i)) %= modd;
cout << ans;
return 0;
}
C
题意:
在数轴上,可以将点染成黑白两种颜色,也可以不染色。有 \(n\) 个限制。每个限制 \(P_i = (l, r, c)\) 代表需要让 \([l, r]\) 区间染成颜色 \(c\)。限制可能无法同时满足。求有多少种本质不同的染色方案。这里本质不同当且仅当两种方案中,有限制 \(P_x\) 在一种方案中成立但在另一种方案中不成立。
Solution:
\(O(n^2)\):
将限制右端点排序。定义 \(f_{i,c}\) 表示只考虑前 \(i\) 个限制,且第 \(i\) 个限制必须满足,且第 \(i\) 个限制颜色是 \(c\) 的方案数。
设 \(p_i\) 表示满足 \(r_{p_i} < l_i\) 的最大下标。\(g_{i - 1, r_j + 1, c}\) 表示前 \((i - 1)\) 条线段中,左端点大于等于 \(r_j + 1\),且颜色为 \(c\) 的线段数量。
有状态转移方程:
形象的理解这个方程。在上一段终点右边的颜色和当前限制颜色一样的限制任意选择是否满足。
初值为:\(r_0 = 0, f_{0,0} = f_{0,1} = 1\)。
最终答案为 \(1 + \sum _ {i - 1} ^ n f_{i, c_i}\)。
正解:
考虑优化 DP。
\(f\) 和 \(g\) 混在一起很烦,令 \(h_{i, j, c} = f_{j, c} \times 2 ^ {g_{i, r_j+1, i - c}}\),将 DP 方程写成:
注意到 \(g\) 有递推关系:
可推出 \(h\) 的递推关系:
因此我们使用线段树维护 \(h_{i,j,c}\),支持前缀 \(\times 2\) ,单点赋值, 前缀求和。
时间复杂度 \(O(n \log n)\)。

浙公网安备 33010602011771号