图论综合

用户头像 发布于 2025-08-21 137 次阅读 OI


S+图论综合

A. 说谎的牛牛

枚举每一个牛作为说谎的牛是否矛盾,
再差分约束建图,跑一边SPFA就可以了,
代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=105;
const int M=1005;
struct node{
	int v,w;
};
int n,q,ans;
int a[M],b[M],c[M];
int s[N],t[N],d[N];
vector<node> g[N];
int f(int x){
	queue<int> q;
	for(int i=1;i<=n;i++) t[i]=1,s[i]=d[i]=0,q.push(i);
	while(q.size()){
		int u=q.front();
		q.pop();
		if(s[u]>=n) return 0;
		t[u]=0;
		for(int i=0;i<g[u].size();i++){
			int v=g[u][i].v,w=g[u][i].w;
			if(d[u]+w<d[v]){
				d[v]=d[u]+w;
				if(!t[v]){
					t[v]=1;
					q.push(v);
					s[v]++;
				}
			}
		}
	}
	return 1;
}
signed main(){
	cin>>n>>q;
	for(int i=1;i<=q;i++) cin>>a[i]>>b[i]>>c[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) g[j].clear();
		for(int j=1;j<=q;j++){
			if(a[j]==i) g[c[j]].push_back({b[j],0});
			else g[b[j]].push_back({c[j],-1});
		}
		ans+=f(i);
	}
	cout<<ans;
	return 0;
}

B. 安慰奶牛

这道题又有点权又有边权,
考虑能否一起讨论。
发现最终每一条边对答案的贡献是这条边边权的两倍加上两个顶点的点权,
所以根据这个跑一边最小生成树即可。
注意有一个过夜点,
贪心选择一个点权最小的点当作过夜点即可。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct node{
	int v,w;
};
vector<node> t[N];
int n,m,mn=INT_MAX,ans;
int dis[N],vis[N],c[N];
void prim(){
	dis[1]=0;
	for(int i=0;i<t[1].size();i++){
		int v=t[1][i].v;
		dis[v]=min(dis[v],t[1][i].w);
	}
	int p=1;
	for(int i=1;i<=n-1;i++){
		vis[p]=1;
		int minn=0x3f3f3f3f;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&minn>dis[j]){
				minn=dis[j];
				p=j;
			}
		}
		ans+=minn;
		for(int j=0;j<t[p].size();j++){
			int v=t[p][j].v;
			if(dis[v]>t[p][j].w&&!vis[v]) dis[v]=t[p][j].w;
		}
	}
}
int main(){
	cin>>n>>m;
	memset(dis,0x3f,sizeof dis);
	for(int i=1;i<=n;i++) cin>>c[i],mn=min(mn,c[i]);
	while(m--){
		int u,v,w;
		cin>>u>>v>>w;
		t[u].push_back({v,w*2+c[u]+c[v]});
		t[v].push_back({u,w*2+c[u]+c[v]});
	}
	prim();
	cout<<ans+mn;
	return 0;
}

D. 毕加猪

问题可以转化为找到一条从 $s$ 到 $t$ 的路径,
使得该路径的总权值不超过 $-k$。
跑Bellman-Ford即可(要判断负环)。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct node{
	int u,v,w;
}e[N];
int n,m,k;
int dis[N];
bool f(int s,int t){
	memset(dis,0x3f,sizeof dis);
	dis[s]=0;
	for(int i=1;i<n;i++) for(int j=1;j<=m;j++) if(dis[e[j].v]>dis[e[j].u]+e[j].w) dis[e[j].v]=dis[e[j].u]+e[j].w;
	for(int i=1;i<n;i++) for(int j=1;j<=m;j++) if(dis[e[j].v]>dis[e[j].u]+e[j].w) dis[e[j].v]=-1e9;
	return dis[t]<=-k;
}
int main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n>>m>>k;
		for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
		int s,t;
		cin>>s>>t;
		if(f(s,t)||f(t,s)) cout<<"yes\n";
		else cout<<"no\n"; 
	}
	return 0;
}

E. 糖果

根据性质差分约束建图,
然后跑一边最短路即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct Edge{
	int v,w;
	int next;
}edge[N*10];
int head[N*3],tot;
inline void add(int u,int v,int w){
	edge[++tot].v=v;
	edge[tot].w=w;
	edge[tot].next=head[u];
	head[u]=tot;
}
inline int read(){
	int x=0;
	bool flag=1;
	char c=getchar_unlocked();
	while(c<'0'||c>'9'){
		if(c=='-')
			flag=0;
		c=getchar_unlocked();
	}
	while(c>='0'&&c<='9'){
		x=(x<<1)+(x<<3)+c-'0';
		c=getchar_unlocked();
	}
	return (flag?x:~(x-1));
}
int dis[N],cnt[N],vis[N];
int n,m;
long long ans;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int op,u,v;
		op=read(),u=read(),v=read();
		if(op==1) add(u,v,0), add(v,u,0);
		if(op==2){
			if(u==v){
				cout<<"-1";
				return 0;
			} 
			add(u,v,1);
		}
		if(op==3) add(v,u,0);
		if(op==4){
			if(u==v){
				cout<<"-1";
				return 0;
			} 
			add(v,u,1);
		}
		if(op==5) add(u,v,0);
	}
	for(int i=n;i;i--) add(0,i,1);
	deque<int> q;
	q.push_back(0);
	vis[0]=1;
	dis[0]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop_front();
		vis[u]=0;
		for(int i=head[u];i;i=edge[i].next){
			int v=edge[i].v, w=edge[i].w;
			if(dis[v]<dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					vis[v]=1;
					if(!q.empty()&& dis[v]>dis[q.front()]) q.push_front(v);
					else q.push_back(v);
					if(++cnt[v]>n){
						cout<<"-1";
						return 0;
					}
				}
			}
		}
	}
	for(int i=1;i<=n;i++) ans+=dis[i];
	cout<<ans;
	return 0;
}

I. 墨墨的等式

通过非零数字的最小值 $mn$ 构建模 $mn$ 的剩余系图,
用最短路求出每个余数类的最小可表示数 $dis[i]$,
然后利用公式 $\sum_{i=0}^{mn-1} \max(0, (u - dis[i]) / mn + 1)$ 统计区间 $[l, r]$ 内能被表示的 $b$ 的个数。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int N=5e5+5;
const ll inf=1e18;
int n,a[15],x,m,mn=0x3f3f3f3f,inq[N];
ll l,r,dis[N];
vector<P> e[N];
void spfa(){
	queue<int> que;
	fill(dis,dis+mn,inf);
	dis[0]=0;inq[0]=1;
	que.push(0);
	while(!que.empty()){
		int u=que.front();que.pop();inq[u]=0;
		for(int i=0;i<(int)e[u].size();i++){
			int v=e[u][i].first,cost=e[u][i].second;
			if(dis[v]>dis[u]+cost){
				dis[v]=dis[u]+cost;
				if(!inq[v]){
					inq[v]=1;
					que.push(v);
				}
			}
		}
	}
}
ll query(ll u){
	ll res=0;
	for(int i=0;i<mn;i++){
		if(dis[i]<=u)
			res+=(u-dis[i])/mn+1;
	}
	return res;
}
int main(){
	cin>>n>>l>>r;
	for(int i=1;i<=n;i++){
		cin>>x;
		if(x) a[++m]=x,mn=min(mn,x);
	}
	for(int i=0;i<mn;i++)
		for(int j=1;j<=m;j++)
			if(a[j]!=mn)
				e[i].push_back(P((i+a[j])%mn,a[j]));
	spfa();
	cout<<query(r)-query(l-1);
	return 0;
}