省选培训day1
5.30 省选集训题解
T1
1.1 题目概要
给你一个 01 串 \(S1\),你需要输出一个最短的 01 串 \(S2\) ,使得 \(S2\) 在 \(S1\) 从未出现过。
要求字典序最小。
数据范围
\(30\%,|S1|\le1000\)
\(50\%,|S1|\le1000000\)
以上时间范围 \(0.2\) 秒,对于另外 \(20\%\) 的数据,时限为 \(2\) 秒。
对于 \(100\%\) 的数据,满足 \(1\le|S1|\le16777216\)
观察到 \(|S2|\) 的长度不会超过 \(\log_{|S1|}\) ,所以我们考虑 dp,直接先预处理出所有该长度的字符串,至于标记我们考虑 2 进制压 10 进制
#include<bits/stdc++.h>
#include<vector>
#define uint unsigned int
#define ll long long
#define ull unsigned long long
#define dd double
#define N 100000000
#define M number
using namespace std;
#pragma GCC optimize(“Ofast”)
const int INF=0x3f3f3f3f;
int lens1,len,ans[50],tail,biao;
bool vis[N];
char s1[N];
vector<int> v''
inline void prework(){
len=min(lens1,biao);
int mod=(1<<len),num=0;
for(int i=1;i<=len;i++){
num<<=1;
num+=(s1[i]-48);
}
vis[num+mod]=1;
for(int i=len+1;i<=lens1;i++){
num&=(mod>>1)-1;
num<<=1;num+=s1[i]-48;
vis[num+mod]=1;
}
}
int main(){
scanf("%s",s1+1);
lens1=strlen(s1+1);
biao=20;if(lens1>1000000) biao=25;
prework();
for(int i=len;i>=2;i--){
for(int j=(1<<i);j<=(1<<(i+1))-1;j++){
int mod=(1<<i);
if(!vis[j]) continue;
int now=j&(mod-1);
vis[(now>>1)+(1<<(i-1))]=1;
vis[(now&((mod>>1)-1))+(1<<(i-1))]=1;
}
}
int w=1;
for(int i=2;i<=(1<<(len))-1;i++){
if(i==(1<<(w-1))<<1) w++;
if(!vis[i]){
while(i>1){
ans[++tail]=i&1;
i>>=1;
}
for(int j=tail;j>=1;j--){
printf("%d",ans[j]);
}
return 0;
}
}
}
T2
给 \(n\) 条线段,有 \(m\) 个矩阵,每一次给出一个边平行于坐标轴的矩阵,问每条与矩阵有交的线段与矩阵交的长度之和与所有线段的长度之和的比值,要求输出与标准答案的相对误差不超过 \(10^{-6}\)。
线段以 \(x_1,y_1,x_2,y_2\) 的形式给出,表示以 \((x_1,y_1),(x_2,y_2)\) 以端点。
我们首先考虑所有斜率为正的线段。为负的那些只需要翻转一下坐标系就可以实现。
注意到这是一个求和问题,贡献可减,我们把所有的询问容斥成前缀矩形。
这里就是通过二维前缀和的差分把一个 \(4-side\) 的矩形差分为 \(4\) 个 \(2-side\) 的矩形。
我们发现,此时所有的线段与矩形的位置关系只有四种情况 :
①相离 ②完全包含 ③与上边界相交 ④与右边界相交
这里没有办法同时与上和右边界相交,除非是刚好在最右上方的点,这个可以归为④处理
对于完全包含的线段,充要条件是 : 右上端点在矩形内。这是个经典二维偏序。
然后,我们就只需要考虑相交的情况。
对于与上边界相交的情况,我们可以翻转坐标系的两轴,或者把这个作为对称情况讨论,这样就只用考虑与右边界相交。
关注到所有线段不想交,表明,如果我们拿一条竖着的直线去切线段,所得的交点的顺序是不会改变的。所以我们可以按照 \(x\) 坐标跑扫描线,用平衡树按照 \(y\) 坐标维护这些线段,查询时,和矩阵右边界相交的线段,再平衡树上一定是一个前缀区间。
考虑如何计算右边界左边长度。
我们先解出每条线段向右经过一单位所得的长度是多少,称之为单位线长。
然后维护单位线长,和从左端点延长到左边界的线段长度和。
我们用单位线长的前缀和乘以边界的 x 坐标,就能得到整条线的长度,再减去准备好的延长线长度总和即可。实际上就是在取补集。
总时间复杂度 \(O((n+m)logn)\),常数大概24倍
T3
给出 \(n\) 个节点的树,求它们两两之间的最短路径的并中,无序取两个相同颜色的边,方案数是多少。
先考虑每次给定的 \(k\) 很小怎么做
可以发现 \(k\) 个点的虚树上可以由 \(O(k)\) 条原树上的链的和表示.
这个两两相等的方案数是一个经典的 \(pair\) 形式的贡献
于是考虑对两两链 \(i,j\) 求 \(i\) 中选一个数,\(j\) 中选一个数,相等的方案数
这样拆出了 \(O(k^2)\) 个两两链之间的询问
然后两两链之间的询问可以用树上前缀和的形式拆为两两点到根路径之间的贡献,于是可以用莫队处理了
注意到这里其实不会造成额外的常数,因为拆出的询问里面很大一部分都是重复的.
如果每次询问的 \(k\) 很大怎么办?
可以直接暴力 \(O(n)\),统计每条边是否在询问的虚树上,计算答案
将两个算法平衡,可以得到一个 \(O(nm^2/3)\) 的算法
造了 \(100\) 多组数据卡了块大小以及树形态,\(std\) 最慢的点为 \(700ms\)
今天早上想到一个复杂度更好的算法
考虑对颜色出现次数进行根号分治
出现次数大于 \(\sqrt n\) 的颜色,每次建出虚树后查询每段链中其出现次数
出现次数小于 \(\sqrt n\) 的颜色,对颜色i,将其出现位置两两的 \(pair\) 找出来,发现一个双前缀贡献中有这个颜色的方案等价于双前缀的两个端点都分别在两个子树中。
于是我们变成了一个 \(n\sqrt n\) 个点,\(n\sqrt n\) 次矩形数点的问题
时间复杂度 \(O(n\sqrt n\log n)\)
将出现次数的平衡向一侧调整可以得到一个
当 \(n,m\) 同阶,时间 \(O(n\sqrt{(n\log n)})\) 的算法
用快速矩阵乘法可能可以优化到 \(o(n^1.5)\),但感觉不太优美
不知道有没有什么优美的方法。
EC final 2020 G
给定一个序列,求总区间有序欧少子区间其颜色数为奇数。
区间有多少子区间颜色数为奇数等价于区间所哟Uzi区间颜色数对 \(2\) 取模的和。
考虑使用扫描线和数据结构维护,扫描线扫区间右端点,数据结构维护所有左端点的答案。
当扫描线扫到 \(r\) 位置时,找到最大的 \(i\) 满足 \(a_i=a_r\) 且 \(i<r\) ,则当前右端点下,左端点若在区间 \([i+1,r]\) 中,则额外出现了颜色 \(s_r\) ,否则不发生变化。
数据解耦股直接维护每个左端点对应的区间内的异或和,这个区间多一种颜色的操作等价于区间异或上 \(1\) ,查询等价于当右端点在 \([l,r]\) 中时,每个时刻查询一次左端点在 \([l,r]\) 内的答案,即查询异或和,这个可以查分后转换为查询区间 \([l,r]\) 端点的历史上每个时刻的和。通过区间在 \(r\) 时刻的历史和减去在 \(l\) 时刻的历史和得到答案。
问题变成进行 \(O(n)\) 次区间异或操作,\(O(m)\) 次查询一个区间的历史和,通过线段树维护。
时间复杂度: \(O(n+m)\log n\)
神秘题
给定一个序列,每次查询区间中出现偶数次的树的异或和。
异或有神秘的性质
问题等价于区间所有出现过的元素的异或和与区间所有出现奇数次的元素的异或和
后者等价于区间异或和,因为出现偶数次的数对答案无贡献
区间所有出现过的元素的异或和也方便统计
和区间出现过的元素个数同样的方法处理即可
总时间复杂度 \(O((n+m)logn)\)
Loj3489 JOISC2021 饮食区
有 \(n\) 个队列 \(m\) 个操作
每个操作形如 \([l,r]\) 的每个队列中进来了 \(k\) 个 \(type=c\) 的人
或者 \([l,r]\) 的每个队列中出去了 \(k\) 个人(不足 \(k\) 个则全部出去)
还有查询某个队列中第 \(k\) 个人的 \(type\)(不足 \(k\) 个输出 \(0\) )
经典问题
给一个DAG,每个边有边权,每次查询从一个点出发到任意点,字典序 \(kth\) 的路径,是到哪个点
\(n,m<=10^5,k<=10^{18}\)
每个点的每种边权的出边只有一条
强制在线。
我们在每个节点用一个可持久化平衡树维护答案,每次在 DAG 上按拓扑顺序合并一个点到所有变得平衡树,每个平衡树是按原顺序合并过来的。
小结
挑了一些提整理上,但实际上因为实力有限,大多数题还是没有听懂,要珍惜时间,努力学习知识,做题刷题。