【做题记录】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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back 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\)。则答案为

\[\begin{aligned} &\sum_{i=n-num+1}^n i-\sum_{i=1}^{num}i\\ =&2\times num(n-num) \end{aligned} \]

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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } 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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } 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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back #define pii pair<int,int> #define mp make_pair #define fir first #define sec second 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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back #define lwrb lower_bound 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\),则:

\[\begin{aligned} &l_i+l_j\le i-j\le r_i+r_j\\ \Leftrightarrow&i-l_i\ge j+l_j\land i-r_i\le j+r_j\\ \Leftrightarrow&[i-r_i,i-l_i]\cap[j+l_j,j+r_j]\ne\varnothing \end{aligned} \]

又因为对于 \(i>j\)\(i+l_i\) 一定大于 \(j-l_j\)
所以对于所有 \(i\)\(j\),能相互传球的条件就是

\[([i-r_i,i-l_i]\cap[j+l_j,j+r_j])\cup([j-r_j,j-l_j]\cap[i+l_i,i+r_i])\ne\varnothing \]

将这样的 \(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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define lwrb lower_bound #define uprb upper_bound 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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pii pair<int,int> #define mp make_pair #define fir first #define sec second 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
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back #define lid id<<1 #define rid id<<1|1 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
#include<bits/stdc++.h> #define ll long long #define il inline #define pb push_back using namespace std; namespace asbt{ namespace cplx{bool begin;} namespace IO{ char buf[1<<20],*p1=buf,*p2=buf; #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++) 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();}
posted @   zhangxy__hp  阅读(11)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开