2.图论
图论/省选图论专题
开题顺序: \(K,C,Q,F,B,J,AC,R,E,A,AL,AH,O,N,L,S,AK,G\)
\(A\) CF19E Fairy
-
观察到 \(n,m \le 10^4\) ,直接线段树分治即可,时间复杂度为 \(O(m \log^{2} m)\) 。
点击查看代码
int n,ans[10010]; pair<int,int>e[10010]; struct quality { int id,fa,siz; }; struct DSU { int fa[20010],siz[20010]; int find(int x) { return (fa[x]==x)?x:find(fa[x]); } void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } void merge(int x,int y,stack<quality>&s) { x=find(x); y=find(y); if(x!=y) { s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); if(siz[x]<siz[y]) { swap(x,y); } fa[y]=x; siz[x]+=siz[y]; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().id]=s.top().fa; siz[s.top().id]=s.top().siz; s.pop(); } } }D; struct SMT { struct SegmentTree { vector<int>info; }tree[40010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { tree[rt].info.push_back(id); return; } int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } void solve(int rt,int l,int r) { if(l>r) { return; } stack<quality>s; int mid=(l+r)/2,x,y,flag=1; for(int i=0;i<tree[rt].info.size();i++) { x=D.find(e[tree[rt].info[i]].first); y=D.find(e[tree[rt].info[i]].second); if(x==y) { flag=0; break; } else { D.merge(e[tree[rt].info[i]].first,e[tree[rt].info[i]].second+n,s); D.merge(e[tree[rt].info[i]].second,e[tree[rt].info[i]].first+n,s); } } if(flag==0) { for(int i=l;i<=r;i++) { ans[i]=0; } } else { if(l==r) { ans[l]=1; } else { solve(lson(rt),l,mid); solve(rson(rt),mid+1,r); } } D.split(s); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,cnt=0,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>e[i].first>>e[i].second; if(i-1>=1) { T.update(1,1,m,1,i-1,i); } if(i+1<=m) { T.update(1,1,m,i+1,m,i); } } D.init(2*n); T.solve(1,1,m); for(i=1;i<=m;i++) { cnt+=ans[i]; } cout<<cnt<<endl; for(i=1;i<=m;i++) { if(ans[i]==1) { cout<<i<<" "; } } return 0; }
-
但实际上复杂度可以做到线性。二分图存在当且仅当不存在奇环。当图中不存在奇环时,所有边都可以删掉;否则,只能删掉所有奇环交集上不被偶环覆盖的边。树上差分维护奇环、偶环数量即可。
点击查看代码
struct node { int nxt,to,id; }e[20010]; int head[20010],d[2][20010],dep[20010],fa[20010],vis[20010],cnt=1,tot=0; vector<int>ans; void add(int u,int v,int id) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].id=id; head[u]=cnt; } void dfs1(int x,int father,int last) { fa[x]=e[last].id; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { if(dep[e[i].to]==0) { vis[e[i].id]=1; dfs1(e[i].to,x,i); } else { if(dep[x]>dep[e[i].to]) { tot+=(dep[x]-dep[e[i].to]+1)%2; d[(dep[x]-dep[e[i].to]+1)%2][e[i].id]++; d[(dep[x]-dep[e[i].to]+1)%2][e[last].id]++; d[(dep[x]-dep[e[i].to]+1)%2][fa[e[i].to]]--; } } } } } void dfs2(int x,int father,int last) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father&&vis[e[i].id]==1) { dfs2(e[i].to,x,i); d[0][e[last].id]+=d[0][e[i].id]; d[1][e[last].id]+=d[1][e[i].id]; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v,i); add(v,u,i); ans.push_back(i); } for(i=1;i<=n;i++) { if(dep[i]==0) { dfs1(i,0,0); dfs2(i,0,0); } } if(tot!=0) { ans.clear(); for(i=1;i<=m;i++) { if(d[1][i]==tot&&d[0][i]==0) { ans.push_back(i); } } } cout<<ans.size()<<endl; for(i=0;i<ans.size();i++) { cout<<ans[i]<<" "; } return 0; }
\(B\) CF412D Giving Awards
-
建出反图后拓扑排序无法处理欠钱关系中存在环的情况,但可以借鉴其思路。
-
仍考虑自下而上叫人,在叫完所有子节点后再加入答案即可。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],vis[200010],cnt=0; vector<int>ans; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x) { vis[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { dfs(e[i].to); } } ans.push_back(x); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); } for(i=1;i<=n;i++) { if(vis[i]==0) { dfs(i); } } for(i=0;i<ans.size();i++) { cout<<ans[i]<<" "; } return 0; }
\(C\) luogu P4151 [WC2011] 最大XOR和路径
\(D\) luogu P1477 [NOI2008] 假面舞会
\(E\) luogu P7025 [NWRRC2017] Grand Test
\(F\) luogu P4819 [中山市选] 杀人游戏
-
缩完点后对所有入度为 \(0\) 的点询问一次即可。
-
一开始询问若不是杀手,则可以顺次知道所在 \(DAG\) 内能到达的所有人是否是杀手;否则就被直接干掉了。
-
形式化地,设最终有 \(c\) 个入度为 \(0\) 的点,每个点是杀手的概率是 \(\frac{1}{n}\) 则 \(1-\frac{c}{n}\) 即为所求。
-
特别地,需要在缩点后存在入度为 \(0\) 大小(缩点后的大小)为 \(1\) 且能到达的点的入度都 \(\ge 2\) 的点时进行特判,此时可以不对这个点进行询问也可以知道这个点的身份。
-
需要对边进行去重。
点击查看代码
struct node { int nxt,to; }e[600010]; int head[600010],dfn[600010],low[600010],ins[600010],col[600010],u[600010],v[600010],din[600010],siz[600010],tot=0,cnt=0,scc_cnt=0; stack<int>s; vector<int>E[600010]; map<pair<int,int>,int> vis; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; ins[x]=1; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; siz[scc_cnt]++; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,ans=0,flag,i,j; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u[i]>>v[i]; add(u[i],v[i]); } for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } for(i=1;i<=m;i++) { if(col[u[i]]!=col[v[i]]&&vis[make_pair(col[u[i]],col[v[i]])]==0) { vis[make_pair(col[u[i]],col[v[i]])]=1; E[col[u[i]]].push_back(col[v[i]]); din[col[v[i]]]++; } } for(i=1;i<=scc_cnt;i++) { ans+=(din[i]==0); } for(i=1;i<=scc_cnt;i++) { if(din[i]==0&&siz[i]==1) { flag=1; for(j=0;j<E[i].size();j++) { flag&=(din[E[i][j]]>=2); } if(flag==1) { ans--; break; } } } printf("%.6lf\n",1.0-1.0*ans/n); return 0; }
\(G\) luogu P4630 [APIO2018] 铁人两项
-
固定 \(s,f\) 后可行的 \(c\) 的数量为 \(s \to f\) 的所有路径的并的点数 \(-2\) ,考虑通过圆方树计算。
-
将圆方树上圆点的点权设为 \(-1\) ,方点的点权设为其所在点双大小,此时 \(c\) 的数量为圆方树上 \(s \to f\) 的点权和。
-
现在只需要统计圆方树上所有路径的点权和了,简单树形 \(DP\) 处理一下即可。
点击查看代码
struct node { ll nxt,to; }e[400010]; ll head[100010],dfn[100010],low[100010],c[200010],siz[200010],v_dcc=0,tot=0,cnt=0,ans=0,n,nn; stack<ll>s; vector<ll>g[200010]; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(ll x) { nn++; tot++; dfn[x]=low[x]=tot; s.push(x); c[x]=-1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { v_dcc++; g[v_dcc].push_back(x); g[x].push_back(v_dcc); c[v_dcc]++; ll tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); c[v_dcc]++; g[v_dcc].push_back(tmp); g[tmp].push_back(v_dcc); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs(ll x,ll fa) { siz[x]=(x<=n); for(ll i=0;i<g[x].size();i++) { if(g[x][i]!=fa) { dfs(g[x][i],x); ans+=2*siz[x]*siz[g[x][i]]*c[x]; siz[x]+=siz[g[x][i]]; } } ans+=2*siz[x]*(nn-siz[x])*c[x]; } int main() { //#define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll m,u,v,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } v_dcc=n; for(i=1;i<=n;i++) { if(dfn[i]==0) { nn=0; tarjan(i); dfs(i,0); } } cout<<ans<<endl; return 0; }
\(H\) LibreOJ 546. 「LibreOJ β Round #7」网格图
\(I\) luogu P3783 [SDOI2017] 天才黑客
\(J\) luogu P3403 跳楼机
-
同余最短路板子。
- 同余最短路常利用同余性质构造状态来进行优化空间,并使用最短路进行辅助转移,形如 \(f((i+y) \bmod x)=f(i)+y\) 。
- 模数 \(x\) 的选择会影响建边的数量,以至于影响时空复杂度,实际应用时应尽可能选择较小的 \(x\) 。
-
操作 \(4\) 没什么用,可以直接不管。
-
设 \(dis_{i}\) 表示仅通过操作 \(1,2\) 能到达的楼层中满足 \(\bmod z=i\) 时的最小楼层,使用同余最短路进行转移。最终有 \(\sum\limits_{i=1}^{n}[dis_{i} \le h] \times (\left\lfloor \frac{h-dis_{i}}{z} \right\rfloor+1)\) 即为所求。
-
因本题中 \(h\) 较大,不妨先让 \(h\) 减一并钦定起始楼层为 \(0\) 楼即可。
点击查看代码
struct node { ll nxt,to,w; }e[400010]; ll head[400010],dis[400010],vis[400010],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dijkstra(ll s,ll h) { priority_queue<pair<ll,ll> >q; dis[s]=0; q.push(make_pair(-dis[s],s)); while(q.empty()==0) { ll x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; q.push(make_pair(-dis[e[i].to],e[i].to)); } } } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll h,x,y,z,ans=0,i; cin>>h>>x>>y>>z; for(i=0;i<=z-1;i++) { add(i,(i+x)%z,x); add(i,(i+y)%z,y); dis[i]=h; } h--; dijkstra(0,h); for(i=0;i<=z-1;i++) { ans+=(dis[i]<=h)*((h-dis[i])/z+1); } cout<<ans<<endl; return 0; }
\(K\) luogu P3275 [SCOI2011] 糖果
\(L\) luogu P7515 [省选联考 2021 A 卷] 矩阵游戏
-
不妨先钦定第 \(n\) 行和第 \(m\) 列的元素均为 \(0\) ,然后就得到了一组可行解,然后对其进行调整。
-
观察到对某一行或某一列的元素第 \(i\) 位的变化量为 \((-1)^{i}x\) 时,整个矩阵仍满足 \(\{ b \}\) 的限制。
-
设第 \(i\) 行、列的变化量分别为 \(r_{i},c_{i}\) ,则需要满足 \(\forall i \in [1,n],j \in [1,m],0 \le a_{i,j}+(-1)^{i}r_{i}+(-1)^{j}c_{j} \le 10^{6}\) ,移项得到 \(-a_{i,j} \le (-1)^{j}r_{i}+(-1)^{i}c_{j} \le 10^{6}-a_{i,j}\) 。
-
差分约束处理不了 \(r_{i}+c_{j}/-r_{i}-c_{j}\) 的情况,仍需进行调整。
-
对于偶数列 \(j\) 将 \(c_{j}\) 取相反数,奇数行 \(i\) 将 \(r_{i}\) 取相反数,此时的约束条件就只剩下了两个数相减的形式,差分约束即可。
-
需要 \(SLF\) 优化 \(SPFA\) 。
点击查看代码
const ll inf=1000000; struct node { ll nxt,to,w; }e[200010]; ll head[610],a[310][310],b[310][310],dis[610],vis[610],num[610],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } bool spfa(ll s,ll n) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); deque<ll>q; dis[s]=0; vis[s]=1; q.push_back(s); while(q.empty()==0) { ll x=q.front(); vis[x]=0; q.pop_front(); for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; num[e[i].to]=num[x]+1; if(num[e[i].to]>=n+1) { return false; } if(vis[e[i].to]==0) { vis[e[i].to]=1; if(q.empty()==0&&dis[e[i].to]>=dis[q.front()]) { q.push_back(e[i].to); } else { q.push_front(e[i].to); } } } } } return true; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,n,m,i,j,k; scanf("%lld",&t); for(k=1;k<=t;k++) { cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(a,0,sizeof(a)); scanf("%lld%lld",&n,&m); for(i=1;i<=n-1;i++) { for(j=1;j<=m-1;j++) { scanf("%lld",&b[i][j]); } } for(i=n-1;i>=1;i--) { for(j=m-1;j>=1;j--) { a[i][j]=b[i][j]-a[i][j+1]-a[i+1][j]-a[i+1][j+1]; } } for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if((i+j)%2==0) { add(j+n,i,inf-a[i][j]); add(i,j+n,a[i][j]); } else { add(i,j+n,inf-a[i][j]); add(j+n,i,a[i][j]); } } } if(spfa(1,n+m)==true) { printf("YES\n"); for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { printf("%lld ",a[i][j]+(((i+j)%2==0)?dis[i]-dis[j+n]:dis[j+n]-dis[i])); } printf("\n"); } } else { printf("NO\n"); } } return 0; }
\(M\) luogu P6965 [NEERC2016] Binary Code
\(N\) luogu P5332 [JSOI2019] 精准预测
\(O\) CF888G Xor-MST
-
难点在于 \(Boruvka\) 的过程中如何求不同连通块间的最小边权。
-
对全局建立一棵 \(01Trie\) ,再对每个连通块建一棵 \(01Trie\) 。枚举 \(a_{i}\) 的过程中二者相减求出 \(a_{j}\) 即可。
-
为方便代码书写,不妨先对 \(\{ a \}\) 进行去重。
点击查看代码
int a[200010]; pair<int,int>g[200010]; struct Trie { int root[200010],rt_sum=0; struct node { int ch[2],cnt,pos; }tree[200010<<6]; int build_rt() { rt_sum++; return rt_sum; } void insert(int &rt,int s,int id) { rt=(rt==0)?build_rt():rt; int x=rt; tree[x].cnt++; for(int i=30;i>=0;i--) { if(tree[x].ch[(s>>i)&1]==0) { tree[x].ch[(s>>i)&1]=build_rt(); } x=tree[x].ch[(s>>i)&1]; tree[x].cnt++; } tree[x].pos=id; } int merge(int rt1,int rt2) { if(rt1==0||rt2==0) { return rt1+rt2; } tree[rt1].cnt+=tree[rt2].cnt; tree[rt1].ch[0]=merge(tree[rt1].ch[0],tree[rt2].ch[0]); tree[rt1].ch[1]=merge(tree[rt1].ch[1],tree[rt2].ch[1]); return rt1; } pair<int,int>query(int rt1,int rt2,int x) { int ans=0; for(int i=30;i>=0;i--) { if(tree[tree[rt2].ch[(x>>i)&1]].cnt-tree[tree[rt1].ch[(x>>i)&1]].cnt>=1) { rt1=tree[rt1].ch[(x>>i)&1]; rt2=tree[rt2].ch[(x>>i)&1]; } else { ans|=(1<<i); rt1=tree[rt1].ch[((x>>i)&1)^1]; rt2=tree[rt2].ch[((x>>i)&1)^1]; } } return make_pair(ans,tree[rt2].pos); } }T; struct DSU { int fa[200010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(int x,int y) { x=find(x); y=find(y); if(x!=y) { fa[x]=y; } } }D; ll boruvka(int n) { ll ans=0; int flag=1,x,y; pair<int,int>tmp; D.init(n); while(flag==1) { flag=0; fill(g+1,g+1+n,make_pair(0,0x7f7f7f7f)); for(int i=1;i<=n;i++) { x=D.find(i); tmp=T.query(T.root[x],T.root[0],a[i]); y=D.find(tmp.second); if(x!=y&&tmp.first<g[x].second) { g[x]=make_pair(y,tmp.first); } } for(int i=1;i<=n;i++) { x=D.find(i); if(g[x].first!=0&&D.find(x)!=D.find(g[x].first)) { y=D.find(g[x].first); D.merge(x,g[x].first); T.root[y]=T.merge(T.root[x],T.root[y]); ans+=g[x].second; flag=1; } } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } sort(a+1,a+1+n); n=unique(a+1,a+1+n)-(a+1); for(i=1;i<=n;i++) { T.insert(T.root[0],a[i],i); T.insert(T.root[i],a[i],i); } cout<<boruvka(n)<<endl; return 0; }
\(P\) CF141E Clearing Up
\(Q\) luogu P4768 [NOI2018] 归程
\(R\) SP41 WORDS1 - Play on Words
- 多倍经验: UVA10129 单词 Play on Words
- 题解
\(S\) luogu P6628 [省选联考 2020 B 卷] 丁香之路
-
转化为对于每一个 \(i \in [1,n]\) ,寻找一个可重边集 \(E\) 使其包含 \(m\) 条关键边且有一条从 \(s\) 到 \(i\) 的欧拉路径,而这个边集中边权总和的最小值即为所求。
-
欧拉路径还有起点和终点的限制,不妨先把 \((s,i)\) 作为一条虚边加入 \(E\) ,即不参与最终答案统计,此时等价于满足存在一条从 \(s\) 到 \(i\) 的欧拉回路。
-
为使尽可能让图连通,考虑用添加的虚边去拼接成最终情况的实边(关键边和实际为了使图连通加入的边)。
-
对度数为奇数的点,按编号排序后相邻两个数先连一条实边(假如我们要连接 \(u,v\) 两点,加入的虚边为 \(\{ (i,i+1) | i \in [u,v-1] \}\) )使得这两个点的度数变成偶数。
-
此时形成了若干个连通块且每个连通块内部已经形成了欧拉路径,为使其连通再跑一遍 \(Kruskal\) 即可,注意此时一去一回边权需要计算两遍。
点击查看代码
struct node { ll from,to,w; }; ll du[2510]; vector<node>e; bool cmp(node a,node b) { return a.w<b.w; } struct DSU { ll fa[2510],tmp[2510]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=tmp[i]; } } void clear(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(ll x,ll y) { x=find(x); y=find(y); if(x!=y) { fa[x]=y; } } }D; ll kruskal() { sort(e.begin(),e.end(),cmp); ll ans=0,x,y; for(ll i=0;i<e.size();i++) { x=D.find(e[i].from); y=D.find(e[i].to); if(x!=y) { D.fa[x]=y; ans+=e[i].w; } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,s,u,v,ans,sum=0,last,i,j,k; cin>>n>>m>>s; D.clear(n); for(i=1;i<=m;i++) { cin>>u>>v; du[u]++; du[v]++; sum+=abs(u-v); D.merge(u,v); } for(i=1;i<=n;i++) { D.tmp[i]=D.fa[i]; } for(i=1;i<=n;i++) { last=0; ans=sum; du[s]++; du[i]++; e.clear(); D.init(n); for(j=1;j<=n;j++) { if(du[j]%2==1) { if(last==0) { last=j; } else { ans+=j-last; for(k=last;k<=j-1;k++) { D.merge(k,k+1); } last=0; } } } for(j=1;j<=n;j++) { if(du[j]!=0) { if(last!=0&&D.find(last)!=D.find(j)) { e.push_back((node){j,last,j-last}); } last=j; } } du[s]--; du[i]--; cout<<ans+2*kruskal()<<" "; } return 0; }
\(T\) luogu P6624 [省选联考 2020 A 卷] 作业题
\(U\) [AGC051D] C4
\(V\) luogu P6657 【模板】LGV 引理
\(W\) luogu P7736 [NOI2021] 路径交点
\(X\) luogu P7428 [THUPC2017] 母亲节的礼物
\(Y\) luogu P4386 [SHOI2015] 零件组装机
\(Z\) luogu P1173 [NOI2016] 网格
\(AA\) luogu P3687 [ZJOI2017] 仙人掌
\(AB\) luogu P3180 [HAOI2016] 地图
\(AC\) luogu P2371 [国家集训队] 墨墨的等式
-
观察到 \(\{ b \}\) 可差分,然后同余最短路维护即可。
点击查看代码
struct node { ll nxt,to,w; }e[6000010]; ll head[6000010],dis[6000010],vis[6000010],a[6000010],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dijkstra(ll s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<ll,ll> >q; dis[s]=0; q.push(make_pair(-dis[s],s)); while(q.empty()==0) { ll x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; q.push(make_pair(-dis[e[i].to],e[i].to)); } } } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,l,r,ans=0,i,j; cin>>n>>l>>r; l--; for(i=1;i<=n;i++) { cin>>a[i]; if(i>=2) { for(j=0;j<=a[1]-1;j++) { add(j,(j+a[i])%a[1],a[i]); } } } dijkstra(0); for(i=0;i<=a[1]-1;i++) { ans+=(dis[i]<=r)*((r-dis[i])/a[1]+1); ans-=(dis[i]<=l)*((l-dis[i])/a[1]+1); } cout<<ans<<endl; return 0; }
\(AD\) CF1450E Capitalism
\(AE\) CF1416D Graph and Queries
\(AF\) luogu P3350 [ZJOI2016] 旅行者
\(AG\) LibreOJ 571. 「LibreOJ Round #11」Misaka Network 与 Accelerator
\(AH\) luogu P4180 [BJWC2010] 严格次小生成树
-
随便找到一棵最小生成树后考虑非树边的替换。倍增或树剖套 \(ST\) 表维护最大值及严格次大值即可。
点击查看代码
struct node { ll from,to,w; }; ll fa[600010][25],dep[600010],vis[600010]; pair<ll,ll>f[600010][25]; vector<node>g; vector<pair<ll,ll> >e[600010]; void add(ll u,ll v,ll w) { e[u].push_back(make_pair(v,w)); } bool cmp(node a,node b) { return a.w<b.w; } struct DSU { ll fa[100010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; ll krsukal(ll n) { sort(g.begin(),g.end(),cmp); D.init(n); ll ans=0,u,v; for(ll i=0;i<g.size();i++) { u=D.find(g[i].from); v=D.find(g[i].to); if(u!=v) { D.fa[u]=v; ans+=g[i].w; vis[i]=1; add(g[i].from,g[i].to,g[i].w); add(g[i].to,g[i].from,g[i].w); } } return ans; } pair<ll,ll> add(pair<ll,ll>a,pair<ll,ll>b) { pair<ll,ll> tmp; tmp.first=max(a.first,b.first); if(a.first>b.first) { tmp.second=max(a.second,b.first); } if(a.first==b.first) { tmp.second=max(a.second,b.second); } if(a.first<b.first) { tmp.second=max(a.first,b.second); } return tmp; } void dfs(ll x,ll father,ll w) { dep[x]=dep[father]+1; fa[x][0]=father; f[x][0]=make_pair(w,-0x3f3f3f3f); for(ll i=1;i<=20;i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; f[x][i]=add(f[x][i-1],f[fa[x][i-1]][i-1]); } for(ll i=0;i<e[x].size();i++) { if(e[x][i].first!=father) { dfs(e[x][i].first,x,e[x][i].second); } } } pair<ll,ll>lca(ll x,ll y) { pair<ll,ll>maxx=make_pair(0,-0x3f3f3f3f); if(dep[x]>dep[y]) { swap(x,y); } for(int i=20;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { maxx=add(maxx,f[y][i]); y=fa[y][i]; } } if(x==y) { return maxx; } else { for(int i=20;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { maxx=add(maxx,add(f[x][i],f[y][i])); x=fa[x][i]; y=fa[y][i]; } } maxx=add(maxx,add(f[x][0],f[y][0])); return maxx; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,u,v,w,sum,ans=0x7f7f7f7f7f7f7f7f,i; pair<ll,ll>tmp; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v>>w; g.push_back((node){u,v,w}); } sum=krsukal(n); dfs(1,0,0); for(i=0;i<g.size();i++) { if(vis[i]==0) { tmp=lca(g[i].from,g[i].to); if(tmp.first==g[i].w&&tmp.second!=-0x3f3f3f3f) { ans=min(ans,sum-tmp.second+g[i].w); } if(tmp.first<g[i].w&&tmp.first!=0) { ans=min(ans,sum-tmp.first+g[i].w); } } } cout<<ans<<endl; return 0; }
\(AI\) luogu P2144 [FJOI2007] 轮状病毒
\(AJ\) luogu P5163 WD与地图
\(AK\) BZOJ3331 压力
-
在圆方树上做树上差分。
点击查看代码
struct node { int nxt,to; }e[400010]; int head[100010],dfn[100010],low[100010],siz[200010],son[200010],fa[200010],dep[200010],top[200010],d[200010],tot=0,cnt=0,v_dcc=0; vector<int>g[200010]; stack<int>s; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(low[e[i].to]==dfn[x]) { v_dcc++; g[v_dcc].push_back(x); g[x].push_back(v_dcc); int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); g[v_dcc].push_back(tmp); g[tmp].push_back(v_dcc); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=0;i<g[x].size();i++) { if(g[x][i]!=father) { dfs1(g[x][i],x); siz[x]+=siz[g[x][i]]; son[x]=(siz[g[x][i]]>siz[son[x]])?g[x][i]:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa[x]&&g[x][i]!=son[x]) { dfs2(g[x][i],g[x][i]); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } void dfs(int x) { for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa[x]) { dfs(g[x][i]); d[x]+=d[g[x][i]]; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,q,i; cin>>n>>m>>q; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } v_dcc=n; for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } dfs1(1,0); dfs2(1,1); for(i=1;i<=q;i++) { cin>>u>>v; d[u]++; d[v]++; d[lca(u,v)]--; d[fa[lca(u,v)]]--; } dfs(1); for(i=1;i<=n;i++) { cout<<d[i]<<endl; } return 0; }
\(AL\) luogu P5631 最小mex生成树
\(AM\) luogu P5633 最小度限制生成树
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18621147,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。