9.22 小记
[COCI2019-2020#5] Putovanje
tag:树上差分
先处理出从 1 到 n 依次走完每条边会走多少次。然后对于每条边都看一下是买单程票还是多程票划算,统计一下就行了
[COCI2019-2020#5] Zapina
首先需要看清楚题面
然后就做出来了。
设 \(f_{i,j}\) 为给前 \(i\) 人分配了前 \(j\) 道题至少一个人开心的的方案数。
假设 \(i\) 开心:\(f_{i,j}=C_{j}^i\times (i-1)^{j-i}\)
假设 \(i\) 不开心:\(f_{i,j}=\sum _{k=0,k\not = i} f_{i-1,j-k}\times C_{j}^{k}\)
CF916E Jamie and Tree
哇是个厉害的分类讨论题!
其实之后的如果又说树上的换根问题的,都可以直接套这种分类讨论的结果
假设我们要操作的点为 \(x\) ,当前确定的根为 \(rt\) 。我们的操作都是对于子树操作,然后我们分如下几种情况讨论(查询和修改都差不多):
- \(x\) 与 \(rt\) 重合: 只需要操作整棵树就可以了
- \(rt\) 不在 \(x\) 的子树内:\(x\) 的子树还是它自己
- \(rt\) 在 \(x\) 的子树内:这种情况有点麻烦。可以发现其实是整棵树刨除 \(x\) 到 \(rt\) 路径上最靠近 \(x\) 的节点的子树。(嗯就自己画一画就知道了)找到这个节点可以用倍增。
然后这道题还要求 \(lca\)
还是分几种情况
- 当 \(x\) 和 \(y\) 在原树上都在根的子树内:\(lca\) 就是 \(x\) 和 \(y\) 的 \(lca\)
- 当 \(x\) 和 \(y\) 一个在子树内,另一个在子树外:显然根就是 \(lca\)
- 当 \(x\) 和 \(y\) 都在子树外:设 \(p=LCA(x,rt),q=LCA(y,rt)\) 取 \(p\) 和 \(q\) 的深度更大的那个
然后对于 \(x,y\) 的 \(lca\) ,我们只需要求 \(LCA(x,rt),LCA(y,rt),LCA(x,y)\) 的深度更大的那个。
[CSP-S2019] 划分
我不行了,不到为啥,现在好困啊
困麻了
这题的话,更像是用一些决策单调性?
\(O(n^3)\) 的 dp 还是很好想的吧
然后还有一个显而易见的结论:\((a+b)^2\geq a^2+b^2\) ,所以在能保证性质的情况下,最后一段要尽可能的小,如果你找到 \(f_j\) 转移到 \(f_i\) 的最优决策点 \(k\),那么 $f_{j-1} $ 到 \(f_i\) 的最优决策点一定在 \(k\) 左边一个尽可能近的位置。 这样是 \(O(n^2)\) 的(64 pts)
然后考虑继续优化。设 \(s_i\) 为到 $i $ 的前缀和。如果 \(i\sim j\) 可以转移的话, \(s_i-s_j\geq s_j-s_{lst_j}\) ,\(lst\) 代表对于某一点的最优决策点。化简一下得到 \(2s_j-s_{lst_j}\leq s_i\),这样我们就把与 \(j\) 有关的东西扔到一起啦!这样就是单调队列可以优化的形式。单调队列可以这样优化是因为 \(s_i\) 单调递增,无法成为 \(i\) 的决策的点一定无法成为 \(i+1\) 的决策,所以决策点一定是单调递增的。
然后细节上的话,不要用 __int128
开数组,会炸的。但是19年根本就不让用啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1<<30;
const int maxn=40000002;
int n;int a[maxn],b[maxn];
int p[100002],l[100002],r[100002];
void init(int typ)
{
if(!typ)for(int i=1;i<=n;i++) scanf("%d",&a[i]);
else
{
int x,y,z,m;scanf("%d%d%d%d%d%d",&x,&y,&z,&b[1],&b[2],&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&p[i],&l[i],&r[i]);
for(int i=3;i<=n;i++) b[i]=((ll)x*b[i-1]+(ll)y*b[i-2]+z)%mod;
for(int j=1;j<=m;j++)
{
for(int i=p[j-1]+1;i<=p[j];i++)
a[i]=(b[i]%(r[j]-l[j]+1))+l[j];
}
}
}
int q[maxn];int lst[maxn];
ll s[maxn];ll g(int x){return 2*s[x]-s[lst[x]];}
template<typename _T>
void write(_T x)
{
if(x<0) {putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int main()
{
int typ;scanf("%d%d",&n,&typ);
init(typ);
for(int i=1;i<=n;i++)
s[i]=s[i-1]+a[i];
int L=1,R=0;q[++R]=0;
for(int i=1;i<=n;i++)
{
while(R>L&&g(q[L+1])<=s[i]) L++; //要找到最靠后的合法的解
lst[i]=q[L];
while(R>=L&&g(q[R])>=g(i)) R--;// g越小越容易成为决策,所以让队列单调递增
q[++R]=i;
}
__int128 xx=0;
for(int i=n;i>0;i=lst[i])
{
xx+=(__int128)(s[i]-s[lst[i]])*(s[i]-s[lst[i]]);
}
write(xx);
}
[SBCOI2020] 一直在你身旁
就好难想的单调队列 awa
对于 \(O(n^3)\) 的就是区间 DP ,设 \(f_{i,j}\) 表示如果已经确定答案在 \(i\sim j\) 的范围内,确定具体的答案需要的最小花费。
\(f_{i,j}=\min\{\max\{f_{i,k},f_{k+1,j}\}+a_k\}\)
含义就是要找到一个点能将区间分成两段,来确定答案在哪一边,然后取两边的 \(\max\) 是考虑最坏情况。
然后考虑怎样优化。
可以发现, \(f_{i,k}\) 随着 \(k\) 的增大而增大,\(f_{k+1,j}\) 随着 \(k\) 的增大而减小。于是对于 \([i,j]\) ,一定有一个分割点使得在 \(K\) 左边的 \(k\) 都是 \(f_{k+1,j}\) 更大,右边的 \(k\) 都是 \(f_{i,k}\) 更大一点 。所以我们要找一下这个分割点。
然后我们可以发现,当 \(r\) 固定时,\(l\) 越往左,\(K\) 也越往左。(还挺好理解的吧)所以我们可以先枚举 \(r\) ,再从大到小枚举 \(l\) ,用一个标记指针就能找到分割点了。
然后是怎么找左右两侧的贡献。
考虑 \(f_{i,k}>f_{k+1,j}\) 的情况,\(f_{i,k}+a_k\) 会成为答案,发现 \(f_{i,k}+a_k\) 一定单调递增,所以只需要取最左面能取到的 \(k\) 就行了
考虑 \(f_{i,k}\leq f_{k+1,j}\) 的情况,这次\(f_{k+1,j}+a_k\)不一定单调递减,但 \(f_{k+1,j}+a_k\) 在 \(r\) 固定时只和 \(k\) 有关,需要维护一个单调队列来维护出最小值。
#include <bits/stdc++.h>
using namespace std;
const int maxn=8002;
typedef long long ll;
int n;ll a[maxn];
ll f[maxn][maxn];
int q[maxn];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int r=2;r<=n;r++)
{
int L=1,R=1;q[L]=r;int k=r;
f[r-1][r]=a[r-1];f[r][r]=0;
for(int l=r-2;l>=1;l--)
{
while(k>l&&f[l][k-1]>f[k][r]) k--;
f[l][r]=f[l][k]+a[k];
while(L<=R&&q[L]>=k) L++;
if(L<=R) f[l][r]=min(f[l][r],f[q[L]+1][r]+a[q[L]]);
while(L<=R&&f[q[R]+1][r]+a[q[R]]>=f[l+1][r]+a[l])
R--;
q[++R]=l;
}
}
printf("%lld\n",f[1][n]);
}
}
困麻了
废话
昨天做了个噩梦,梦见被亲人追杀(?)
反正是梦很长很长,梦里的我都累了,醒来的我更累,感觉像根本没睡
然后下午几乎睁不开眼睛
不管了,今天好好休息
成 为不了光鲜模样 就无法悲伤
面 对不了真实自我 就不必逞强
不堪的独白 被负罪感割开
抹消 我存在 的意外
解脱我 仅剩一丝色彩
不明白 潦草的结局 封锁角色
不安 飘荡在 决策以外