A.Round Dance
Problem
一张图中有 \(n\) 个点,每个点都有两条边,现在给出每个点两条边中任意一条,请求出整张图中可能的环的最大数和最小数。
Solution
首先我们将点 \(i\) 和点 \(a_i\) 之间连一条双向边。
- 求环最大数量:要环数最大,就要环的长度最小。因为相联通的点一定在同一个环内,每一个联通块内的点相互联通,所以我们直接求出联通块的数量即可。
- 求环最小数量:要环数最小,就要环的长度最大,所以我们想办法将部分联通块连起来。部分联通块是长度大于 2 的环,无法和别的联通块拼接,所以我们先求出这些环的数量;然后我们可以将剩下的所有不是长度大与 2 的环的联通块拼成一个环。两者相加即为答案(注意判断是否有不是长度大与 2 的环的联通块)。
Code
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int T,n,a[N],vis[N],ansx,ansn,f,sum;
set<int>g[N];
void dfs(int);
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>T;while(T--){cin>>n;for(int i=1;i<=n;i++)g[i].clear();ansx=0,ansn=0;for(int i=1;i<=n;i++){cin>>a[i];g[i].insert(a[i]);g[a[i]].insert(i);}memset(vis,0,sizeof vis);for(int i=1;i<=n;i++){if(!vis[i]){++ansx;vis[i]=1;f=1,sum=0;dfs(i);if(f&&sum>2)++ansn;}}cout<<ansn+(ansx-ansn>0)<<" "<<ansx<<"\n";}
}void dfs(int x){++sum;if(g[x].size()==1)f=0;for(int y:g[x]){if(vis[y])continue;vis[y]=1;dfs(y);}
}
B.Path Queries
Problem
给你一棵 \(n\) 个点的树,每条边都有权值。问你 \(m\) 次问题,每次给你一个 \(p\),请求出最大权值不大于 \(p\) 的简单路径数量。
Solution
首先我们需要知道一点,在一棵大小为 \(n\) 的树中,简单路径数量为 \(\frac{n\times (n-1)}{2}\)。
做法就是将 \(m\) 个询问按从小到大排序,再将 \(n-1\) 条边按边权从小到大排序,然后遍历这 \(m\) 个查询,将不大于这个查询的边建起来(这时候排序边就起作用了),同时维护一下每个联通块的大小,再将两个联通块加到一起时再重新计算一下最短路径的数量即可。
Code
#include<bits/stdc++.h>
#define int long long
#define N 200005
using namespace std;
int n,m,q,lst,f[N],num[N],ans[N],sum;
struct edge{int u,v,w;}e[N];
struct node{int x,id;}a[N];
int cmp(node a,node b){return a.x<b.x;}
int tmp(edge a,edge b){return a.w<b.w;}
int findf(int x){if(x==f[x])return x;return f[x]=findf(f[x]);
}
signed main(){cin>>n>>m;for(int i=1;i<=n;i++)f[i]=i,num[i]=1;for(int i=1;i<n;i++)cin>>e[i].u>>e[i].v>>e[i].w;for(int i=1;i<=m;i++)cin>>a[i].x,a[i].id=i;sort(a+1,a+1+m,cmp);sort(e+1,e+n,tmp);for(int d=1;d<=m;d++){int i;for(i=lst+1;i<n&&e[i].w<=a[d].x;i++){int x=findf(e[i].u),y=findf(e[i].v);if(x!=y){sum-=(num[x]*(num[x]-1)/2+num[y]*(num[y]-1)/2);num[x]+=num[y];sum+=(num[x]*(num[x]-1))/2;f[y]=x;}}lst=i-1;ans[a[d].id]=sum;}for(int i=1;i<=m;i++)cout<<ans[i]<<" ";return 0;
}
C.Vlad and the Mountains
Problem
给你一张图,点 \(i\) 有相应的点权 \(h_i\),从 \(i\) 到 \(j\) 需要花费 \(h_j-h_i\) 的能量(如果为负即为补充相应绝对值),共 \(q\) 次询问,每次询问从 \(s\) 到 \(t\),初始能量值为 \(e\),能否保证有一条路线,使得能量值始终不低于 0。
Solution
首先我们能需要想到一点:从 \(i\) 到 \(j\) 需要花费 \(h_j-h_i\) 的能量值,从 \(j\) 到 \(k\) 需要花费 \(h_k-h_j\) 的能量值,也就是从 \(i\) 到 \(j\) 到 \(k\) 需要花费 \(h_j-h_i-h_j+h_k=h_k-h_i\) 的能量值,也就是说,从点 \(a\) 到点 \(b\)需要花费 \(h_b-h_a\) 的能量值而与路途无关。
那么问题就可以转化为:找一条从 \(s\) 到 \(t\) 的路径,使得路上的每个点 \(i\) 都保证 \(e-(h_i-h_s)\ge 0\),也就是保证 \(e+h_s\ge h_i\),再转化一步,就是让这条路径上的 \(h\) 的最大值保证 \(e+h_s\ge h_{max}\)。
熟悉吗?太熟悉了!这不就是上一题吗!同样,我们进行排序后建边的操作,同时用并查集来判断连通性即可。
Code
#include<bits/stdc++.h>
#define Pr pair<int,int>
#define N 200005
#define M 200005
#define int long long
using namespace std;
int T,n,m,q,f[N],x[N];
vector<int>g[N];
string ans[N];
int findf(int x){if(x==f[x])return x;return f[x]=findf(f[x]);
}
struct node{int st,ed,e,id;
}a[N];
struct Nd{int x,id;
}p[N];
int cmp(node a,node b){return x[a.st]+a.e<x[b.st]+b.e;
}
int tmp(Nd a,Nd b){return a.x<b.x;
}
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>T;while(T--){cin>>n>>m;for(int i=1;i<=n;i++){f[i]=p[i].id=i;cin>>p[i].x;g[i].clear();x[i]=p[i].x;}for(int i=1;i<=m;i++){int a,b;cin>>a>>b;g[a].push_back(b);g[b].push_back(a);}cin>>q;for(int i=1;i<=q;i++){cin>>a[i].st>>a[i].ed>>a[i].e;a[i].id=i;}sort(a+1,a+1+q,cmp);sort(p+1,p+1+n,tmp);int lst=0;for(int d=1;d<=q;d++){int i,num=x[a[d].st]+a[d].e;for(i=lst+1;i<=n&&p[i].x<=num;i++){int fx=findf(p[i].id);for(int y:g[p[i].id]){if(x[y]<=num){int fy=findf(y);if(fx!=fy)f[fy]=fx;}}}lst=i-1;if(findf(a[d].st)==findf(a[d].ed))ans[a[d].id]="YES\n";else ans[a[d].id]="NO\n";}for(int i=1;i<=q;i++)cout<<ans[i];cout<<"\n";}return 0;
}
D.Information Graph
Problem
给你 \(n\) 个点,在将其连城一棵树的过程中对 \(x\) 到 \(x\) 目前的祖宗打上一个标记,同时查询某个标记是否存在于某个节点上。
Solution
非常板的一道树上差分+线段树合并。在输入时记录一下修改操作的当前节点和祖宗节点,然后离线修改,将节点 \(x\) 的 \(y\) 标记加一,给他当时祖宗节点的父亲节点 \(y\) 标记减一,最后跑一遍 dfs 进行线段树合并操作,合并时顺便统计一下答案即可。
Code
#include<bits/stdc++.h>
#define Pr pair<int,int>
#define N 100005
using namespace std;
int n,m,op,fa[N],f[N],cnt,a[N],b[N],num,rt[N],vis[N],id[N];
vector<int>g[N];
vector<Pr>v[N];
string ans[N];
struct node{int ls,rs,sum;
}t[N<<4];
struct s_tree{int idcnt=0;void add(int &id,int l,int r,int x,int val){if(!id)id=++idcnt;if(l==r){t[id].sum+=val;return;}int mid=(l+r)>>1;if(x<=mid)add(t[id].ls,l,mid,x,val);else add(t[id].rs,mid+1,r,x,val);t[id].sum=t[t[id].ls].sum+t[t[id].rs].sum;}int query(int id,int l,int r,int x){if(!id||!t[id].sum)return 0;if(l==r)return t[id].sum;int mid=(l+r)>>1;if(x<=mid)return query(t[id].ls,l,mid,x);else return query(t[id].rs,mid+1,r,x);}int merge(int a,int b,int l,int r){if(!a||!b)return a|b;if(l==r){t[a].sum+=t[b].sum;return a;}int mid=(l+r)>>1;t[a].ls=merge(t[a].ls,t[b].ls,l,mid);t[a].rs=merge(t[a].rs,t[b].rs,mid+1,r);t[a].sum+=t[b].sum;return a;}
}tr;
void dfs(int x){for(int y:g[x]){if(vis[y])continue;vis[y]=1;dfs(y);rt[x]=tr.merge(rt[x],rt[y],1,cnt);}for(auto y:v[x]){if(tr.query(rt[x],1,cnt,y.first))ans[y.second]="YES\n";else ans[y.second]="NO\n";}
}
int findf(int x){if(x==f[x])return x;return f[x]=findf(f[x]);
}
signed main(){cin>>n>>m;for(int i=1;i<=n;i++)f[i]=i;for(int i=1;i<=m;i++){cin>>op;if(op==1){int x,y;cin>>x>>y;f[x]=y;fa[x]=y;g[y].push_back(x);}if(op==2){cin>>a[++cnt];b[cnt]=findf(a[cnt]);}if(op==3){int x,y;cin>>x>>y;v[x].push_back({y,++num});}}for(int i=1;i<=cnt;i++){tr.add(rt[a[i]],1,cnt,i,1);tr.add(rt[fa[b[i]]],1,cnt,i,-1);}for(int i=1;i<=n;i++){int x=f[i];if(vis[x])continue;vis[x]=1;dfs(x);}for(int i=1;i<=num;i++)cout<<ans[i];return 0;
}
E.白雪皑皑
Problem
给你一个全部为 0 的长为 \(n\) 的序列,操作 \(m\) 次,第 \(i\) 次将 \(i\times p+q\mod n\) 到 \(i\times q+p\mod n\) 区间内的值修改为 \(i\),求最后的状态。
Solution
F1:
直接线段树区间修改即可。同时,因为求操作的边界时系数不变,且进行去摸操作,所以一定有循环节,直接找到最后一个循环节进行修改即可。
F2:
用并查集判断连通性。选择从后往前操作,因为后面的会把前面的覆盖。用 \(f_i\) 表示 \(i\) 和 \(i\) 前面中最近一个可以修改的,也就是还没有被修改的。在修改的同时维护 \(f\) 即可。
Code
F1:
#include<bits/stdc++.h>
#define N 1000005
#define int long long
using namespace std;
int n,m,p,q;
struct node{int num,l,r,lazy;
}t[N<<2];
struct s_tree{#define ls id<<1#define rs id<<1|1void addtag(int id,int val){t[id].lazy=t[id].num=val;}void pushdown(int id){if(t[id].lazy){addtag(ls,t[id].lazy);addtag(rs,t[id].lazy);t[id].lazy=0;}}void build(int id,int l,int r){t[id].l=l,t[id].r=r,t[id].num=t[id].lazy=0;if(l==r)return;int mid=(l+r)>>1;build(ls,l,mid);build(rs,mid+1,r);}void add(int id,int l,int r,int x){if(l<=t[id].l&&t[id].r<=r){addtag(id,x);return;}pushdown(id);int mid=(t[id].l+t[id].r)>>1;if(l<=mid)add(ls,l,r,x);if(r>mid)add(rs,l,r,x);if(t[ls].num==t[rs].num)t[id].num=t[ls].num;else t[id].num=0;}int query(int id,int x){if(t[id].num||t[id].l==t[id].r)return t[id].num;pushdown(id);int mid=(t[id].l+t[id].r)>>1;if(x<=mid)return query(ls,x);else return query(rs,x);}
}tr;
signed main(){cin>>n>>m>>p>>q;tr.build(1,1,n);int a=(m*p+q)%n+1,b=(m*q+p)%n+1;int k=1;for(int i=m-1;i;i--){if((i*p+q)%n+1==a&&(i*q+p)%n+1==b){k=i+1;break;}}for(int i=k;i<=m;i++){int l=(i*p+q)%n+1,r=(i*q+p)%n+1;if(l>r)swap(l,r);tr.add(1,l,r,i);}for(int i=1;i<=n;i++)cout<<tr.query(1,i)<<"\n";return 0;
}
F2:
#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,m,p,q,f[N],c[N];
int findf(int x){if(x==f[x])return x;return f[x]=findf(f[x]);
}
signed main(){cin>>n>>m>>p>>q;int a=(m*p+q)%n+1,b=(m*q+p)%n+1;int k=1;for(int i=m-1;i;i--){if((i*p+q)%n+1==a&&(i*q+p)%n+1==b){k=i+1;break;}}for(int i=1;i<=n;i++)f[i]=i;for(int i=m;i>=k;i--){int l=(i*p+q)%n+1,r=(i*q+p)%n+1;if(l>r)swap(l,r);for(int j=r;j>=l;j=f[j]){int t=findf(j);if(t==j)c[j]=i,f[j]=findf(j-1);}}for(int i=1;i<=n;i++)cout<<c[i]<<"\n";return 0;
}