To_Heart—题集——Ladyきみは雨にけむる
1.AGC061C
link && submission
很神仙的一道题。先考虑所有的人都选择 \(a_i\) 时刻登记。那么对于一个人来说他变成 \(b_i\) 的时会增加贡献当且仅当 \([a_i,b_i]\) 之间有其他人被登记。
定义 \(C\) 数组, \(C_i\) 为 0 表示第 \(i\) 个人在 \(a_i\) 被登记,为 \(1\) 表示第 \(i\) 个人在 \(b_i\) 被登记。考虑将计数转换成对 \(C\) 计数,但是由上所述,有时候 C 数组不同所得到的序列却是相同的。于是考虑定义合法,将序列对应的最终顺序唯一化。合法不好算,转换成全部的减去不合法的。
定义不合法为存在一个 \(i\) 满足 \(C_i=1\) 但 \(C_i\) 变为 \(0\) 后最终顺序不改变。
还是由上所述,对于 i 来说他不合法说明 \([a_i,b_i]\) 没有其他人被登记了,也就是说对于任意的 j 来说,如果 \(a_j\in [a_i,b_i]\) 那么 j 就只能在 \(b_j\) 被登记才有可能让 i 不合法。\(b_j\in [a_i,b_i]\) 也是同理。
处理出每个位置 $ i $ 的 $ l_i $ 和 $ r_i $,其中 $ l_i $ 表示最小的满足 $ b_j > a_i $ 的 $ j $ ,$ r_i $ 表示最大的 $ j $ 满足 $ a_j < b_i $ 的 $ j $。所以当 $ i $ 不合法时有 $ C_{l_i} = C_{l_i + 1} = \cdots = C_{i - 1} = 0 $ ,$ C_{i} = C_{i + 1} = \cdots = C_{r_i} = 1 $,此时 $ l_i \sim r_i $ 的值是确定的。
显然 \(l,r\) 是单调的,然后如果对于 i,j 有 $[l_i,r_i] $ 和 \([l_j,r_j]\) 是相交的,那么两个位置一定不能同时不合法。
所以问题就转换成了选择 \(k\) 个区间,\(k\in [0,n]\),区间内不不相交,每个选择方案的容斥贡献是 \(2^x(-1)^y\),\(x\) 为未被覆盖的节点个,\(y\) 为区间选择个数。然后考虑刷表。
2.AGC016D
link && submission
最妙的是第一步转换。因为替换的数是所有的数异或起来的,所以你发现第一次以后每次生成的数一定在第一次操作后的 n+1 个数之中。
于是可以转换一下 ,一开始就把所有数异或起来的数放在 n+1。无解就很好判了。现在考虑操作次数,如果 \(a_i\) 和 \(b_i\) 不同就连边,发现每个连通块需要的操作次数是连通块大小+1。再特判一下第 n+1 个数就好了。
3.luoguP8519
link && submission
吐槽一下洛谷的查看代码机制,没过题不能看代码让我感到很傻逼所以代码放的是 loj 的提交。
这道题最核心的思想是倍增。考虑一开始将每个点看成一个连通块,每一轮就把相邻的两个连通块合在一起。如果连通块不能向外扩张了就永远不管他,否则每次连通块个数至少少一半所以就做完了。类似的思路是不是还有CSP2022的T1?但那个好像是单纯的折半。
4.CF735E
link && submission
dp[x][dep] 表示子树节点为 x,子树内距离最近为 dep。假设 y 是树上 x 的儿子节点枚举 dp[x][i] 和 dp[y][j]
- \(i+j<2k\) 那么 \(\min(i,j+1) <k\) 所以直接合法了
- \(i+j<2k\),有\(\max(i,j+1)>k\) 所以两边有够不到的但是可以先放在 \(max(i,j+1)\) 上下个儿子更新的时候再用。
5.CF1119G
link && submission
神奇构造题。有个显然的结论是答案的下界。考虑每次操作都一定能够产生实际贡献直到全部亖完,那么最少的轮数一定是 \(\left\lceil\dfrac{\sum hp_i}{m}\right\rceil\)。
直接开干。考虑一个一个干。假设现在把第一个怪兽干掉了,当前这轮用了 \(k_1\) 个兵。那么我们能不能直接让这 \(k_1\) 个人分成一组呢?这样每次就没有损失了。发现是可以的,我们吧每个怪兽最后一轮需要用的兵拿出来( \(k_i=hp_i\mod m\)),然后把这个数组排序,这时候第 \(i\) 个人每一轮剩下使用的会比第 \(i-1\) 个多 \(k_i-k_{i-1}\) 个所以把这些人又分在一个组。因为是取模所以说差分的和不会超过 m 所以这样分组一定是可以的。接下来填数就随便了。
6.CF526F
link && submission
考试的时候没做出来。真不应该。这种题做不出来这一年的 OI 生涯直接被否定了。
分治。但是还是非常精妙的第一步转换。把每一对点 \((x,y)\) 转换成 \(a_x=y\),好处问题就转换成了如果 \(a[l,r]]\) 中最大值减最小值的差为 \(r-l\) 那么这个区间就是合法的一个区间。现在的问题转变成了统计区间个数。你看变成一维的了!(要换个理解方式的话就是利用排序解决一维然后考虑另一维,这么看这道题居然很常规?!!)
接下来考虑分治 [l,r] 跨过 mid 的区间有多少个。根据最大/小值在 mid 左/右边一共有四种状态。用数组 \(Mx\) 和 \(Mn\) 表示 [i,mid] 或者 [mid,i] 的前/后缀最小/大值。如果最大值和最小值同侧,知道了 Mx_i,Mn_i,_i 那么相对应的 j 可以直接得到,判断是不是在另一侧而且是否满足 Mx_i 最大,Mn_i 最小就好了。较为麻烦的是 Mx 和 Mn 在两侧。不妨让我们来处理 Mx在左边,Mn 在右边的情况 那么 \(Mx_i-Mn_j=j-i\) 也就是 \(Mx_i+i=j+Mn_j\) 如果只是这样的话可以用桶存一边然后枚举另一边。但是有个限制是这种情况的贡献必须在 Mx在左边,Mn 在右边的 的前提下计算。为了满足前提,用一个双指针就好了。
7.AFC010E
因为后手只能操控互质的,所以先手可以决定的是不互质的内部的顺序。发现不互质的数内部是要从小到大的。那么直接这样连边就好了。然后拓扑排序的时候把队列改成大根堆就完了。代码很简单就不写了。
8.luoguP5992
你发现问题可以转换成数轴上面找零点。根据初一学过的知识我们知道最优点肯定在最中间,那么每个节点的取值范围一定是最中间的两个数。那么直接由儿子向父亲更新就好了。值得注意的是这个代价可能会包含与父亲的代价,所以接下来我们来证明关于父亲的代价可以直接在父亲上面算。很显然的发现是如果为了父亲而改变当前区间的选值范围,假设增加了 d,那么对于当前点来说他的移动导致距离增加肯定不少于 d 所以说一定不优。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[500005];
int r[500005],l[500005];
vector<int> v[500005];
int qwq[1000005];
long long ans=0;
void DFS(int x,int fa){
if(x<=m) return ;
for(auto y:v[x]) if(y^fa) DFS(y,x);
int tot=0;
for(auto y:v[x]) if(y^fa) qwq[++tot]=l[y],qwq[++tot]=r[y];
sort(qwq+1,qwq+tot+1);
l[x]=qwq[tot/2],r[x]=qwq[tot/2+1];
for(auto y:v[x]) if(y^fa){
if(l[y]<=l[x]&&r[x]<=r[y]) continue ;
ans+=min(abs(r[x]-l[y]),abs(r[x]-r[y]));
}
}
signed main(){
cin>>n>>m;
for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
for(int i=1,x;i<=m;i++) scanf("%d",&x),l[i]=r[i]=x;
if(n==m){
printf("%d\n",abs(r[1]-r[2]));
return 0;
}
DFS(m+1,0);
cout<<ans<<endl;
return 0;
}
9.luoguP5292
link && sumbission
通过观察有个先天性的预见就是预处理出来每一个询问。于是考虑找到最开始的相邻的回文串然后分别向外扩张。复杂度是 \(\mathrm{O(m^2)}\) 级别的。考虑优化边数。发现可以来回走所以并不用在意实际的距离儿只需要考虑奇偶性。然后你发现我们一开始的暴力有很多边其实是不需要走的,我们将颜色相同的缩成连通块,发现内部其实只要保证他联通就可以了其他边都是无所谓的。但是因为要考虑奇偶性所以还要看看连通块内部是不是二分图,如果是那么经过连通块的奇偶性是唯一的,否则奇偶性可能变化。针对这种变化我们对于连通块的点连一个自环。然后不同的连通块之间一条边就够了。这样边数就降到了 \(n^2\) 级别,暴力就可以通过了。
10.luoguP6168
这道题是真的神仙卧槽。IOI 的含金量!
第一步的转换就非常有含金量,考虑遍历完点是个类哈密尔顿回路问题,是难做的。但是我们可以把点转换成边!也就是从 s[i] 向 t[i] 连边。点数不能太多所以要离散化。这时候问题就变成把图补全成一个欧拉路!但是欧拉通路并不好处理,这是我们发现任何点到 INF 都没有代价所以可以直接变成欧拉回路了。
然后来统计代价。题目所说是减速需要代价,那么反映成边就是我加一条 i+1 -> i 的边就需要 1 的代价。但是 i -> i+1 反而不需要代价。这个离散化后遍历一下然后前缀和就可以求出来了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int INF=2e9;
int s[200005],t[200005];
int tot=0;
int lsh[400005],sum[400005],pre[400005];
int ans=0,n,m;
int Find(int x){
if(pre[x]!=x) pre[x]=Find(pre[x]);
return pre[x];
}
struct zz{
int x,y,w;
}q[400005];
bool operator <(zz x,zz y){ return x.w<y.w; }
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%lld%lld",&s[i],&t[i]),lsh[++tot]=s[i],lsh[++tot]=t[i];
n++,s[n]=INF,t[n]=1;
lsh[++tot]=s[n],lsh[++tot]=t[n];
sort(lsh+1,lsh+tot+1);tot=unique(lsh+1,lsh+tot+1)-(lsh+1);
for(int i=1;i<=n;i++) s[i]=lower_bound(lsh+1,lsh+tot+1,s[i])-lsh,t[i]=lower_bound(lsh+1,lsh+tot+1,t[i])-lsh;
for(int i=1;i<=tot;i++) pre[i]=i;
for(int i=1;i<=n;i++){
sum[s[i]]++,sum[t[i]]--;
if(Find(s[i])!=Find(t[i])) pre[Find(s[i])]=Find(t[i]);
}
for(int i=1;i<=tot;i++) sum[i]+=sum[i-1];
for(int i=1;i<=tot;i++) if(sum[i]){
if(sum[i]>0) ans+=sum[i]*(lsh[i+1]-lsh[i]);
if(Find(i)!=Find(i+1)) pre[Find(i)]=Find(i+1);
}
// printf("%d\n",ans);
int cnt=0;
for(int i=1;i<tot;i++) if(Find(i)!=Find(i+1)) q[++cnt]=(zz){i,i+1,lsh[i+1]-lsh[i]};
sort(q+1,q+cnt+1);
for(int i=1;i<=cnt;i++){
int x=q[i].x,y=q[i].y,w=q[i].w;
if(Find(x)==Find(y)) continue;
pre[Find(x)]=Find(y),ans+=w;
}
cout<<ans<<endl;
return 0;
}