【做题记录】csp2025-提高组并查集专题
A. Arpa's weak amphitheater and Mehrdad's valuable Hoses
用并查集将每个朋友圈找出,然后 DP。
设 \(dp_{i,j}\) 表示前 \(i\) 个朋友圈,重量为 \(j\) 的最大美丽度。转移分为从这个朋友圈中选一个转移、用这个朋友圈的和转移和不转移(直接继承)。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e3+5;
int n,m,tot,dp[maxn][maxn];
int a[maxn],b[maxn];
int fa[maxn],sz[maxn];
int ying[maxn];
vector<int> hao[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m)read(tot);
for(int i=1;i<=n;i++){
read(a[i]);
}
for(int i=1;i<=n;i++){
read(b[i]);
}
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
while(m--){
int u,v;
read(u)read(v);
u=find(u),v=find(v);
if(u==v){
continue;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
int num=0;
for(int i=1;i<=n;i++){
hao[find(i)].pb(i);
if(find(i)==i){
ying[++num]=i;
}
}
memset(dp,-0x3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=num;i++){
for(int j=0;j<=tot;j++){
dp[i][j]=dp[i-1][j];
}
int sa=0,sb=0;
for(int x:hao[ying[i]]){
sa+=a[x],sb+=b[x];
for(int j=a[x];j<=tot;j++){
dp[i][j]=max(dp[i][j],dp[i-1][j-a[x]]+b[x]);
}
}
for(int j=sa;j<=tot;j++){
dp[i][j]=max(dp[i][j],dp[i-1][j-sa]+sb);
}
}
int ans=0;
for(int i=1;i<=tot;i++){
ans=max(ans,dp[num][i]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
B. Tokitsukaze and Two Colorful Tapes
将 \(a_i\) 与 \(b_i\) 连边。发现得到若干个相互独立的环。要求是给每个点赋权,边权为两点权的差,使边权最大。
考虑环上点权比两边都大的点,设权值之和为 \(x\),点权比两边都小的点记为 \(y\),则 \(ans=2(x-y)\)。
发现只有这两种点会对答案产生贡献,那么我们尽量让这两种点的数量最大。容易发现这两种点的数量相同。对于一个大小为 \(sz\) 的环,数量最大都为 \(\lfloor sz\rfloor\),记为 \(num\)。
还是要让答案最大,那么就让大的那些点为 \(n-num+1\dots n\),小的那些为 \(1\dots num\)。则答案为
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int T,n,a[maxn],b[maxn];
int fa[maxn],sz[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(T);
while(T--){
read(n);
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
for(int i=1;i<=n;i++){
read(a[i]);
}
for(int i=1;i<=n;i++){
read(b[i]);
}
for(int i=1,u,v;i<=n;i++){
u=find(a[i]),v=find(b[i]);
if(u==v){
continue;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
int num=0;
for(int i=1;i<=n;i++){
if(fa[i]==i){
num+=sz[i]>>1;
}
}
printf("%lld\n",num*2ll*(n-num));
}
return 0;
}
}
int main(){return asbt::main();}
C. AND-MEX Walk
因为是不断按位与,\(w_1,w_1\&w_2,w_1\&w_2\&w_3,\dots\) 这个序列一定是单调不增的。观察样例可以发现,答案只可能为 \(0\),\(1\) 或 \(2\)。
简单证明一下:
如果答案大于 \(2\),那么在 \(w_1,w_1\&w_2,w_1\&w_2\&w_3,\dots\) 中一定存在 \(0\),\(1\) 和 \(2\)。又因为单调不增,则一定是在 \(2\) 后面出现了一个 \(1\)。然而 \(2\) 怎么与都是与不出来 \(1\) 的。因此答案不可能大于 \(2\)。
于是直接去判断答案是 \(0\),\(1\) 还是 \(2\) 就行了。
首先是 \(0\) 的情况,即这个序列中没出现过 \(0\),那么在二进制位中一定有至少一位是 \(1\),即走过的每一条边在这一位都是 \(1\)。于是可以给每一个二进制位开一个并查集,连接在这一位上是 \(1\) 的边两边的点。如果 \(u\) 和 \(v\) 在某一位上在一个集合里,那么答案就是 \(0\)。
考虑判断答案是 \(1\) 的情况。记 \(w_1,w_1\&w_2,w_1\&w_2\&w_3,\dots\) 为 \(a_1,a_2,a_3,\dots\),记总共有 \(tot\) 条边。则一定存在一个 \(k\in[1,tot)\) 满足 \(a_1,a_2,\dots,a_{k-1}\) 均大于 \(1\),而 \(a_k,a_{k+1},\dots,a_{tot}\) 都是 \(0\)。前面那部分是好处理的,只需在除了末位的某一位上都是 \(1\) 就好了,和第一种情况类似。考虑要与出 \(0\),则末位一定得是 \(0\)。因此新开一个并查集,将每条末位为 \(0\) 的边两边的每个点都向一个虚点 \(0\) 连边,只需检查 \(u\) 与 \(v\) 中任意一个在除末位以外的某一位与 \(0\) 联通就行了。这样正确的原因是,因为答案已经不可能是 \(0\) 了,所以每一位到最后都是一定能被消掉的。
如果以上两种情况均不满足,则答案为 \(2\)。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,q;
struct dsu{
int fa[maxn],sz[maxn];
il void init(){
for(int i=0;i<=n;i++){
fa[i]=i,sz[i]=1;
}
}
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
// cout<<u<<" "<<v<<"\n";
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v];
fa[v]=u;
}
else{
sz[v]+=sz[u];
fa[u]=v;
}
}
il bool check(int u,int v){
return find(u)==find(v);
}
}D[2][35];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// cout<<cplx::usdmem();
read(n)read(m);
for(int x=0;x<30;x++){
D[0][x].init();
D[1][x].init();
}
for(int i=1,u,v,w;i<=m;i++){
read(u)read(v)read(w);
for(int x=0;x<30;x++){
if(w>>x&1){
// puts("666");
D[0][x].merge(u,v);
D[1][x].merge(u,v);
}
}
if(w&1){
continue;
}
for(int x=0;x<30;x++){
// puts("777");
D[1][x].merge(u,0);
D[1][x].merge(v,0);
}
}
read(q);
while(q--){
int u,v;
read(u)read(v);
for(int x=0;x<30;x++){
if(D[0][x].check(u,v)){
puts("0");
goto togo;
}
}
for(int x=1;x<30;x++){
if(D[1][x].check(u,0)){
puts("1");
goto togo;
}
}
puts("2");
togo:;
}
return 0;
}
}
int main(){return asbt::main();}
D. Anton and Tree
直接说结论:将相同颜色的连通块缩成点建出新树,设新树的直径上点数为 \(d\),则答案为 \(\lfloor \frac d 2\rfloor\)。
证明:
缩点后要把整棵树变成同一个颜色,那直径肯定也得变成同一种颜色。将直径变为同一种颜色的最小操作次数显然为 \(\lfloor \frac d 2\rfloor\)。
然后直径上的点如果有连出直径外的边,这条多出来的链长度一定 \(\le\) 这个点与较近的直径端点的距离,否则就会有更长的路径充当直径。因此从这个点开始扩充,扩充到较近的直径端点时一定已经把这个点连出去的链都扩充完了,于是就可以在直径上换个点来扩充了。因此答案就是 \(\lfloor \frac d 2\rfloor\)。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int n,a[maxn],fa[maxn],sz[maxn];
pii ed[maxn];
vector<int> e[maxn];
il int find(int x){
return fa[x]!=x?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
int dep[maxn],mxd[maxn],des[maxn];
il void dfs(int u,int fa){
mxd[u]=dep[u]=dep[fa]+1;
des[u]=u;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
if(mxd[v]>mxd[u]){
mxd[u]=mxd[v];
des[u]=des[v];
}
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=1;i<=n;i++){
read(a[i]);
fa[i]=i,sz[i]=1;
}
for(int i=1,u,v;i<n;i++){
read(u)read(v);
ed[i]=mp(u,v);
if(a[u]==a[v]){
merge(u,v);
}
}
for(int i=1,u,v;i<n;i++){
u=find(ed[i].fir);
v=find(ed[i].sec);
if(u!=v){
e[u].pb(v),e[v].pb(u);
}
}
int rt=find(1);
dfs(rt,0);
rt=des[find(1)];
dfs(rt,0);
printf("%d",mxd[rt]>>1);
return 0;
}
}
int main(){return asbt::main();}
E. Sanae and Giant Robot
记 \(c_i=a_i-b_i\)。问题转化为每次选择一个区间 \([l,r]\) 满足 \(\sum_{i=l}^{r}c_i=0\),将 \(c_{l\dots r}\leftarrow 0\)。要求是最后要满足 \(\forall i\in[1,n],c_i=0\)。
再记 \(sc_i\) 为 \(c_i\) 的前缀和。问题再次转化为每次选择一个区间 \([l,r]\) 满足 \(sc_{l-1}=sc_r\),将 \(sc_{l\dots r-1}\leftarrow sc_r\)。要求是最后要满足 \(\forall i\in[1,n],sc_i=0\)。
因为最后要将 \(sc\) 数组推平成 \(0\),所以只有操作 \(sc_{l-1}=sc_r=0\) 的区间才有意义。于是可以用 set
维护 \(sc\) 值不为 \(0\) 的位置,进行 \(bfs\),每次用 \(sc=0\) 的位置,枚举它能更新的区间并将区间内的元素从 set
中删除即可。
因为每个位置最多进出 set
各一次,所以时间复杂度为 \(O(n\log n)\)。
突然发现这道题没有用到并查集。。。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int T,n,m;
ll a[maxn],b[maxn],sc[maxn];
queue<int> q;
set<int> ji;
vector<int> e[maxn];
il void solve(){
read(n)read(m);
for(int i=1;i<=n;i++){
read(a[i]);
}
for(int i=1;i<=n;i++){
read(b[i]);
}
for(int i=1,l,r;i<=m;i++){
read(l)read(r);
e[l-1].pb(r),e[r].pb(l-1);
}
for(int i=1;i<=n;i++){
sc[i]=sc[i-1]+a[i]-b[i];
}
ji.clear();
for(int i=0;i<=n;i++){
if(sc[i]){
ji.insert(i);
}
else{
q.push(i);
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int v:e[u]){
if(sc[v]){
continue;
}
int l=min(u,v),r=max(u,v);
auto tmp=ji.lwrb(l);
while(tmp!=ji.end()&&*tmp<=r){
sc[*tmp]=0,q.push(*tmp);
tmp=ji.erase(tmp);
}
}
}
puts(ji.empty()?"YES":"NO");
for(int i=0;i<=n;i++){
e[i].clear();
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(T);
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
F. Nene and the Passing Game
先推式子。
令 \(i>j\),则:
又因为对于 \(i>j\),\(i+l_i\) 一定大于 \(j-l_j\)。
所以对于所有 \(i\) 和 \(j\),能相互传球的条件就是
将这样的 \(i\) 和 \(j\) 连边,答案显然就是连通块的数量。
于是建虚点,将 \(i\) 向 \([i-r_i,i-l_i]\) 和 \([i+l_i,i+r_i]\) 中的虚点连边。时间无法接受,因此将 \(i\) 向区间中的一个点连边,区间内部再互相连边。这可以用并查集 \(+\) 类似扫描线的方式完成。
但是问题在于,这样连边可能会使 \(i\) 和 \(j\) 因为 \([i-r_i,i-l_i]\) 和 \([j-r_j,j-l_j]\) 相交而连通,但这显然不是我们希望得到的。
那怎么办呢,答案是对于所有 \(x\),如果 \(\forall i\in[1,n],x\notin[i-r_i,i-l_i]\) 或者 \(\forall i\in[1,n],x\notin[i+l_i,i+r_i]\),那就直接将这个点删掉。这样就能保证两个点相连一定是合法的了。这也可以用类似扫描线的方式完成。
连边时需要二分,复杂度为 \(O(n\log n)\)。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e6+5;
int T,n,a[maxn],b[maxn];
int fa[maxn<<2],sz[maxn<<2];
int cl[maxn<<2],cr[maxn<<2];
int hao[maxn<<2],c[maxn<<2];
bitset<maxn<<2> vis;
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il int id(int x){
return x+(n<<1|1);
}
il void solve(){
read(n);
for(int i=1;i<=n;i++){
read(a[i])read(b[i]);
}
// cout<<(n<<2|1);
for(int i=1;i<=(n<<2|1);i++){
// cout<<i<<"\n";
fa[i]=i,sz[i]=1;
cl[i]=cr[i]=c[i]=vis[i]=0;
}
// puts("666");
for(int i=1,l,r;i<=n;i++){
l=id(i-b[i]),r=id(i-a[i]);
cl[l]++,cl[r+1]--;
l=id(i+a[i]),r=id(i+b[i]);
cr[l]++,cr[r+1]--;
}
int tot=0;
for(int i=1;i<=(n<<2|1);i++){
cl[i]+=cl[i-1],cr[i]+=cr[i-1];
if(cl[i]&&cr[i]){
hao[++tot]=i;
}
}
for(int i=1,l,r;i<=n;i++){
l=lwrb(hao+1,hao+tot+1,id(i-b[i]))-hao;
r=uprb(hao+1,hao+tot+1,id(i-a[i]))-hao-1;
if(l<=r){
merge(i,hao[l]);
c[l]++,c[r]--;
}
l=lwrb(hao+1,hao+tot+1,id(i+a[i]))-hao;
r=uprb(hao+1,hao+tot+1,id(i+b[i]))-hao-1;
if(l<=r){
merge(i,hao[l]);
c[l]++,c[r]--;
}
}
for(int i=1;i<=tot;i++){
c[i]+=c[i-1];
if(c[i]){
merge(hao[i],hao[i+1]);
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(!vis[find(i)]){
vis[find(i)]=1;
ans++;
}
}
printf("%d\n",ans);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(T);
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
G. Clearing Up
思路是首先在不成环的前提下加入所有 \(S\) 边,然后加入若干条 \(M\) 边使加入的边构成一棵树。此时加入的所有 \(M\) 边都钦定必须选。然后再删掉所有 \(S\) 边,继续在不成环的前提下加 \(M\) 边使 \(M\) 边数量达到 \(\lfloor\frac{n}{2}\rfloor\)。然后再加入 \(\lfloor\frac{n}{2}\rfloor\) 条 \(S\) 边使图变成一棵树即可。操作过程中不断判无解。
证明看似麻烦,实际一点都不简单很容易。要证明的其中一点是:如果我们选定的这 \(\lfloor\frac{n}{2}\rfloor\) 条 \(M\) 边中的一部分能与所有不成环的 \(S\) 边形成一棵树,那么就一定能构造出方案。其实这是显然的,考虑生成树的构造方式,当前我们选定的 \(M\) 边因为不成环一定是生成树的一部分,而剩下的点一定是能由 \(S\) 边加进来的。所以正确性是有的。
另一点是:一开始加入 \(S\) 边时加了一些边而舍去了另一些边,这会不会影响答案?答案是不会。原因是,首先不论以怎样的顺序加入,加进来的边数都一定是相同的。其次,在最后加 \(S\) 边时,可供选择的 \(S\) 边实际上是变多了的(就是说第一步中没选的现在也可以选),而根据上面的证明,一定是能构造出方案的,所以多一些可供选的边也不会影响正确性。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e3+5,maxm=1e5+5;
int n,m,num1,num2;
int fa[maxn],sz[maxn];
int a[maxm],b[maxm];
pii e[maxm];
bool vis[maxm];
il void wuj(){
puts("-1");
exit(0);
}
il void init(){
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
}
il int find(int x){
return fa[x]!=x?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v];
fa[v]=u;
}
else{
sz[v]+=sz[u];
fa[u]=v;
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m);
if(n%2==0){
wuj();
}
for(int i=1,u,v;i<=m;i++){
read(u)read(v);
e[i]=mp(u,v);
char w;
scanf(" %c",&w);
if(w=='S'){
a[++num1]=i;
}
else{
b[++num2]=i;
}
}
init();
int cnt1=0,cnt2=0;
for(int i=1,u,v;i<=num1;i++){
u=e[a[i]].fir,v=e[a[i]].sec;
if(find(u)==find(v)){
continue;
}
cnt1++,merge(u,v);
}
if(cnt1<n>>1){
wuj();
}
for(int i=1,u,v;i<=num2;i++){
u=e[b[i]].fir,v=e[b[i]].sec;
if(find(u)==find(v)){
continue;
}
cnt2++,merge(u,v),vis[b[i]]=1;
}
if(cnt1+cnt2<n-1){
wuj();
}
init();
for(int i=1;i<=m;i++){
if(vis[i]){
merge(e[i].fir,e[i].sec);
}
}
for(int i=1,u,v;i<=num2;i++){
if(cnt2==n>>1){
break;
}
u=e[b[i]].fir,v=e[b[i]].sec;
if(find(u)==find(v)){
continue;
}
cnt2++,merge(u,v),vis[b[i]]=1;
}
if(cnt2<n>>1){
wuj();
}
for(int i=1,u,v;i<=num1;i++){
u=e[a[i]].fir,v=e[a[i]].sec;
if(find(u)==find(v)){
continue;
}
merge(u,v),vis[a[i]]=1;
}
printf("%d\n",n-1);
for(int i=1;i<=m;i++){
if(vis[i]){
printf("%d ",i);
}
}
return 0;
}
}
int main(){return asbt::main();}
H. [HEOI2016/TJOI2016] 树
离这个节点最近的一个祖先,那必然是 \(dfn\) 最大的一个。直接用线段树区间取 \(\max\) 和单点查询就行了。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,dfn[maxn],idx[maxn],sz[maxn],cnt;
vector<int> e[maxn];
il void dfs(int u,int fa){
dfn[u]=++cnt;
idx[cnt]=u;
sz[u]=1;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
sz[u]+=sz[v];
}
}
int zhi[maxn<<2];
il void build(int id,int l,int r){
zhi[id]=1;
if(l==r){
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
il void upd(int id,int L,int R,int l,int r,int val){
if(L>=l&&R<=r){
zhi[id]=max(zhi[id],val);
return ;
}
int mid=(L+R)>>1;
if(l<=mid){
upd(lid,L,mid,l,r,val);
}
if(r>mid){
upd(rid,mid+1,R,l,r,val);
}
}
il int query(int id,int l,int r,int pos){
int res=zhi[id];
if(l==r){
return res;
}
int mid=(l+r)>>1;
if(pos<=mid){
return max(res,query(lid,l,mid,pos));
}
return max(res,query(rid,mid+1,r,pos));
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m);
for(int i=1,u,v;i<n;i++){
read(u)read(v);
e[u].pb(v),e[v].pb(u);
}
dfs(1,0);
build(1,1,n);
while(m--){
char opt;
int u;
scanf(" %c",&opt);
read(u);
if(opt=='Q'){
printf("%d\n",idx[query(1,1,n,dfn[u])]);
}
else{
upd(1,1,n,dfn[u],dfn[u]+sz[u]-1,dfn[u]);
}
}
return 0;
}
}
int main(){return asbt::main();}
本来还以为是又放错题了,结果去洛谷题解区一看还真能拿并查集做,有点类似 \(tarjan\) 求 \(lca\)。就是离线下来,如果这个点被染色了就把 \(fa\) 指向自己,否则指向自己的父亲,倒序处理询问,操作就将这个节点的染色次数 \(-1\) 就行。方法不错,就不打了(逃
I. [HNOI2016] 最小公倍数
显然能将最小公倍数转化为 \(a\) 和 \(b\) 分别取 \(\max\)。显然对于一个询问,只有 \(a\) 和 \(b\) 都不超过它的边能产生贡献。将能产生贡献的边加入,并查集判断连通性再判断 \(a\) 和 \(b\) 的最大值是否都符合要求即可。
将所有边先按 \(a\) 排序,分块。然后对于每个询问,令它归属的块编号为最大的 \(i\),使 \(1\) 到 \(i-1\) 块中的边的 \(a\) 都 \(\le\) 这个询问的 \(a\)。接下来顺序扫描每一个块,维护当前扫过的所有块中的边按 \(b\) 排序的序列。每扫到一个块,先将这个块中的询问插入到维护的序列中,然后遍历插入后的序列,若遍历到边则直接合并,若遍历到询问则将当前块中能对这个询问产生贡献的边加入,统计答案后再撤销。需要可撤销并查集。最后再将当前块中的边插入序列即可。可以用归并排序完成。
然后就是玄学的复杂度分析了。设块长为 \(B\)。加入所有边的复杂度为 \(O(\frac{m^2\log n}{B})\),而处理询问的复杂度为 \(O(qB\log n)\)。令二者相等,显然最佳的块长应为 \(\frac{m}{\sqrt{q}}\),时间复杂度为 \(O(m\log n\sqrt{q})\) 约为 \(3\times 10^8\),然而会 TLE 2 个点。考虑处理询问那一块每个询问都是跑不满 \(B\) 次的,不妨将那个 \(\log n\) 舍掉,于是令 \(\frac{m^2\log n}{B}=qB\),解出块长应为 \(m\sqrt{\frac{\log n}{q}}\),然而会 TLE 4 个点。但若将加边的 \(\log n\) 舍掉,解出块长为 \(\frac{m}{\sqrt{q\log n}}\),此时加边那部分的复杂度为 \(O(m\log n\sqrt{q\log n})\),是 \(1.3\times 10^9\) 左右的,却通过了。所以说做分块时一定要多试试不同的块长跑极限数据,以实际跑下来的时间为准。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
namespace IO{
char buf[1<<20],*p1=buf,*p2=buf;
il int read(){
char ch=getchar();
while(ch<'0'||ch>'9'){
ch=getchar();
}
int x=ch^48;
ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
}
using namespace IO;
const int maxn=1e5+5;
int n,m,q,blen,bnum,st[maxn],ed[maxn],bel[maxn],wel[maxn];
int fa[maxn],sz[maxn],mxa[maxn],mxb[maxn],top;
bool ans[maxn];
vector<int> xwn[maxn];
struct node{
int u,v,a,b,id;
bool typ;
}bian[maxn],wen[maxn],hp1[maxn<<1],hp2[maxn<<1];
il bool cmpa(const node &x,const node &y){
return x.a<y.a;
}
il bool cmpb(const node &x,const node &y){
return x.b<y.b;
}
il void gwel(){
int p1=1,p2=1,p3=1;
while(p1<=m&&p2<=q){
if(cmpa(wen[p2],bian[p1])){
hp1[p3++]=wen[p2++];
}
else{
hp1[p3++]=bian[p1++];
}
}
while(p1<=m){
hp1[p3++]=bian[p1++];
}
while(p2<=q){
hp1[p3++]=wen[p2++];
}
bel[0]=++bnum;
for(int i=1,li=0,cnt=0;i<=p3;i++){
if(hp1[i].typ){
continue;
}
for(int j=li+1;j<i;j++){
wel[hp1[j].id]=bel[hp1[i].id];
xwn[bel[hp1[i].id]].pb(++cnt);
}
li=i;
}
}
il int find(int x){
return x!=fa[x]?find(fa[x]):x;
}
struct unod{
int u,v,fau,fav,mxau,mxbu,mxav,mxbv;
}zhan[maxn];
il void merge(int u,int v,int a,int b){
u=find(u),v=find(v);
zhan[++top]=(unod){u,v,fa[u],fa[v],mxa[u],mxb[u],mxa[v],mxb[v]};
if(u==v){
mxa[u]=max(mxa[u],a),mxb[u]=max(mxb[u],b);
return ;
}
if(sz[u]<sz[v]){
swap(u,v);
}
sz[u]+=sz[v],fa[v]=u;
mxa[u]=max({mxa[u],mxa[v],a});
mxb[u]=max({mxb[u],mxb[v],b});
}
il void che(){
unod tmp=zhan[top--];
int u=tmp.u,v=tmp.v;
fa[u]=tmp.fau,fa[v]=tmp.fav;
mxa[u]=tmp.mxau,mxb[u]=tmp.mxbu;
mxa[v]=tmp.mxav,mxb[v]=tmp.mxbv;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
n=read(),m=read();
for(int i=1,u,v,a,b;i<=m;i++){
u=read(),v=read(),a=read(),b=read();
bian[i]=(node){u,v,a,b,0,0};
}
q=read();
for(int i=1,u,v,a,b;i<=q;i++){
u=read(),v=read(),a=read(),b=read();
wen[i]=(node){u,v,a,b,i,1};
}
sort(bian+1,bian+m+1,cmpa);
sort(wen+1,wen+q+1,cmpa);
blen=max(sqrt(m*1.0*m/q/(log2(n)>1?log2(n):1)),1.0);
bnum=(m+blen-1)/blen;
for(int i=1;i<=bnum;i++){
st[i]=ed[i-1]+1;
ed[i]=min(ed[i-1]+blen,m);
for(int j=st[i];j<=ed[i];j++){
bel[j]=i;
}
}
for(int i=1;i<=m;i++){
bian[i].id=i;
}
gwel();
for(int i=1;i<=bnum;i++){
sort(xwn[i].begin(),xwn[i].end(),[](const int &x,const int &y){return cmpb(wen[x],wen[y]);});
sort(bian+st[i],bian+ed[i]+1,cmpb);
}
st[bnum]=m+1;
for(int i=1,p1,p2,p3;i<=bnum;i++){
p1=p3=1,p2=0;
while(p1<st[i]&&p2<xwn[i].size()){
if(cmpb(wen[xwn[i][p2]],hp2[p1])){
hp1[p3++]=wen[xwn[i][p2++]];
}
else{
hp1[p3++]=hp2[p1++];
}
}
while(p1<st[i]){
hp1[p3++]=hp2[p1++];
}
while(p2<xwn[i].size()){
hp1[p3++]=wen[xwn[i][p2++]];
}
for(int j=1;j<=n;j++){
fa[j]=j,sz[j]=1,mxa[j]=mxb[j]=-1;
}
top=0;
for(int j=1;j<p3;j++){
if(hp1[j].typ){
int tmp=top;
for(int k=st[i];k<=ed[i];k++){
if(bian[k].a<=hp1[j].a&&bian[k].b<=hp1[j].b){
merge(bian[k].u,bian[k].v,bian[k].a,bian[k].b);
}
}
int rt=find(hp1[j].u);
if(rt==find(hp1[j].v)&&mxa[rt]==hp1[j].a&&mxb[rt]==hp1[j].b){
ans[hp1[j].id]=1;
}
while(top>tmp){
che();
}
}
else{
merge(hp1[j].u,hp1[j].v,hp1[j].a,hp1[j].b);
}
}
p1=p3=1,p2=st[i];
while(p1<st[i]&&p2<=ed[i]){
if(cmpb(hp2[p1],bian[p2])){
hp1[p3++]=hp2[p1++];
}
else{
hp1[p3++]=bian[p2++];
}
}
while(p1<st[i]){
hp1[p3++]=hp2[p1++];
}
while(p2<=ed[i]){
hp1[p3++]=bian[p2++];
}
for(int j=1;j<p3;j++){
hp2[j]=hp1[j];
}
}
for(int i=1;i<=q;i++){
puts(ans[i]?"Yes":"No");
}
return 0;
}
}
int main(){return asbt::main();}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步