杂题合集
abc_405e
简单组合计数。形式化地说,A 必须在 C 左边,A 必须在 D 左边,B 必须在
D 左边,不难发现应对 C 进行分割,左半部分与 B 重合,而右半部分与 D
重合。如图 枚举 C 中的断点 \(i\),答案即为
\[\sum_{i=0}^{c}\binom{a+b+i}{b}\binom{c-i+d-1}{d-1}\]
预处理阶乘和逆元可以做到 \(O(n)\)。
### 【模板】通信题 通信题真好玩。
我们只能使用不大于 \(2^{20}\)
的非负整数存储状态。注意到这个数刚好比 \(10^6\)
略大。这给我们对每一位附上不同权值提供了方便。考虑利用异或运算的性质,给每一位附上当前字符下标的权值(下标从
\(1\))开始,然后求其异或和。将 \(S\) 和 \(T\) 的结果异或起来即得答案。注意下标一定从
\(1\) 开始,否则第一位的值无论如何都是
\(0\)。
communication.cpp
1 |
|
『MdOI R1』Group
二分答案。 注意到,对 \(a\) 进行排序,则我们可以选中其中一段连续的区间,计算它的方差。而两侧的值直接设为区间的平均值,不产生贡献。这样是最优策略。在检查合法性时直接从 \(1\) 到 \(n\) 枚举区间就好了,预处理前缀和和前缀平方和可做到 \(O(1)\) 求方差。 二分时,\(l\) 必须从 \(1\) 开始。
code
1 |
|
[NOIP 2014 提高组] 飞扬的小鸟
\(dp_{i,j}\) 表示在 \((i,j)\) 所用的最少点击屏幕数,显然有朴素转移方程 \[dp_{i,j}=\min(dp_{i,j},dp_{i-1,j-k\times x_{i-1}},dp_{i-1,j+y_{i-1}})\] 时间复杂度 \(O(nm^2)\)。 考虑优化,复杂度瓶颈在于 \(k\) 的枚举。我们发现,可以使用类似完全背包的处理,从 \(dp_{i,j-(k-1)\times x_{i-1}}\) 转移到 \(dp_{i,j-k\times x_{i-1}}\) 而不必从 \(dp_{i-1}\) 转移。时间复杂度 \(O(nm)\)。 注意要先处理点击屏幕的情况,否则可能出现同一横坐标既向下又向上的情况。code
1 |
|
邦邦的大合唱站队
状压
DP,每一位表示一种乐队。预处理前缀和计算长度。代码实现也很简单。(很久没有独立想出过
DP 题了)
code
1 |
|
可持久化并查集
用可持久化数组实现。使用按秩合并但不使用路径压缩。注意修改 \(fa\) 与修改 \(dep\) 都要新建版本。
code
1 |
|
[PA 2024] Modernizacja Bajtocji
挺喜欢这道题,但出在模拟赛里我就不喜欢了。 显然需要维护连通块,连通块内出现环了就说明都有电脑了,连通块是树形的就无法确定。然而这里有一个删除操作。并查集不好进行删除,我们发现被删的点留在连通块内无影响,则考虑对每个人维护 \(id\),删除即为更新 \(id\)。
code
1 |
|
【模板】最长公共子序列
好早以前欠的一道题,现在补上。 朴素 DP \(O(n^2)\),但这题可以转化为求最长上升子序列。将序列 \(P_1\) 视为是“有序的”,按照 \(P_1\) 的排序规则在 \(P_2\) 中求最长上升子序列,显然这就是答案。实现方面的话开个桶就行。 最长上升子序列 \(O(n\log n)\) 求法: 设 \(dp_i\) 表示到第 \(i\) 个数时的答案,\(t_i\) 表示 \(dp_i\) 对应的序列最大值,显然有 \(dp_i=\max_{j<i \land t_j<b_i} dp_j\),我们可以用树状数组维护前缀 \(\max\),实现 \(O(n\log n)\) 的时间复杂度。
code
1 |
|
[CQOI2017] 小Q的棋盘
也是模拟赛的史,可以树形 DP,但我选择贪。 优先走最长链,如果还有剩余步数就需要每两步访问一个新节点。
code
1 |
|
SZA-Cloakroom
很巧妙的一道 DP。显然可以将物品和询问离线下来分别按时间排序。设 \(dp_k\) 表示总价值为 \(k\) 的物品能拿走的最后的时间。转移 \[dp_k=\max(dp_k,\min(dp_k-c_j,b_j))\] 表示新加入物品 \(j\),我们能否凑出 \(k\) 决定于最早被取走的那个物品的时间,所以取 \(\min\),而我们显然希望这个值越晚越好,所以取 \(\max\),则只要 \(dp_k>m_i+s_i\) 就说明合法。 初始状态所有 \(dp\) 均为 \(-\inf\),表示均不合法。\(dp_0\) 为 \(\inf\) 表示不选任何物品总是可行的。
code
1 |
|
abc_288f
设 \(dp_i\) 表示前 \(i\) 位的答案,有朴素的转移 \[dp_i=\sum_{j=1}^{i-1} dp_j\times \overline{X_{j+1}X_{j+2}\cdots X_i}\] 化简: \[dp_i=10\times dp_{i-1}+X_i+X_i\times \sum_{j=1}^{i-1}dp_j\] 可以维护前缀和实现 \(O(n)\)。
code
1 |
|
小 a 和 uim 之大逃离
我们事实上只关注二者的差,所以无需分别记录状态。\(dp_{i,j,t,0/1}\) 表示在 \((i,j)\),二者之差为 \(t\),当前应为小 a/uim 走。转移很朴素,看代码吧。
code
1 |
|
[SDOI2010] 地精部落
\(dp_{j,0/1}\) 表示前 \(i\) 个数中最后一个是第 \(j\) 大的数的升/降序方案数。
code
1 |
|
[SCOI2009] 游戏
神仙题,转化太难想了。观察题目给的实例,发现转化关系可分为 \(3\) 组,不难看出每组的周期应为该组内元素数量,总排数即为各组的元素数的 \(\operatorname{lcm}\) 再加 \(1\)。接下来想如何不重地枚举所有对应关系。考虑使用质因数分解。使用类似完全背包的写法,这样就保证了所有方案的枚举。
code
1 |
|
[LnOI2019] 真正的 OIer 从不女装
我们发现,女装只有零次和无数次。所以将 \(k>0\) 的情况视为 \(k=1\)。线段树维护区间最长连续段、包含最左/右端最长连续段。
code
1 |
|
[NOIP 2004 提高组] 合并果子 加强版
传统做法中堆的 \(O(n\log n)\) 复杂度太高了,我们使用两个队列,先桶排,然后按顺序插入队列 \(q_1\) 中,之后每合并一次就将结果插入队列 \(q_2\) 中,容易发现,\(q_1\) 和 \(q_2\) 都具有单调性,所以每次只需从两个队列的队头取出 \(2\) 个最小的数即可。时间复杂度 \(O(n)\)。注意读入的常数。 使用这种思路,做 [NOIP 2016 提高组] 蚯蚓。
code
1 |
|
多人背包
也是很久以前的题,之前好几次想写都觉得太抽象写不了,今天静下心来想一想其实并不难。
\(dp_{j,k}\) 表示容量为 \(j\) 时的第 \(k\)
优解。我们直接双指针枚举选或不选当前物品,枚举出当前的前 \(k\)
优,将它们存入队列中然后直接转移。时间复杂度 \(O(nvk)\),空间复杂度 \(O(vk)\)。 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
using namespace std;
typedef long long ll;
int k,v,n,a[210],b[210],dp[5010][60],temp[60];
ll ans;
int main(){
memset(dp,0xcf,sizeof(dp));
cin>>k>>v>>n;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
dp[0][1]=0;
for(int i=1;i<=n;i++)
for(int j=v;j>=a[i];j--){
int p1=1,p2=1,cnt=0;
while(cnt<=k){
if(dp[j][p1]>dp[j-a[i]][p2]+b[i])
temp[++cnt]=dp[j][p1++];
else
temp[++cnt]=dp[j-a[i]][p2++]+b[i];
}
for(int t=1;t<=k;t++)
dp[j][t]=temp[t];
}
for(int i=1;i<=k;i++)
ans+=dp[v][i];
printf("%lld\n",ans);
return 0;
}