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

\(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 最小度限制生成树

posted @ 2024-12-21 20:37  hzoi_Shadow  阅读(15)  评论(0编辑  收藏  举报
扩大
缩小