11 月日记
11.14
快死在 \(\text{tarjan}\) 里了
今天做了好几道强连通分量的题,基环树没怎么补。
P1653 [USACO04DEC] Cow Ski Area G
二位矩阵压成一维按照题目要求建图,高的向低的连,随后 \(\text{tarjan}\) 缩点再建新图,容易发现,缩点后的新图中,每个点的入度和出度的最大值即为答案。
WA 了八次才发现自己输入时 \(n,m\) 输入反了……
P4306 [JSOI2010] 连通数
依旧是先缩点建新图,记录每个强连通分量的大小,用一个\(\text{bitset}\) 标记 \(i\) 是否能到 \(j\),在新图上跑一遍拓扑并利用 \(\text{bitset}\) 的特性将连通的点“合并”,最后答案即为:
\(\sum^{sccnt}_{i=1}{\sum^{sccnt}_{j=1}{siz_i \times siz_j (\text{i can arrive j})}}\)
后来发现 \(N \leq 2000\) 的数据似乎只需要 \(\text{bitset}\) 把可以到达的点压进去然后类 \(\text{Floyd}\) 的 \(O(n^2)\) 唐过去
好嘛
今日歌单:Loser,经过,蒲公英的约定
11.15
上午%你赛,考了一套\(\text{usaco}\)金、珀金组的题
\(5 + 0 + 0 + 0 = 5 \enspace \text{Rank inf +}\)
事实证明前一天没睡好我太困了,没什么精力,那就回顾一下。
T1
给定 \(n \times n\) 的矩阵,使得每个 \(2 \times 2\) 的子矩阵中,恰好有2
个A班同学和2
个B班同学的最大总方案数。
找规律题,考场脑子抽了
容易发现A班同学和B班同学两个两个竖着错开排或横着,只需与上个1
开俩二维数组统计下就行。
T2 P5839 [USACO19DEC] Moortal Cowmbat G
\(\text{dp + Floyd}\)
将 "\(i\) 变成 \(j\) 花费 \(a_{i,j}\) 天" 看做 \(i\) 到 \(j\) 连了一条边权为 \(a_{i,j}\) 的有向边,邻接矩阵存图跑 \(\text{Floyd}\)
前缀和统计下原字符串变成字符 \(a_{i,j}\) 所对应字符的花费,由于要使新字符串中连续 \(p\) 个字符相等,我们定义转移方程 \(dp_{i,j}\) 表示以 \(i\) 结尾的一段长度 \(p\) 变成字符 \(j\) 的最小天数,由于 \(dp_{i,j}\) 只会从 \(dp_{i,j-1}\) 转移,且为背包问题,所以第二维可以滚掉,转移方程:\(dp_{i} = \min({dp_{i - p} - sum_{i - p,j} + sum_{i,j}})\)
最终答案即为 \(dp_n\)
T3 P6280 [USACO20OPEN] Exercise G
手玩几组数据,我们发现如果想让每头牛都回到原来的位置的步数为 \(\text{lcm }{a_i}\),要求一个序列的\(\text{lcm}\) 我们首先想到预处理每一项的质因数,然后每个数取最高次项,然后考虑如何处理结果。
我们试着将题面的问题换一个问法:求所有满足条件的 \(K\) 的总方案数
容易发现这是一个计数类的问题,可以用 \(dp\) 解决,然而该题只是让我们求满足条件的 \(K\) 的和,我们也可以用 \(dp\) 实现。
定义 \(dp_{i,j}\) 表示枚举前 \(i\) 个素数和为 \(j\) 时 \(K\) 的值,即有转移方程 \(dp_{i,j} = dp_{i - 1,j - k} \times k \enspace [j \geq k]\) 容易发现这是一个背包问题,第一维可以滚掉,最终答案即为 \(\sum dp_j\)
T4 P6280 [USACO20OPEN] Exercise G
谁 tm 想得到这是凸包
也是看了 \(\text{oi-wiki}\) 并 A 了凸包板子再来做的
将下落高度与时间写成一次函数并画在平面直角坐标系中,自然的,图像会有交点,容易发现如果有一个图像与其他图像没有一个交点,那么它就没有贡献
容易想到一个贪心,如果 \(A_{q_i} < A_i\),我们就要尽可能快的向下走,即为碰到斜率更大的交点的图像就立马跃迁,反之就慢慢向下走
由于两种答案存在对称性,在此只讨论一种
从 \(i\) 号世界出发的路径经过的点是一个凸包,并且可以发现 \(A_j < A_i\) 的世界没有贡献,因为如果跃迁过去只会走的更慢,不考虑,所以我们只需要从大到小插入每条直线维护凸包,容易发现答案的截距单调,选择单调栈 \(O(n)\) 维护
在建出凸包之后可以二分找出满足条件最大的 \(q_i\),时间复杂度 \(O(n \log n)\)
雀食很难
11.16
周六就比较放松
依旧在做强连通分量的题
fzh 同学讲了 \(\text{2 - SAT}\) 上 \(\text{oi-wiki}\),当晚打了板子
11.18
又双叒叕在做强连通分量
ccf 又把出分时间延了两天,话说后天是不是更5.2卡池
注:今晚会打 $ \text{ICPC} $,cf 上的同步赛
P3627 [APIO2009] 抢掠计划
缩点之后跑 \(\text{spfa}\) 就行,答案即为 \(\max(dis[color[bar[i]]])\)
P2515 [HAOI2010] 软件安装
乍一看是道背包 \(\text{dp}\),但是有题目上给了软件安装的依赖,可以把每个依赖当做是一条 \(d_i\) 指向 \(i\) 的有向边,表示 \(d_i\) 被 \(i\) 限制,随后缩点,同时统计每个强连通分量里的花费总数和价值总数,建新图的时候统计没有入度的边,接着建一个超级源点,把没有入度的边都连上,这样这个图就成了一棵树,随后树形 \(\text{dp}\)
定义 \(dp_{u,i}\) 表示 \(u\) 的子树中内存限制为 \(i\) 时的最大价值,有状态转移方程:\(dp_{u,j + cost_u} = \max(dp_{u,j + cost_u - k} + dp_{v,j})\)
其中 \(j \in [0,m - cost_u]\) 倒序枚举,\(k \in [0,j]\) 正序枚举,意为减小 \(u\) 的子树的内存限制,去转移 \(v\) 的子树
poj Priest John's Busiest Day
乍一看没甚思路,发呆ing
突然想到约翰不能同时参与一场婚礼开头的仪式和结尾的仪式,所以断定是 \(\text{2 - SAT}\),经过几分钟的研究想好如何建边,然后就是用 \(\text{tarjan}\) 缩点后拓扑
11.19
%你赛,cqnk给的题
$35 + 25 + 0 + 0 = 60 \enspace \text{rank} \inf^+ $
11.20
依旧%你赛
$ 20 + 20 + 0 + 0 = 40$ 反方向的 \(\text{rank 1}\) ……
T1
正解区间 \(\text{dp}\)
定义 \(dp_{l,r}\) 表示区间 \([l,r]\) 的最终最大得分,由于dogenya
先手,可以得到转移方程 \(dp_{l,r} = min_{i=l}^{r}\{ max\{sum_l - sum_{i - 1} + dp_{i +1,r},sum_r - sum_{i + 1} + dp_{l,i-1} \}\}\),最后答案即为 \(dp_{1,n}\)
T2
很明显,这是一道树上问题 (废话
注意力惊人,我们可以发现,如果Venn
的攻击力越大,她所能够到达的点就越多,即我们可以二分Venn
的攻击力,每次 \(\text{dfs}\) 找到最小的 \(\geq W\) 的耗时
T3
没改
T4
简单明了,就是求无向图中四元环每个环的权值和
先从三元环计数起手 csdn blog
接着是四元环,按照度数从大到小建立有向边,对于一个四元环 \(\text{(u,v,x,w)}\),在新建的有向图中有两种可能:
\(\begin{cases} u \to v \to w,u \to x \to w \\ u \to v \to x \to w,u \to w\end{cases}\)
所以说,对于一个四元环,可以将其写成两个无向边 + 两个有向边的形式,即为:
\(u - v \to w \text{ and } u - x \to w\)
且保证 \(rank_u\) 最小,\(rank_w\) 最大
随后建新图,枚举 \(u\) 的每一条出边,如果 \(rank_u > rank_v\),从 \(u \to v\),反之 $ v \to u $
枚举 \(u\) 的出边 \(v\),再枚举 \(v\) 在新图的出边 \(x\),如果说 \(rank_x > rank_u\),说明 \(\exists \enspace (u,v_1,x) \text{ and } (u,v_2,x)\) 使得 \((u,v_1,v_2,x)\) 是一个四元环,此时 \(Answer \leftarrow in_x\),\(in_x\) 即为 \(x\) 的入度,然后 \(in_x + 1\),在每次枚举 \(u\) 节点时要先清空 \(in_x\)
接下来考虑如何计算权值,由于每次枚举的是 \((u,v_i,x)\),我们用 \(sum_x\) 表示当前 \(v_i\) 的点权总和,每次统计 \((u,v,x)\) 的和时要 \(\times in_x\),因为可能存在多组 \((u,v,x)\)
11.21
强连通 + 字典树专题
P8306 【模板】字典树
板子题没什么好说的,就是要注意数组大小,谨防开大 \(\text{MLE}\),开小 \(\text{RE}\) 的情况
话说这题开 \(\text{long long}\) 不让我过编译 (?
P4551 最长异或路径
P3119 [USACO15JAN] Grass Cownoisseur G
进食后人:\(\text{tarjan}\) 缩点之后建新图加边一定是 \(u,v\) 所在的强连通分量之间加边!!!
也是爱吃罚时:
如果不逆行,那就是缩点之后 \(\text{spfa}\) 跑最长路,但是要逆行一次,因为原图有向,所以干脆建新图时顺带建一个新图的反图,跑一次以节点 1
开始的最长路和以节点 1
结尾的最长路,边权即为每个 \(\text{scc}\) 的 \(\text{size}\),答案即为\(\max\{ dis1_u + dis2_v - size_1\}\),由于每次第一块强连通分量的 \(\text{size}\) 被算了两次,求 \(\text{Answer}\) 的时候减去一次就行,另外,由于可以选择不逆行,最终的答案应是 \(\max\{Answer,size_1\}\)
P6824 「EZEC-4」可乐
因为 \((a_i \oplus x) \leq k \text{ and } a_i,k \leq 10^6 < 2^{20}\),因此可以得到 \(x\) 与 \(a_i\) 在二进制下最多有 19
位都为一,每次拆分 \(a_i\) 的每一位插入字典树,直接暴力枚举 \(i \in [0,2^{20} - 1]\),每次处理 \(i\) 时考虑与 \(k\) 的每一位对接,如果 \(k\) 当前一位为 1
,那么子树可取,如果 \(i\) 当前位走不到子树就直接 \(\text{break}\) 掉,最后如果没有哪次走不到子树,要加上最后 \(root\) 的 \(size\),因为要求 \((a_i \oplus x ) \leq k\)
11.22 谈 OI
上午%你赛
\(\text{100 + 20 + 0 + 10 = 130pts}\) 反方向的 \(\text{rank 3}\)
你所热爱的,就是你的生活
——CCF_NOI
自己 出 搬了一套普转提的模拟赛,数据自己造了,题解自己写了,就是没人想做,呜呜呜,别人出题我出锅
邀请码:oi67
学 OI 的意义何在 (?
——逃离文化课计划
在于坚持一个你所热爱的事物,或许我不适合学 OI,但是我选择了她,就定要坚持到底,学不懂的新算法,每次考试反方向的 \(\text{rank}\),不会做只能看题解的题,极少能够自己做出来的考试题,教练也不知道我会在哪里脱节,纵使这些,只有静下心来学 OI,才能铸造属于自己的最高点,学 OI 的路也许对于别人来说是最短路,但 \(\text{dfs}\) 总能遍历完所有的点,那就意味着一切都还有希望
闲话
每日砷金(1/1)
11.25
感觉自己颓了一天
P1272 重建道路
早上来翻到这道放了许久的树形 \(\text{dp}\),发现上次提交半年之前,点开 \(\text{12 pts}\) 的代码发现边上没有边权当时我的代码输入了边权竟然有 \(\text{12 pts}\) ?
典型的树形背包,定义 \(f_{u,i}\) 表示以 \(u\) 为根的子树,当前保留 \(i\) 个节点去删边的最小值,记录每个点儿子的个数
初始化有:\(f_{u,1} = son_u\)
转移方程:\(f_{u,i} = \max\{f_{u,i - j} + f_{v,j} - 1\}\)
因为题目给的树是有根树,干脆直接转成有向边处理,只需要每次记录入度找根就行
答案:\(\min^n_{i=1}\{f_{i,q} + 1\}\)
P8819 [CSP-S 2022] 星战
不可以,总司令
45pts
直接 \(\text{Noooooooooooooooooooooooooooooo}\) 就行
100pts
随机化和哈希
P3384 【模板】重链剖分/树链剖分
树链剖分 \(\text{and Segmentree}\) 维护操作
P2194 HXY烧情侣
缩点,根据乘法原理答案方案数即为每个 \(\text{scc}\) 中的最小花费点的乘积
P2169 正则表达式
没看题解,挺板的
缩点之后新图上跑最短路
11.26
上午%你赛,开题之前先把昨天剩的 \(\text{96 pts}\) 代码调出来了
P3530 [POI2012] FES-Festival
很明显题目给的条件可以转化成差分约束系统,所以我们连边 \(u \xrightarrow{1} v,v \xrightarrow{-1} u,v \xrightarrow{0} u\)
注意力惊人得知 $ N \leq 600$,可以 \(\text{Floyd}\),若有负环则无解,由于两者之间的差值有固定的上下界,同时我们注意到,若两点在同一强连通分量中,可得这两点的关系是确定的,所以每个 \(\text{scc}\) 的答案为最短路的最大值 \(+1\)
但是这题比较特殊,由于边权有且仅有 \(\text{1,0,-1}\),最后最短路的权值也可以反映出不同取值的数量 \(-1\),所以最后答案要 \(+1\)
\(\text{96 pts}\) 调了两天の死因:
for(int k = 1;k <= n;k ++)
{
for(int i = 1;i <= n;i ++)
{
if(col[k] == col[i] && dis[i][k] != inf)
{
for(int j = 1;j <= n;j ++)
{
if(col[i] == col[j])
dis[i][j] = min(dis[i][j],dis[k][j] + dis[i][k]);
}
}
}
}
for(int k = 1;k <= n;k ++)
{
for(int i = 1;i <= n;i ++)
{
if(col[k] == col[i] && dis[i][k] != inf)
{
for(int j = 1;j <= n;j ++)
{
if(col[i] == col[j] && dis[k][j] != inf)
dis[i][j] = min(dis[i][j],dis[k][j] + dis[i][k]);
}
}
}
}
考的 \(\text{S+}\) 的难度,\(10 + 10 + 10 + 0 = 30 pts\),唐完了
T1
挂了……
题意就是给了一个森林,让你求加入边使得其成为一棵树上的最长链的大小
答案就是每棵树直径之和
T2 P9032 [COCI2022-2023#1] Neboderi
当然一开始想的是线段树 \(\text{ or ST}\) 表
当然我的线段树被卡成 \(\text{10 pts}\) 了,别人的 \(\text{ST}\) 表也被卡了……
然后就是每次枚举右端点 \(+1\) 时只需要更新 \(gcd\{l,r\}\) 就行,排序去重依旧一样可以维护
复杂度 \(O(n \log^2 V)\),\(\text{V}\) 为值域上限
T3 P8313 [COCI2021-2022#4] Izbori
注意力惊人得知:对于 \(\forall \enspace [l,r]\) 绝对众数只有一个,因此我们可以考虑对于 \(x\),有多少个区间可以使它产生贡献,记 \(s_i\) 表示 \(x\) 出现的前缀次数,可得下式:
\({s_r - s_{l - 1} > \frac{r - l + 1}{2}}\)
\(2s_r - r > 2s_l - (l - 1)\)
当然维护感觉很复杂 (废话
考虑每次维护新加上的数与之前的数产生的贡献
对于每个位置 \(i\) 以及它的后继 \(nxt_i\),一开始 \((i,nxt_i)\) 中并未存在顺序对
因此我们可以开一个在值域上的桶 \(buck\),去维护 \([l,r]\) 上与之前产生的顺序对,然后再对 \([l,r] + 1\)
维护顺序对分两种
对于 \(i < l - 1\):\(\displaystyle\sum_{i < l - 1}{(r - l + 1) \times buck_i}\)
对于 \(i \in [l - 1,r - 1]\):\(\displaystyle\sum_{i \in [l - 1,r - 1]}{(r - i + 1) \times buck_i}\)
所以我们分别维护一个区间加和区间二阶前缀和(题解写成二维前缀和了),线段树,启动!
放个码,这代码真的臭不可闻
code
#include<bits/stdc++.h>
#define int long long
#define lson rt << 1
#define rson rt << 1 | 1
#define int long long
using namespace std;
inline int read()
{
int f = 1,res = 0;
char ch = getchar();
while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
while(isdigit(ch)){res = (res << 1) + (res << 3) + (ch ^ 48);ch = getchar();}
return res * f;
}
const int MAXN = 2e5 + 10;
int n,a[MAXN],ind,m,Answer,opt,tot;
map <int,int> buck;
vector <int> vec[MAXN];
struct Segmentree{
int val,val_sum,lazy;
}tree[MAXN << 4];
inline void apply(int rt,int l,int r,int k)
{
tree[rt].val += (r - l + 1) * k;
tree[rt].val_sum += (r - l + 1) * (l + r) / 2 * k;
tree[rt].lazy += k;
}
inline void pushup(int rt)
{
tree[rt].val = tree[lson].val + tree[rson].val;
tree[rt].val_sum = tree[lson].val_sum + tree[rson].val_sum;
}
inline void pushdown(int rt,int l,int r)
{
if(tree[rt].lazy)
{
int mid = l + r >> 1;
apply(lson,l,mid,tree[rt].lazy);
apply(rson,mid + 1,r,tree[rt].lazy);
tree[rt].lazy = 0;
}
}
void modify(int l,int r,int ql,int qr,int rt,int k)
{
if(ql <= l && qr >= r)
return apply(rt,l,r,k);
int mid = l + r >> 1;
pushdown(rt,l,r);
if(ql <= mid)
modify(l,mid,ql,qr,lson,k);
if(qr > mid)
modify(mid + 1,r,ql,qr,rson,k);
pushup(rt);
}
int query(int l,int r,int ql,int qr,int rt,int opt)
{
int ret = 0;
if(ql <= l && qr >= r)
return opt == 1 ? tree[rt].val : tree[rt].val_sum;
int mid = l + r >> 1;
pushdown(rt,l,r);
if(ql <= mid)
ret += query(l,mid,ql,qr,lson,opt);
if(qr > mid)
ret += query(mid + 1,r,ql,qr,rson,opt);
return ret;
}
signed main()
{
n = read();
m = (n << 1);
for(int i = 1;i <= n;i ++)
{
a[i] = read();
if(!buck[a[i]]) buck[a[i]] = ++ ind;
a[i] = buck[a[i]];
vec[a[i]].push_back(i);
}
for(int t = 1;t <= ind;t ++)
{
int len = vec[t].size(),ret = 0;
modify(0,m,n - vec[t][0] + 1,n,1,1);
for(int i = 0;i < len;i ++)
{
int tmp = i * 2 + 2 - vec[t][i],nxt;
if(i == len - 1) nxt = n;
else nxt = vec[t][i + 1] - 1;
nxt = i * 2 + 2 - nxt;
int s1 = query(0,m,0,nxt + n - 2,1,1),s2 = query(0,m,nxt + n - 1,tmp + n - 1,1,1),s3 = query(0,m,nxt + n - 1,tmp + n - 1,1,2);
ret += s1 * (tmp - nxt + 1) + s2 * (tmp + n) - s3;
modify(0,m,nxt + n,tmp + n,1,1);
}
Answer += ret;
for(int i = 0;i < len;i ++)
{
int tmp = i * 2 + 2 - vec[t][i],nxt;
if(i == len - 1) nxt = n;
else nxt = vec[t][i + 1] - 1;
nxt = i * 2 + 2 - nxt;
modify(0,m,nxt + n,tmp + n,1,-1);
}
modify(0,m,n - vec[t][0] + 1,n,1,-1);
}
printf("%lld\n",Answer);
return 0;
}
T4
没改
P2656 采蘑菇
缩点建新图,在建新图的同时在同一 \(\text{scc}\) 中的两点重新加上恢复的蘑菇数,然后不在同一 \(\text{scc}\) 再加边,最后 \(\text{spfa}\) 最长路
P1434 [SHOI2002] 滑雪
真不是我水题,我真的在补dp
记忆化搜索
11.27
今日团建,教练带我们出去玩了一天
11.28
我考 \(\text{NOIP}\) %你赛,尊都假都(?
上午和高一高二一起联考 \(\text{NOIP}\),还有另外两所学校(qdez,sstf)
已暴零,求放过(我很弱的)
T1
注意力惊人手玩几组数据我们可以发现,最优的策略就是前 \(\frac{n}{2}\) 个小的数去匹配后 \(\frac{n}{2}\) 个大的数,若 \(n\) 为奇数 \(mid\) 取两次(\(\frac{n}{2} + 1 \enspace and \enspace \frac{n}{2}\))取 \(\min\) 就行
T2
最短路径树 + 并查集维护
最短路径树分为两种情况,一种是从 \(i\) 出发的最短路径即为一个环,另一种是从 \(i\) 出发是一条正常最短路径,但是从头到尾多了一条边,从而形成了一个环
所以并查集维护每个点的父亲,最后由于 \(N \leq 600\) ,可以类似 \(\text{Floyd} \enspace O(n^3)\) 枚举,对于每个点跑 \(\text{dijkstra}\)
放个码:
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
int n,x,sub,a[305][305],dis[305],fa[305];
bool vis[305];
int find(int x)
{
if(fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
inline int read()
{
int f = 1,res = 0;
char ch = getchar();
while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
while(isdigit(ch)){res = (res << 1) + (res << 3) + (ch ^ 48);ch = getchar();}
return res * f;
}
inline void dijkstra(int s)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s] = 0;
priority_queue < pii,vector<pii>,greater<pii> > q;
q.push(make_pair(dis[s],s));
while(!q.empty())
{
int u = q.top().second;
q.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int v = 1;v <= n;v ++)
{
if(a[u][v] > 0 && a[u][v] < inf)
{
if(dis[v] > dis[u] + a[u][v])
{
if(u != s) fa[v] = u;
dis[v] = dis[u] + a[u][v];
q.push(make_pair(dis[v],v));
}
}
}
}
}
signed main()
{
sub = read(),n = read();
for(int i = 1;i <= n;i ++)
{
for(int j = i + 1;j <= n;j ++)
{
x = read();
if(x == -1)
a[i][j] = a[j][i] = inf;
else
a[i][j] = a[j][i] = x;
}
}
for(int i = 1;i <= n;i ++) a[i][i] = 0;
for(int i = 1;i <= n;i ++)
{
int Answer = inf;
for(int j = 1;j <= n;j ++) fa[j] = j;
dijkstra(i);
for(int j = 1;j <= n;j ++)
{
if(fa[j] != j)
Answer = min(Answer,dis[j] + a[i][j]);
}
for(int j = 1;j <= n;j ++)
{
for(int k = 1;k <= n;k ++)
{
if(i != k && i != j && find(j) != find(k))
Answer = min(Answer,dis[j] + dis[k] + a[j][k]);
}
}
if(Answer < inf)
printf("%lld%c",Answer," \n"[i == n]);
else
printf("-1%c"," \n"[i == n]);
}
return 0;
}
/*
2 4
9 1 1
-1 1
-1
*/
然后就是在做各种 \(\text{dp}\),基本是 cf 1600~2000,毕竟窝的 \(\text{dp}\) 基础不好
CF2025D Attribute Checks
区间加我竟然一开始想用线段树,但是 \(n \leq 2 \times 10^6\) 以及巨大码量暂时给我劝退
记 \(dp_i\) 表示当前 \(I\) 值为 \(i\) 的最大分数,记 \(tot\) 为 \(r\) 中 0 的个数
- \(r < 0,\forall \enspace i \in [r,tot]\) 有转移 \(dp_i \leftarrow dp_i + 1\)
- \(r > 0,\forall \enspace i \in [0,r + tot]\) 有转移 \(dp_i \leftarrow dp_i + 1\)
- \(r = 0,\forall \enspace i \in [1,tot]\) : \(dp_i \leftarrow \max\{dp_i,dp_{i-1}\}\)
综上,我们可以差分维护区间加
CF2022C Gerrymandering
记 \(dp_{0/1,i}\) 表示当前行答案的最大值
有如下情况:
转移就很简单,这得放个码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 10;
int T,n;
char mz[3][MAXN];
inline bool check(char x,char y,char z){return (x == 'A') + (y == 'A') + (z == 'A') >= 2;}
signed main()
{
scanf("%lld",&T);
while(T --)
{
scanf("%lld%s%s",&n,mz[0],mz[1]);
vector < vector<int> > dp(3,vector <int> (n + 33,-0x3f));
dp[0][0] = 0;
for(int i = 0;i < n;i ++)
{
if(i % 3 == 0)
{
dp[0][i + 3] = max(dp[0][i + 3],dp[0][i] + check(mz[0][i],mz[0][i + 1],mz[0][i + 2]) + check(mz[1][i],mz[1][i + 1],mz[1][i + 2]));
dp[0][i + 1] = max(dp[0][i + 1],dp[0][i] + check(mz[0][i],mz[0][i + 1],mz[1][i]));
dp[1][i + 1] = max(dp[1][i + 1],dp[0][i] + check(mz[0][i],mz[1][i],mz[1][i + 1]));
}
if(i % 3 == 1)
{
if(i < n - 3)
{
dp[0][i + 3] = max(dp[0][i + 3],dp[0][i] + check(mz[0][i + 1],mz[0][i + 2],mz[0][i + 3]) + check(mz[1][i],mz[1][i + 1],mz[1][i + 2]));
dp[1][i + 3] = max(dp[1][i + 3],dp[1][i] + check(mz[0][i],mz[0][i + 1],mz[0][i + 2]) + check(mz[1][i + 1],mz[1][i + 2],mz[1][i + 3]));
}
dp[0][i + 2] = max(dp[0][i + 2],dp[0][i] + check(mz[0][i + 1],mz[1][i],mz[1][i + 1]));
dp[0][i + 2] = max(dp[0][i + 2],dp[1][i] + check(mz[0][i],mz[0][i + 1],mz[1][i + 1]));
}
}
printf("%lld\n",dp[0][n]);
}
return 0;
}
CF2014F Sheriff's Defense
没看题解
不用说肯定是树形 \(\text{dp}\),记 \(dp_{u,0/1}\) 表示选或不选 \(u\) 的子树的最大权值和,有转移:
- \(dp_{u,0} \leftarrow \displaystyle\sum_{v \in son_u}\max\{dp_{v,0},dp_{v,1}\}\)
- \(dp_{u,1} \leftarrow \displaystyle\sum_{v \in son_u}{\max\{dp_{v,0},dp_{v,1} - 2c\}}\)
答案即为 \(\max\{dp_{root,0},dp_{root,1}\}\)
$ 爱能克服远距离,多远都要在一起,宁愿换个方式至少还能遥远爱着你$
$ ——邓紫棋《多远都要在一起》$
11.29
先祝参加 NOIP 的同学 RP ++(除了我不去)
今日全天 DP
CF1970E3 Trails (Hard/Medium/Eazy)
如果当前位于湖,下次移动到某个木屋之后一定会返回到湖,所以只考虑前半天从另一个木屋走来是短路或长路,如果走的是短路则后半天长短皆可,反之只能走短路
记 \(dp_{i,0/1}\) 表示第 \(i\) 天后半天长短路皆可走或只能走短路,有转移:
可以写成矩阵:
\(\begin{pmatrix} \displaystyle\sum_{j = 1}^{m}{s_j(s_j + l_j)} & \displaystyle\sum_{j=1}^{m}{s_j^2} \\ \displaystyle\sum_{j=1}^{m}{l_j(s_j + l_j)} & \displaystyle\sum_{j=1}^{m}{s_jl_j}\end{pmatrix}\begin{pmatrix}dp_{i-1,0} & dp_{i-1,1} \\ dp_{i - 1,0} & dp_{i - 1,1}\end{pmatrix} = \begin{pmatrix}dp_{i,0} \\ dp_{i,1}\end{pmatrix}\)
所以可以矩阵快速幂优化
初始化 \(dp_{1,0} = s_1,dp_{1,1} = l_1\),最后答案即为对于每个树屋作为最后一个到达的答案之和
CF2005C Lazy Narek
记 \(dp_{i,j}\) 表示枚举到第 \(i\) 个字符串需要字符 \(j\),对于每个字符串 \(i\),我们先记录它能推到的位置 \(pos\),记分差为 \(dif\),遇到需要的字母 \(dif - 1\),最后如果 5 个字符都匹配上了就 \(dif + 10\),正好维护了两个操作,有转移:
\(dp_{i,pos} = \max\{dp_{i-1,j} + dif\}\)
答案即为 \(\max\{dp_{n,i}\},i \in [1,5]\)
CF2000F Color Rows and Columns
记 \(dp_{i,j}\) 表示前 \(i\) 个矩形获得 \(j\) 分的代价,有转移:
\(dp_{i,j} = \displaystyle\min_{k = 0}^{a_i + b_i}\{dp_{i-1,j - k} + calc(i,k)\}\)
其中 \(calc(i,k)\) 记为在 \(i\) 可以获得至少 \(k\) 分的代价,每次考虑矩阵 \(i\) 所剩的有效双边,取小的一边,另外一边 \(-1\)
答案即为 \(dp_{n,k}\)
CF1989D Smithing Skill
说句闲话:贪心可以过
首先完全可以想到一个贪心,每次铸造后再融掉肯定是最优的,每次 \(\Delta c_i\) 即为 \(a_i - b_i\),想要获取尽量多的经验, \(a_i - b_i\) 只能越小更优,所以每次去考虑对于每个 \(c_i\) 它最多能够铸造融掉多少次,对于每种武器,如果 \(a_j \leq c_i\),我们肯定会去操作它,然后再往后枚举以此类推,但是由于 \(10^6\) 的数据范围,\(O(n \times m)\) 想跑过去 \(\text{is impossible}\)
考虑优化
并不是优化策略,而是类似 dp 的优化
注意力惊人,我们发现,\(c_i\) 它满足单减,所以我们可以在值域上维护 \(c_i\) 的单调性,可以优先队列实现,且可以保证每种武器只会枚举一遍,但是,由于我们会在处理的过程中遇到相同的 \(c_i\) 多次,加上优化后仍然不行,因此我们用 \(map\) 代替优先队列,进行去重并统计每个 \(c_i\) 出现的次数,当然我用了 \(pbds\) 的哈希表,但是虽然 gp_hash_table 更快,但被卡了,因此换成了 cc_hash_table
虽然可以在 \(c_i\) 的值域上维护它,可是 \(c_i \leq 10^9\),由于 \(a_i,b_i \leq 10^6\),所以说第一次操作过后 \(c_i \leq 10^6\),所以对于 \(c_i > 10^6\) 预处理就可以了
不得不说竟然用一生之敌的算法 A 了这题
我一路向北,离开有你的季节,你说你好累,已无法再爱上谁,风在山路吹,过往的画面全都是我不对,细数惭愧,我伤你几回
—— 周杰伦《一路向北》
平静的十一月结束了呢,又是一年冬月,Hello,December