2.图论
图论/省选图论专题
开题顺序:
CF19E Fairy
-
观察到
,直接线段树分治即可,时间复杂度为 。点击查看代码
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; }
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; }
luogu P4151 [WC2011] 最大XOR和路径
luogu P1477 [NOI2008] 假面舞会
-
若没有环,最大答案显然为所有极大连通块内最长链的长度,最小答案为
。 -
否则,最大答案为所有环长的
。同 luogu P4151 [WC2011] 最大XOR和路径 ,考虑通过 树上的路径拼接成环。此时最小答案即为最大答案的一个 的因子。 -
难点在于环有相交的部分导致出现环套环了怎么处理。由更相减损法,不妨从
到 连接一条权值为 的边,从 到 连接一条权值为 的边。正确性显然。点击查看代码
struct node { int nxt,to,w; }e[2000010]; int head[100010],dis[100010],vis[100010],maxx,minn,sum,cnt=0; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(int x,int d) { if(dis[x]!=0) { sum=__gcd(sum,abs(d-dis[x])); return; } dis[x]=d; vis[x]=1; maxx=max(maxx,d); minn=min(minn,d); for(int i=head[x];i!=0;i=e[i].nxt) dfs(e[i].to,d+e[i].w); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,ans=0,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v,1); add(v,u,-1); } for(i=1;i<=n;i++) { if(vis[i]==0) { maxx=0; minn=0x7f7f7f7f; dfs(i,0); ans+=maxx-minn+1; } } if(sum==0) { if(ans>=3) cout<<ans<<" "<<3<<endl; else cout<<-1<<" "<<-1<<endl; } else { if(sum>=3) { for(i=3;i<=sum;i++) { if(sum%i==0) { cout<<sum<<" "<<i<<endl; break; } } } else cout<<-1<<" "<<-1<<endl; } return 0; }
luogu P7025 [NWRRC2017] Grand Test
luogu P4819 [中山市选] 杀人游戏
-
缩完点后对所有入度为
的点询问一次即可。 -
一开始询问若不是杀手,则可以顺次知道所在
内能到达的所有人是否是杀手;否则就被直接干掉了。 -
形式化地,设最终有
个入度为 的点,每个点是杀手的概率是 则 即为所求。 -
特别地,需要在缩点后存在入度为
大小(缩点后的大小)为 且能到达的点的入度都 的点时进行特判,此时可以不对这个点进行询问也可以知道这个点的身份。 -
需要对边进行去重。
点击查看代码
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; }
luogu P4630 [APIO2018] 铁人两项
-
固定
后可行的 的数量为 的所有路径的并的点数 ,考虑通过圆方树计算。 -
将圆方树上圆点的点权设为
,方点的点权设为其所在点双大小,此时 的数量为圆方树上 的点权和。 -
现在只需要统计圆方树上所有路径的点权和了,简单树形
处理一下即可。点击查看代码
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; }
LibreOJ 546. 「LibreOJ β Round #7」网格图
luogu P3783 [SDOI2017] 天才黑客
luogu P3403 跳楼机
-
同余最短路板子。
- 同余最短路常利用同余性质构造状态来进行优化空间,并使用最短路进行辅助转移,形如
。 - 模数
的选择会影响建边的数量,以至于影响时空复杂度,实际应用时应尽可能选择较小的 。
- 同余最短路常利用同余性质构造状态来进行优化空间,并使用最短路进行辅助转移,形如
-
操作
没什么用,可以直接不管。 -
设
表示仅通过操作 能到达的楼层中满足 时的最小楼层,使用同余最短路进行转移。最终有 即为所求。 -
因本题中
较大,不妨先让 减一并钦定起始楼层为 楼即可。点击查看代码
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; }
luogu P3275 [SCOI2011] 糖果
luogu P7515 [省选联考 2021 A 卷] 矩阵游戏
-
不妨先钦定第
行和第 列的元素均为 ,然后就得到了一组可行解,然后对其进行调整。 -
观察到对某一行或某一列的元素第
位的变化量为 时,整个矩阵仍满足 的限制。 -
设第
行、列的变化量分别为 ,则需要满足 ,移项得到 。 -
差分约束处理不了
的情况,仍需进行调整。 -
对于偶数列
将 取相反数,奇数行 将 取相反数,此时的约束条件就只剩下了两个数相减的形式,差分约束即可。 -
需要
优化 。点击查看代码
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; }
luogu P6965 [NEERC2016] Binary Code
luogu P5332 [JSOI2019] 精准预测
CF888G Xor-MST
-
难点在于
的过程中如何求不同连通块间的最小边权。 -
对全局建立一棵
,再对每个连通块建一棵 。枚举 的过程中二者相减求出 即可。 -
为方便代码书写,不妨先对
进行去重。点击查看代码
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; }
CF141E Clearing Up
luogu P4768 [NOI2018] 归程
SP41 WORDS1 - Play on Words
- 多倍经验: UVA10129 单词 Play on Words
- 题解
luogu P6628 [省选联考 2020 B 卷] 丁香之路
-
转化为对于每一个
,寻找一个可重边集 使其包含 条关键边且有一条从 到 的欧拉路径,而这个边集中边权总和的最小值即为所求。 -
欧拉路径还有起点和终点的限制,不妨先把
作为一条虚边加入 ,即不参与最终答案统计,此时等价于满足存在一条从 到 的欧拉回路。 -
为使尽可能让图连通,考虑用添加的虚边去拼接成最终情况的实边(关键边和实际为了使图连通加入的边)。
-
对度数为奇数的点,按编号排序后相邻两个数先连一条实边(假如我们要连接
两点,加入的虚边为 )使得这两个点的度数变成偶数。 -
此时形成了若干个连通块且每个连通块内部已经形成了欧拉路径,为使其连通再跑一遍
即可,注意此时一去一回边权需要计算两遍。点击查看代码
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; }
luogu P6624 [省选联考 2020 A 卷] 作业题
[AGC051D] C4
luogu P6657 【模板】LGV 引理
luogu P7736 [NOI2021] 路径交点
luogu P7428 [THUPC2017] 母亲节的礼物
luogu P4386 [SHOI2015] 零件组装机
luogu P1173 [NOI2016] 网格
luogu P3687 [ZJOI2017] 仙人掌
luogu P3180 [HAOI2016] 地图
luogu P2371 [国家集训队] 墨墨的等式
-
观察到
可差分,然后同余最短路维护即可。点击查看代码
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; }
CF1450E Capitalism
CF1416D Graph and Queries
luogu P3350 [ZJOI2016] 旅行者
LibreOJ 571. 「LibreOJ Round #11」Misaka Network 与 Accelerator
luogu P4180 [BJWC2010] 严格次小生成树
-
随便找到一棵最小生成树后考虑非树边的替换。倍增或树剖套
表维护最大值及严格次大值即可。点击查看代码
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; }
luogu P2144 [FJOI2007] 轮状病毒
luogu P5163 WD与地图
-
考虑将删边转化为加边,线段树合并维护前
大的数之和。 -
现在问题来到了如何快速地维护加边后强连通分量的变化。
-
观察到每条边第一次被加入强连通分量的时间具有单调性,考虑整体二分。
-
将出现时间
的边加入一张新图中跑 进行缩点,为保证复杂度递归右区间时继承当前区间已经缩完点的图,递归左边的时候再撤销回去,可撤销并查集维护强连通分量即可。 -
清空时只对连边时涉及到的两个端点进行清空。
点击查看代码
const ll inf=1000000000; struct edge { ll u,v,t; bool operator < (const edge &another) const { return t<another.t; } }q[200010],tmp[2][200010]; struct quality { ll x,fa,siz; }; ll c[200010],op[200010],a[200010],b[200010],dfn[200010],low[200010],ins[200010],tot=0; vector<ll>e[200010]; vector<pair<ll,ll> >pos[200010]; stack<ll>s,ans; map<pair<ll,ll>,ll>tim; void add(ll u,ll v) { e[u].push_back(v); } struct SMT { ll root[200010],rt_sum; struct SegmentTree { ll ls,rs,siz,sum; }tree[200010<<6]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) void pushup(ll rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz; tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].siz=tree[rt_sum].sum=0; return rt_sum; } void update(ll &rt,ll l,ll r,ll pos,ll val) { if(rt==0) rt=build_rt(); if(l==r) { tree[rt].siz+=val; tree[rt].sum+=val*l; return; } ll mid=(l+r)/2; if(pos<=mid) update(lson(rt),l,mid,pos,val); else update(rson(rt),mid+1,r,pos,val); pushup(rt); } ll merge(ll rt1,ll rt2,ll l,ll r) { if(rt1==0||rt2==0) return rt1+rt2; if(l==r) { tree[rt1].siz+=tree[rt2].siz; tree[rt1].sum+=tree[rt2].sum; return rt1; } ll mid=(l+r)/2; lson(rt1)=merge(lson(rt1),lson(rt2),l,mid); rson(rt1)=merge(rson(rt1),rson(rt2),mid+1,r); pushup(rt1); return rt1; } ll query(ll rt,ll l,ll r,ll k) { if(rt==0) return 0; if(tree[rt].siz<=k) return tree[rt].sum; if(l==r) return min(k,tree[rt].siz)*l; ll mid=(l+r)/2; if(tree[rson(rt)].siz>=k) return query(rson(rt),mid+1,r,k); else return tree[rson(rt)].sum+query(lson(rt),l,mid,k-tree[rson(rt)].siz); } }T; struct DSU { ll fa[200010],siz[200010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } ll find(ll x) { return fa[x]==x?x:find(fa[x]); } void merge(ll x,ll y,stack<quality>&s) { x=find(x); y=find(y); if(x!=y) { if(siz[x]<siz[y]) swap(x,y); s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); fa[y]=x; siz[x]+=siz[y]; } } void _merge(ll x,ll y) { x=find(x); y=find(y); if(x!=y) { if(siz[x]<siz[y]) swap(x,y); T.root[x]=T.merge(T.root[x],T.root[y],0,inf); fa[y]=x; siz[x]+=siz[y]; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().x]=s.top().fa; siz[s.top().x]=s.top().siz; s.pop(); } } }D; void clear(vector<ll>&v) { tot=0; while(s.empty()==0) s.pop(); for(ll i=0;i<v.size();i++) { dfn[v[i]]=low[v[i]]=ins[v[i]]=0; e[v[i]].clear(); } } void tarjan(ll x,stack<quality>&_s) { tot++; dfn[x]=low[x]=tot; s.push(x); ins[x]=1; for(ll i=0;i<e[x].size();i++) { if(dfn[e[x][i]]==0) { tarjan(e[x][i],_s); low[x]=min(low[x],low[e[x][i]]); } else if(ins[e[x][i]]==1) low[x]=min(low[x],dfn[e[x][i]]); } if(dfn[x]==low[x]) { int tmp=0; while(tmp!=x) { tmp=s.top(); s.pop(); ins[tmp]=0; D.merge(tmp,x,_s); } } } void solve(ll l,ll r,ll ql,ll qr) { if(ql>qr) return; if(l==r) { for(ll i=ql;i<=qr;i++) pos[l].push_back(make_pair(q[i].u,q[i].v)); return; } ll mid=(l+r)/2,x=0,y=0,_x,_y; vector<ll>v; stack<quality>s; for(ll i=ql;i<=qr;i++) { if(q[i].t<=mid) { _x=D.find(q[i].u); _y=D.find(q[i].v); if(_x!=_y) { add(_x,_y); v.push_back(_x); v.push_back(_y); } } } for(ll i=0;i<v.size();i++) if(dfn[v[i]]==0) tarjan(v[i],s); for(ll i=ql;i<=qr;i++) { if(q[i].t<=mid&&D.find(q[i].u)==D.find(q[i].v)) { x++; tmp[0][x]=q[i]; } else { y++; tmp[1][y]=q[i]; } } for(ll i=1;i<=x;i++) q[ql+i-1]=tmp[0][i]; for(ll i=1;i<=y;i++) q[ql+x+i-1]=tmp[1][i]; clear(v); solve(mid+1,r,ql+x,qr); D.split(s); solve(l,mid,ql,ql+x-1); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,k,x,i,j; cin>>n>>m>>k; for(i=1;i<=n;i++) cin>>c[i]; for(i=1;i<=m;i++) { cin>>q[i].u>>q[i].v; } for(i=1;i<=k;i++) { cin>>op[i]>>a[i]>>b[i]; if(op[i]==1) tim[make_pair(a[i],b[i])]=k-i+1; if(op[i]==2) c[a[i]]+=b[i]; } for(i=1;i<=m;i++) q[i].t=tim[make_pair(q[i].u,q[i].v)]; sort(q+1,q+1+m); D.init(n); solve(0,k+1,1,m); for(i=1;i<=n;i++) T.update(T.root[i],0,inf,c[i],1); D.init(n); reverse(op+1,op+1+k); reverse(a+1,a+1+k); reverse(b+1,b+1+k); for(i=0;i<=k;i++) { for(j=0;j<pos[i].size();j++) D._merge(pos[i][j].first,pos[i][j].second); if(op[i]==2) { x=D.find(a[i]); T.update(T.root[x],0,inf,c[a[i]],-1); c[a[i]]-=b[i]; T.update(T.root[x],0,inf,c[a[i]],1); } if(op[i]==3) ans.push(T.query(T.root[D.find(a[i])],0,inf,b[i])); } for(;ans.empty()==0;ans.pop()) cout<<ans.top()<<endl; return 0; }
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; }
luogu P5631 最小mex生成树
luogu P5633 最小度限制生成树
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18621147,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下