11 月做题记录(一)
这个题单用来存放一些宝宝题,不是特别宝宝的放在隔壁了。
11.2 迁徙
已通过。
题目链接。
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=2e6+7;
typedef long long ll;
int n;
ll d;
ll x[N];
int f1[N],f2[N],dp[N];
int fir[N],las[N],mx;
int c1[N],c2[N];
int d1[N],d2[N],h[N];
vector<int> g1[N],g2[N];
ll ans;
void solve(int c){
if(c==mx) return;
for(int i=fir[c]+1;i<=las[c];i++){
if(i>fir[c]+1&&f1[i]==fir[c]){
ans+=c2[fir[c]+1];
if(f2[fir[c+1]]==fir[c]+1) ans+=d2[fir[c+1]];
}
if(f1[i]==las[c+1]){
if(f1[las[c+1]]==fir[c+1]) ans+=d2[fir[c]];
else ans+=c2[fir[c]];
}
}
}
void subt(){
cin>>n>>d;
for(int i=1;i<=n;i++) cin>>x[i];
for(int i=1;i<=n;i++){
f1[i]=lower_bound(x+1,x+n+1,x[i]-d)-x;
f2[i]=upper_bound(x+1,x+n+1,x[i]+d)-x-1;
}
x[n+1]=x[n];
mx=0;
for(int i=n;i>=1;){
int f=i;
if(x[i]>=x[i+1]-d){
while(f>=1&&x[f]>=x[i+1]-d) f--;
}else{
while(f>=1&&x[f]>=x[i]-d) f--;
}
mx++;
fir[mx]=f+1,las[mx]=i;
for(int j=fir[mx];j<=las[mx];j++) dp[j]=mx;
//cout<<fir[mx]<<" "<<las[mx]<<endl;
i=f;
}
//for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
//cout<<endl;
for(int i=1;i<=n;i++) g1[i].clear(),g2[i].clear();
for(int i=1;i<=n;i++){
g1[f1[i]].push_back(i);
g2[f2[i]].push_back(i);
}
for(int i=n;i>=1;i--){
d1[i]=c1[i]=h[i]=0;
for(int j:g1[i]){
if(dp[i]!=dp[j]) c1[i]++,d1[i]+=(f2[i]==j?d1[j]+h[j]:0);
else if(i!=j) h[i]++;
}
d1[i]+=c1[i];
}
for(int i=1;i<=n;i++){
d2[i]=c2[i]=0;
for(int j:g2[i]) if(dp[i]!=dp[j]) c2[i]++,d2[i]+=(f1[i]==j?d2[j]:0);
d2[i]+=c2[i];
}
ans=0;
for(int i=1;i<=mx;i++) solve(i);
for(int i=1;i<n;i++) if(dp[i]==dp[i+1]){
int a=0,b=0;
for(int j:g1[i]) if(dp[i]!=dp[j]){
a++;
if(f2[i+1]==j) a+=d1[j]+h[j];
}
for(int j:g2[i+1]) if(dp[i+1]!=dp[j]){
b++;
if(f1[i]==j) b+=d2[j];
}
ans+=1ll*a*b;
}
cout<<ans<<endl;
}
}
int c,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c>>t;
while(t--) tymxz::subt();
return 0;
}
:::
11.2 研发基地*
已通过。
题目链接。
二分答案,现在一个点可以覆盖距离它 \(\le x\) 的点。
在树上断掉一条边,分成的两个连通块独立求。
考虑怎么求,有一个比较简单的方法:考虑所有叶子,叶子都要求它的父亲距离 \(\le x-1\) 的地方有点。
那么答案就是删除 \(x\) 轮叶子后剩下的点数。
考虑一个点何时会被删。以其为根的子树有很多种,取高度最小的那种,如果高度 \(\le x\) 那就会被删。这是一个下界,我们觉得这也是上界。

直径中点一定在当前根的长链上,显然有两个部分:直径中点的祖先,和其他点。其他点的贡献显然可以直接计算,直径中点到根的直链上只有一段是合法的。
那么我们记录子树里黑点的最高处,如果发现短儿子有限制,现在的黑点够不到,那你就直接把黑点往上抬!虽然这很弱智,但是确实是对的。当然你不能抬到这个点上。
设 \(f\) 是根不一定是黑点的答案,\(g\) 是子树全部是子树的答案。\(h\) 是长儿子黑点的最高处。除了这个还需要维护长儿子的 \(f\) 和 \(g\),短儿子的最大 \(d\),短儿子的 \(g\)。
处理子树的补,直接换根 dp 即可。有结合律,维护前后缀信息。
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=1e6+7;
struct info{
int d1,d2;
int f,g;
int h;
};
info operator + (info a,info b){
if(a.d1<b.d1) swap(a,b);
a.d2=max(a.d2,b.d1);
a.g+=b.g;
return a;
}
int x;
info turn(info a){
info c;
c.d2=-1,c.g=0;
c.d1=a.d1+1,a.d2++;
c.g=a.g+(c.d1>=x);
c.f=0x3f3f3f3f,c.h=0;
if(a.d2<x){
a.h++;
int to=x-a.d2;
c.f=(a.h>to?a.h-to:0)+a.f;
c.h=min(a.h,to);
}
if(c.f>=max(1,c.g)) c.f=max(1,c.g),c.h=0;
return c;
}
int n,k;
vector<int> g[N],t[N];
int dfn[N],tot;
void dfs(int u,int fa){
dfn[++tot]=u;
for(int v:g[u]){
if(v==fa) continue;
dfs(v,u);
t[u].push_back(v);
}
}
info z=(info){-1,-1,1,0,0};
info tmp1[N],tmp2[N];
info dp1[N],dp2[N];
void solve1(int u){
dp1[u]=z;
for(int v:t[u]){
dp1[u]=dp1[u]+dp1[v];
}
dp1[u]=turn(dp1[u]);
}
bool solve2(int u){
if(dp1[u].f+dp2[u].f<=k) return 1;
int s=(int)(t[u].size());
if(s==0) return 0;
tmp1[0]=z;
for(int i=1;i<=s;i++) tmp1[i]=tmp1[i-1]+dp1[t[u][i-1]];
tmp2[s+1]=z;
for(int i=s;i>=1;i--) tmp2[i]=tmp2[i+1]+dp1[t[u][i-1]];
for(int i=1;i<=s;i++) dp2[t[u][i-1]]=turn(tmp1[i-1]+tmp2[i+1]+dp2[u]);
return 0;
}
bool chk(int mid){
x=mid;
for(int i=n;i>=1;i--) solve1(dfn[i]),dp2[i]=z;
for(int i=1;i<=n;i++) if(solve2(dfn[i])) return 1;
return 0;
}
void subt(){
cin>>n>>k;
for(int i=1;i<=n;i++) g[i].clear(),t[i].clear();
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
tot=0;
dfs(1,0);
int lb=0,ub=n,ans=1;
while(lb<=ub){
int mid=(lb+ub)>>1;
if(chk(mid)){
ub=mid-1,ans=mid;
}
else{
lb=mid+1;
}
}
cout<<ans<<endl;
}
}
int c,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c>>t;
while(t--){
tymxz::subt();
}
return 0;
}
:::
11.3 Count of Euclid Function Call
题目链接。
\(n=0\) 时特判。
\(n>0\) 时,辗转相除可以倒过来,而不需要考虑 \(n=m\) 时无法转移的情况。
手玩一下发现最终得到的 \((n',m')\) 中,\(m'=cn+dm\)。要求 \(cn+dm\le N\) 的数量,注意不是 \((c,d)\) 的数量而是有多少种方法到达 \((c,d)\)。
\((c,d)\) 的合法转移是 \((c,d)\to (d,c+kd)\),其中 \(k\ge 1\)。从 \((0,1)\) 开始。
发现对于 \((c,d)\) 倒过来还原,只要 \(\gcd(c,d)=1\),大部分情况下都能唯一还原,只有最后几步是 \((0,1,1,a,...)\) 还是 \((0,1,a,...)\)。特判即可。
问题还是变成 \(c<d\),\(\gcd(c,d)=1\) 且 \(cn+dm\le N\) 的数量。莫反即可。
11.3 货运通道
题目链接。
题意:一条链上有 \(n+2\) 个点,\(n+1\) 条边。
连接 \(i\) 和 \(i+1\) 的边有边权 \(B_i\)。规定 \(B_0=B_{n}=\infty\)。
对于 \(1\le i\le n\),\(A_i\) 表示到达点 \(i\) 时货物量增加 \(A_i\)。任何时候货物量不能 \(<0\)。
经过第 \(i\) 条边时货物量超过 \(B_i\),多余的货物会被丢弃。可以花费一点代价给任意一个 \(B_i\) 增加 \(1\)。
有两种操作,第一种是单点给 \(A_i\) 加上一个正数,第二种是查询初始货物为 \(k\) 从点 \(0\) 出发到达 \(n+1\) 的最小代价。
设 \(h_i\) 表示至少要携带多少货物到达点 \(i\)(还没有卸货)才能度过 \(i\to n+1\)。
显然 \(h_{n+1}=0\),且 \(h_i=\max(h_{i+1}-A_i,0)\)。若 \(B_i<h_{i+1}\) 则一定要付出 \(h_{i+1}-B_i\) 的代价来抬高。
考虑如何维护单点给 \(A_i\) 加上正数。显然 \(h_i\) 只减不增,付出的代价也只减少。
先对 \(h_i\) 减少 \(y\),再对 \(h_{i-1}\) 减少 \(h_i\) 实际减少的量。类似单侧递归,先递归右侧,如果右侧减少完还剩 \(x>0\),就再到左侧减少。对整个区间都 \(\ge y\) 的情况特判。
问题变成在 \(h_{i}-B_{i-1}\) 的序列上做区间减法,但是要对 \(0\) 取 \(\max\)。
维护非 \(0\) 最小值,如果一个区间内有会被减完的,就要递归下去,显然复杂度是 \(O(n\log n)\) 的。
这玩意显然没有细节,懒得写了。
11.3 Splitting Haybales P
已通过。
题目链接。
题意相当于每次到 \(i\) 时,如果 \(x>0\) 则 \(x\leftarrow x-a_i\),如果 \(x\le 0\) 则 \(x\leftarrow x+a_i\)。
在 \(l\) 开始前加入询问,在 \(r\) 结束时删除询问。把平衡树按 \(0\) 分裂,两边打加法 tag 即可。
但是对于相等值的处理需要思考一下:要么给相等值赋一个第二关键字,要么把相等值缩起来使得不存在相等值。
11.3 树的价值*
已通过。
题目链接。
首先考虑一个 \(O(n^3)\) 的做法。设 \(f_{i,j,k}\) 表示 \(i\) 子树里的 \(\mathrm{mex}\) 恰好是 \(j\),\(i\) 子树里有 \(k\) 个点还没有确定填什么数。
转移时会获得 \(i\) 的儿子中最大的 \(j\) 和所有的 \(k\),并且根也还没有确定填什么数。然后再计算 \(i\) 的 \(\mathrm{mex}\) 是多少:在儿子的基础上,每消耗一个未填的数就可以令其增加 \(1\)。
提前钦定 \(i\) 的儿子中 \(j\) 最大的是 \(\mathrm{son}_i\),用其剖分,如果钦定错误显然不优秀。
上面的那个 dp,在根处会获得一个新的数待填,假设点 \(u\) 在其祖先 \(\mathrm{anc}\) 时被填上数,贡献会持续到链顶。
设 \(f_{i,j,k}\) 表示点 \(i\) 的祖先上,最长的同链连续段长度为 \(j\),当前的同链连续段长度为 \(k\),子树内的答案。做到 \(O(nm^2)\)。
设 \(f_{i,j}\) 表示 \(i\) 的祖先上最长的同链连续段长度为 \(j\),\(i\) 到父亲的边是轻边,子树内的答案。
显然当子树高度不足时,整个子树的答案就是 \(\mathrm{siz}_i\times j\) 了,可以减枝。
找到 \(i\) 子树里的点 \(v\),且 \(i\) 到 \(v\) 之间的点数恰好是 \(j\)。钦定 \(i\) 到 \(v\) 是一条长链,那么这条链旁边的轻边贡献都是 \(f_{*,j}\),而 \(v\) 的子树的贡献是 \(g_{i,j}\)。
\(g_{i,j}\) 意味着 \(i\) 所在的链长度为 \(j\),转移时直接选择一个点从 \(g_{\mathrm{son}_i,j+1}\) 转移来,其他点从 \(f_{*,j}\) 转移来即可。
考虑如何在线算完所有的数值。也就是所有轻边的 \(f\) 之和。要对每一对 \((i,v)\) 做计算。
要长链剖分或者树状数组。
11.4 Equal Mex*
已通过。
题目链接。
介绍一个简单的极小 mex 区间求法。这篇博客。这里复读一下。
从左到右扫描线,设当前扫到 \(i\)。
维护 \(\mathrm{las}_x\) 表示考虑了 \(1\sim i\) 的数,\(x\) 这个数最后一次出现是在 \(\mathrm{las}_x\)。对 \(\mathrm{las}\) 建立线段树。
右端点是 \(i\) 的第一个极小 mex 区间的左端点是 \(j\),且 \([j,i]\) 的 mex 要 \(>a_i\)。\(j\) 显然就是 \(\mathrm{las}\) 数组上 \([0,a_i]\) 的最小值。
接下来要找其他的极小 mex 区间,设上一个极小 mex 区间的 mex 是 \(w\),则这一个极小 mex 区间的 \(j\) 显然是 \(\mathrm{las}_w\)。
然后如果 \(j\) 小于等于 \(a_i\) 上一次出现的位置,则同样不是极小 mex 区间,查询 mex 使用线段树二分,写个 zkw 就能维护。
11.4 古代机器 2
已通过。
题目链接。
题解待补。
11.6 大订单
已通过。
题目描述:给你一个长度为 \(n\) 的序列 \(a\),对于 \(1\le i\le k\),请你把这个序列切成 \(i\) 段并重新排列,求新序列最长不下降子序列的长度的最大值。
\(1\le n\le 1000\),\(1\le k\le 100\),\(0\le a_i\le 5\)。
假设 \(f_{i,j,x,y}\) 表示保留 \([i,j]\) 中值域在 \([x,y]\) 的数后,最长不下降子序列的长度。
把序列切成几段之后,每一段都可以指定一个 \([x,y]\)。这些区间只在端点处相交,没有包含关系,也不能重叠。
直接刻画不好刻画,考虑先枚举端点集合 \(S\subseteq \{0,1,2,3,4,5\}\),重新标号之后,区间变成了两类:\([i,i]\) 形和 \([i,i+1]\) 形。那条件就很简单了,\([i,i+1]\) 型每种只出现一次即可。
枚举上一段的末尾 \(l\),记 \(g_{i,j,T}\) 表示前 \(i\) 个数分 \(j\) 段,已选的 \([i,i+1]\) 集合为 \(T\)。复杂度为 \(O(n^2k)\) 乘上状压复杂度。
考虑优化掉一个 \(n\),发现根本没必要记上一段的末尾,设 \(h_{i,j,x,y,T}\) 表示继承了 \(l\) 处的 \(g\) 数组后任务为选取 \([x,y]\) 的数,并拿取最长不下降子序列。转移完成任务即可。
显然 \([x,y]\) 的个数只有 \(2v\) 个,而状压复杂度也最多 \(3^v\)。而且 \(S\) 不取 \(0\) 和 \(5\) 不优,做一些剪枝就能过了。
11.6 人造序列题
题目链接。
这题也有一个相对平凡的 Clubstep 做法,有时间再补。
把问题转化成给你一个序列 \(a\),你可以给第 \(i\) 个位置 \(+2\),如果存在第 \(i+1\) 个位置可以让它也 \(+1\),让整个序列非负。
显然这是一个贪心题,从左到右依次操作使得每个位置非负即可。
单点修改时,设修改量为 \(\Delta\),下一个位置的修改量大致是 \(-\frac{\Delta}{2}\),那么我们做完了。。。吗?
不难发现还有上取整,有时上取整会反馈到 \(\Delta\) 上,那么如果 \(|\Delta|=1\),可能会一直传递下去。
形式化,设 \(b_{i}\) 表示点 \(i\) 收到上一个位置的多少数,转移是 \(b_i=\max(\lceil \dfrac{-a_{i-1}-b_{i-1}}{2}\rceil,0)\)。
设 \(d_{i-1}\) 是 \(a_{i-1}+b_{i-1}\) 的变化量,那么 \(d_i=\max(\lceil \dfrac{-a_{i-1}-b_{i-1}-d_{i-1}}{2}\rceil,0)-\max(\lceil \dfrac{-a_{i-1}-b_{i-1}}{2}\rceil,0)\)。
若 \(a_{i-1}+b_{i-1}\) 是奇数,且 \(d_{i-1}=1\),那么 \(d_{i}=-1\)。但是若 \(d_{i-1}=-1\) 则 \(d_i=0\)。
若 \(a_{i-1}+b_{i-1}\) 是偶数,且 \(d_{i-1}=-1\),那么 \(d_{i}=1\)。但是若 \(d_{i-1}=1\) 则 \(d_i=0\)。
那么在线段树上,如果操作考虑到了整个线段树区间,且这个区间是奇偶交替的,且 \(d_{i-1}=1\),那么就可以快速跳过,并且给 \(b_i\) 奇数位 \(-1\) 偶数位 \(+1\)。
如果这个区间是偶奇交替的,且 \(d_{i-1}=-1\),也可以快速跳过。随便在线段树上实现一下就好了。
11.7 houran 和芒果
题目链接。
\(m=n\) 时,每个点选择到儿子的边中最大的,树上的每个叶子恰好对应一条链。
一般情况,相当于选择 \(m\) 个叶子,对于一个非叶子,能够选择一个儿子,当其子树里有被选的叶子。所以直接选能选择的儿子里最大的。
假设有 \(4\) 条到儿子的边。设到儿子的边从小到大排序后是 \(w_1,w_2,w_3,w_4\)。构造如下的检测器:
- 若 \(1,2,3,4\) 中存在点,则 \(+w_1\)。
- 若 \(2,3,4\) 中存在点,则 \(+w_2-w_1\)。
- 若 \(3,4\) 中存在点,则 \(+w_3-w_2\)。
- 若 \(4\) 中存在点,则 \(+w_4-w_3\)。
建 \(4\) 个虚点表示这些决策,问题转化为选择 \(m\) 条链覆盖的点的权值最大化。这是模版题。
11.10 小丑大师的荣耀*
已通过。
题目链接。
将一张卡牌 \((x,y)\) 建边,每个连通块是独立的。
如果一个连通块是基环树,显然可以让整个连通块都被覆盖。如果一个连通块是树,会有一个点没法被覆盖。所以只有 \([mn,mx]\subseteq [l,r]\) 时才会不合法。
首先我们应该思考如何找出所有限制。对于时间维线段树分治,把删除改成撤销,可以找出所有限制。
注意这样会找到 \(O(n\log n)\) 个限制,但是把一些限制合并后,有效的限制其实只有 \(O(n)\) 个。
然后考虑给出 \(n\) 个在 \([tl,tr]\) 时间出现的限制 \([l,r]\),查询某个时刻 \(t\) 时,有多少个 \(s\le L\le t\) 且 \(u\le R\le v\) 满足 \([L,R]\) 不包含任何一个限制。
设 \(f_i\) 表示左端点为 \(i\),最小的非法右端点是 \(f_i\)。初始时 \(f_i=m+1\),显然加入一个限制时 \(f[1,l]\) 需要对 \(r\) 取 \(\min\)。\(f\) 是单调递减的。
把查询差分成 \([s,t],[u,m]\) 的形式。显然合法的个数是 \(\displaystyle \sum_{i=s}^t \max(f_i-u,0)\),因为 \(f\) 是单调递增的,容易查询。
再次线段树分治,并用线段树维护 \(f\)。操作撤销就记录操作栈即可。
线段树操作只需要记录区间最大值,最小值,防止无用递归即可。
第二种做法,考虑把 \(f\) 看成 \(f'\) 的后缀最小值,\(f'\) 的删除,只需要维护单点修改。实际上限制不会重复,如果限制重复了就用堆支持删除。这是楼房重建板子。
但是你真的会楼房重建吗?设 \(w(l,r,x)\) 表示区间 \([l,r]\) 从右侧来了一个对 \(x\) 取 min,此时的和。
若 \(\min(f[mid+1,r])\le x\),则 \(w(l,r,x)=w(l,mid,\min(f[mid+1,r]))+w(mid+1,r,x)\)。其中 \(w(l,mid,\min(f[mid+1,r]))=w(l,r,\inf)-w(mid+1,r,\inf)\)。
若 \(\min(f[mid+1,r])> x\),则 \(w(l,r,x)=w(l,mid,x)+(r-mid)x\)。
改成 \(-u\) 之后,如果 \(x\le u\) 就直接退出,如果 \(\min(f[l,r],x)\ge u\) 就取一整段。
td ARC Stamp
题目链接。

浙公网安备 33010602011771号