title: 构图方法与图论算法应用
tags: [图论]
categories: [OI, 算法专题]

P4568 JLOI2011 飞行路线

原题 · 标签:#图论

考点

分层图板子题

题意

  • 给定 \(n\) 个城市、\(m\) 条无向边(航线),每条边有价格。从起点 \(s\) 到终点 \(t\),可以免费乘坐最多 \(k\) 条航线,求最少花费。

数据范围:\(2 \le n \le 10^4\),\(1 \le m \le 5 \times 10^4\),\(0 \le k \le 10\),\(0 \le c \le 10^3\)。

思路

分层图板子题。
以 \((u,k)\) 表示在 \(u\) 节点,已免费乘坐 \(k\) 次航线为图上的节点,建图。跑最短路。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
const int N = 1.1e5+10;
vector <pair<int,int> >g[N];
priority_queue <pair<int,int> > q;
int n,m,k;
int s,t;
int dis[N],vis[N];
void dijkstra(){
	memset(dis,0x3f,sizeof dis);
	dis[s] = 0;
	q.push({0,s});
	while (!q.empty()){
		int u = q.top().second;
		q.pop();
		if (vis[u]) continue;
		for(auto line:g[u]){
			int v = line.first;
			int val = line.second;
			if (dis[v] > dis[u] + val){
				dis[v] = dis[u] + val;
				if (!vis[u]){
					q.push({-1 * dis[v],v});
				}
			}
		}
	}
}
signed main(){
	cin >> n >> m >> k;
	cin >> s >> t;
	rep(i,1,m){
		int a,b,c;
		cin >> a >> b >> c;
		g[a].push_back({b,c});
		g[b].push_back({a,c});
		rep(i,1,k){
			int u = a + i * n;
			int v = b + i * n;
			g[u].push_back({v,c});
			g[v].push_back({u,c});
			g[u-n].push_back({v,0});
			g[v-n].push_back({u,0});
		}
	}
	dijkstra();
	int ans = 0x3f3f3f3f;
	rep(i,0,k){
		ans = min(ans,dis[t+i*n]);
	}
	cout << ans;
	return 0;
}

CF2184E Exquisite Array

原题 · 标签:#并查集

考点

离线并查集

题意

  • 定义一个子数组为 \(k\)-精致:长度至少为 \(2\),且任意相邻两数之差的绝对值 \(\ge k\)。
  • 给定一个 \(1 \sim n\) 的排列 \(p\),对每个 \(k \in [1, n-1]\),求 \(k\)-精致子数组的数量。

数据范围:\(1 \le t \le 25000\),\(2 \le n \le 10^5\),\(\sum n \le 2 \times 10^5\)。

思路

将满足题目要求的连续元素看做连通块,对于单个 \(k\),题目实际上要求在连通块内指定不相同两点的方案数。
使用并查集以维护连通块,记录连通块大小以方便统计答案。
考虑以从 \(n-1\) 到 \(1\) 的逆序遍历 \(k\),来统计答案。对于差为 \(k\) 的两个相邻元素,合并两个节点对应的连通块。对答案的贡献是合并前两个连通块大小相乘。因为左端点可以落在左边连通块的任意一点,右端点可以落在右边连通块的任意一点,其相乘就是此次操作对答案的影响。
左右端点都在一个连通块的情况我们在以前的 \(k\) 里已经计算贡献了,所以不会重复计算答案所有的答案;所有的答案都需要这样的左右端点,所以不会漏算答案。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
int t;
const int N = 2e5+10;
int n;
int p[N];
int fa[N];
int sz[N];
int find(int u){
	return (fa[u] == u ? u : fa[u] = find(fa[u]));
}
void merge(int u,int v){
	int fu = find(u),fv = find(v);
	if (fu != fv){
		fa[fu] = fv;
		sz[fv] += sz[fu];
	}
}
void init(){
	f(i,1,n){
		fa[i] = i;
		sz[i] = 1;
	}
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> t;
	while (t--){
		cin >> n;
		init();
		f(i,1,n) cin >> p[i];
		vector <int> g[n+1];
		vector <int> ans(n+1);
		f(i,2,n){
			g[abs(p[i] - p[i-1])].push_back(i);
		}
		int cnt = 0;
		for (int i=n-1;i>=1;i--){
			for (int v:g[i]){
//				Dl(v);
				cnt += sz[find(v)] * sz[find(v-1)];
				merge(v,v-1);
			}
			ans[i] = cnt;
		}
		f(i,1,n-1){
			cout << ans[i] << " ";
		}
		cout << "\n";
	}
	return 0;
}

CF2179F Blackslex and Another RGB Walking

原题 · 标签:#图论

考点

通信题,二分图染色中蕴含方向关系

题意

  • 这是一个二次运行(通讯)问题。程序会运行两次,分别扮演特工(Agent)和 Blackslex,两次运行之间不能直接通信。
  • 给定一个 \(n\) 个点 \(m\) 条边的连通无向二分图。Blackslex 从顶点 \(1\) 出发,被随机传送到某未知顶点 \(v\)(\(v \ne 1\)),需逐步走回顶点 \(1\),但他对图结构一无所知。
  • 第一次运行(Agent):你需要为每个顶点染三种颜色之一(r/g/b),染色方案会在第二次运行时被使用。
  • 第二次运行(Blackslex):每次询问给出当前顶点 \(v\) 所有邻居的颜色(顺序随机),你需要选择一个邻居前往,使得能朝顶点 \(1\) 更近一步。

数据范围:所有测试用例 \(\sum n, \sum m, \sum q \le 10^5\),\(\sum d(v) \le 2 \times 10^5\)。图为连通无向二分图,无重边自环。

思路

指定红色为 \(0\),绿色为 \(1\),蓝色为 \(2\)。
发现可以填充三种颜色,可以由取模的关系来蕴含方向。具体的,对于边 \((u,v)\),已知 \(u\) 的颜色,指定 \(v\) 的颜色是 \((c_u + 1) \bmod m\)。
于是,当位于 \(v\) 时,要走的节点 \(u\) 的颜色就一定是 \((c_v - 1) \bmod m\)。
二分图会保证 bfs 得到的同层节点不会相邻,同时 \(+1\),\(-1\) 的相邻节点关系是有方向的。所以这样写是对的。实际上,所有无奇环的图都有这个性质。
实现时,对于第一次运行,跑一遍 bfs 染色。对于第二次运行,如果邻居只有一种颜色,只能输出这一种颜色;如果邻居有两种颜色,邻居没有的颜色就是当前节点的颜色,带入上面的式子即可。邻居不可能有三种颜色。

代码

#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
string s;
// r 0 g 1 b 2 第二次时 2->1->0->2
int t;
void solve1(){
	int n,m; cin >> n >> m;
	vector <int> g[n+1];
	vector <char> ans(n+1);
	vector <bool> vis(n+1);
	f(i,1,m){
		int a,b;cin >> a >> b;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	queue <pii> q;
	q.push({1,0});
	while (!q.empty()){
		int u = q.front().first;
		int val = q.front().second;
		q.pop();
		if (vis[u]) continue;
		vis[u] = 1;
		if (val == 0) ans[u] = 'r';
		else if (val == 1) ans[u] = 'g';
		else ans[u] = 'b';
		for (int v : g[u]){
			q.push({v,(val + 1) % 3});
		}
	}
	f(i,1,n){
		cout << ans[i];
	}
	cout << "\n";
}
//int cnt;
void solve2(){
	int q;
	cin >> q;
	while (q--){
		int dv;string c; cin >> dv >> c;
//		Dl(c.size());
		bool haver=0,haveg=0,haveb=0;
		for (char ch:c){
			if (ch == 'r') haver = 1;
			if (ch == 'g') haveg = 1;
			if (ch == 'b') haveb = 1;
		}
		char toc;
		if ((haver && !haveg && !haveb) || (!haver && haveg && !haveb) || (!haver && !haveg && haveb)){
			if (haver) toc = 'r';
			if (haveg) toc = 'g';
			if (haveb) toc = 'b';
		}
		else{
			if (!haver) toc = 'b';
			if (!haveg) toc = 'r';
			if (!haveb) toc = 'g';
		}
		f(i,0,c.size()-1){
//			D(i);
			if (toc == c[i]){
//				cnt++;
//				if (cnt > 20) exit(0);
				cout << i + 1 << "\n";
				break;
			}
		}
	}
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> s;
	if (s == "first"){
		cin >> t;
		while (t--){
			solve1();
		}
	}
	else{
		cin >> t;
		while (t--){
			solve2();
		}
	}
	return 0;
}

P12275 蓝桥杯 2024 国 Python B 工厂

原题 · 标签:#DP

考点

拓扑序

题意

  • 有 \(n\) 种物品,第 \(i\) 种售价为 \(a_i\)(\(a_i=0\) 表示不可售出)。
  • \(m\) 种生产方式,每种给出 \(x_i, y_i, k_i, w_i\):
    • 若 \(k_i=0\):一个工人一天可直接生产 \(w_i\) 件物品 \(y_i\)(忽略 \(x_i\))。
    • 若 \(k_i>0\):一个工人一天可用 \(k_i\) 件物品 \(x_i\) 生产 \(w_i\) 件物品 \(y_i\)(\(x_i \le y_i\))。
  • 每个工人独立生产,求平均每个工人一天能获得的最大收益(四舍五入保留两位小数)。

数据范围:\(1 \le n, m \le 3 \times 10^5\),\(0 \le a_i \le 10^6\),\(1 \le w_i \le 10\),\(0 \le k_i \le 10\),\(1 \le x_i \le y_i \le n\)。保证至少存在一个 \(k_i=0\)。

思路

很好想的,最终只可能卖单个品类产品。否则将其余的劳动力去做性价比最高的品类会更赚。
考虑 \(y\) 品类依赖 \(x\),这一定是一个 DAG。同时,其所有的依赖都是小指向大,连拓扑排序都不用写,只按照 \(y\) 排序就可以了。
设 \(dp_i\) 表示单个工人最多能生产多少 \(i\) 产品,最终答案是 \(dp_i \times a_i\) 的最大值。
当 \(k\) 为 \(0\) 时,\(dp_y\) 是 \(w\)。
否则,计算 \(dp_i\) 公式是 \(w\) 除以生产 \(w\) 件物品需要的劳动力数量。生产其原料需要 \(k\) 除以 \(dp_x\) 个工人,将原料加工需要 \(1\) 个人,相加即是需要的劳动力数量。

代码

#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 3e5+10;
struct st{
	int x,y,k,w;
	bool operator < (st a){
		return y < a.y;
	}
}s[N];
int a[N];
int n,m;
double dp[N];
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> n >> m;
	f(i,1,n){
		cin >> a[i];
	}
	f(i,1,m){
		cin >> s[i].x >> s[i].y >> s[i].k >> s[i].w;
	}
	sort(s+1,s+1+m);
	f(i,1,m){
		if (s[i].k == 0){
			dp[s[i].y] = max(dp[s[i].y],s[i].w * 1.0);
		}
		else{
			if (dp[s[i].x] == 0) continue;
			dp[s[i].y] = max(dp[s[i].y],(double) s[i].w / ((double)s[i].k / dp[s[i].x] + 1.0));
		}
	}
	double ans = 0;
	f(i,1,n){
		ans = max(ans,dp[i] * a[i]);
	}
	printf("%.2lf",ans);
	return 0;
}

ABC416E Development

原题 · 标签:#图论

考点

floyd

题意

  • 有 \(N\) 个城市,\(M\) 条无向道路(带通行时间),\(K\) 个机场。有机场的城市之间可以互相到达,通行时间为 \(T\)。
  • \(Q\) 次操作,支持三种:
    1. 1 x y t:在城市 \(x\) 和 \(y\) 之间新建一条双向道路,通行时间为 \(t\)。
    2. 2 x:在城市 \(x\) 新建一个机场。
    3. 3:求 \(\sum_{x=1}^N \sum_{y=1}^N f(x,y)\),其中 \(f(x,y)\) 为 \(x\) 到 \(y\) 的最短时间(不可达则为 \(0\))。

数据范围:\(1 \le N \le 500\),\(0 \le M \le 10^5\),\(0 \le K \le N\),\(1 \le Q \le 1000\),\(1 \le C_i, T, t \le 10^9\)。

思路

添加虚拟节点,来实现 \(2\) 操作的 \(O(n^2)\) 处理。

代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

#define pii pair<int,int>

#define f(i,a,b) for(int i=(a);i<=(b);i++)

#define Dl(a) cout << #a << " : " << a << "\n";

#define D(a) cout << #a << " : " << a;

#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}

const int N = 5e2+10;

int n,m,k,T,q;

int dis[N][N];

bool air[N];

signed main(){

    //freopen(".in","r",stdin);

    //freopen(".out","w",stdout);

    cin >> n >> m;

    memset(dis,0x3f,sizeof dis);

    f(i,0,n){

        dis[i][i] = 0;

    }

    f(i,1,m){

        int a,b,c; cin >> a >> b >> c;c *= 2;

        dis[a][b] = min(dis[a][b],c);

        dis[b][a] = min(dis[b][a],c);

    }

    for (int k=1;k<=n;k++){

        for (int i=1;i<=n;i++){

            for (int j=1;j<=n;j++){

                dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);

            }

        }

    }

    cin >> k >> T;

    f(idx,1,k){

        int x; cin >> x;

        dis[0][x] = min(dis[0][x],T);

        dis[x][0] = min(dis[x][0],T);

        for (int i=0;i<=n;i++){

            for (int j=0;j<=n;j++){

                dis[i][j] = min({dis[i][j],dis[i][0] + T + dis[x][j],dis[i][x] + T + dis[j][0]});

            }

        }

    }

    cin >> q;

    while (q--){

        int op; cin >> op;

        if (op == 1){

            int x,y,t; cin >> x >> y >> t; t*=2;

            for (int i=0;i<=n;i++){

                for (int j=0;j<=n;j++){

                    dis[i][j] = min({dis[i][j],dis[i][x] + t + dis[y][j],dis[j][x] + t + dis[y][i]});

                }

            }

        }

        else if (op == 2){

            int x; cin >> x;

            dis[0][x] = min(dis[0][x],T);

            dis[x][0] = min(dis[x][0],T);

            for (int i=0;i<=n;i++){

                for (int j=0;j<=n;j++){

                    dis[i][j] = min({dis[i][j],dis[i][0] + T + dis[x][j],dis[i][x] + T + dis[j][0]});

                }

            }

        }

        else if (op == 3){

            int sum = 0;

            for (int i=1;i<=n;i++){

                for (int j=1;j<=n;j++){

                    if (dis[i][j] == 0x3f3f3f3f3f3f3f3f) continue;

//                  D(i);D(j);Dl(dis[i][j]);

                    sum += dis[i][j];

                }

            }

            cout << sum / 2<< "\n";

        }

    }

    return 0;

}

ABC414F Jump Traveling

原题 · 标签:#图论

考点

思维 bfs

题意

  • 给定一棵 \(N\) 个节点的树,初始在节点 \(1\)。每次操作可以从当前节点移动到距离恰好为 \(K\) 的节点(距离为简单路径上的边数)。
  • 对每个 \(k = 2, \ldots, N\),求从节点 \(1\) 到达节点 \(k\) 所需的最少操作次数,若不可达则输出 \(-1\)。
  • 有 \(T\) 个测试用例。

数据范围:\(1 \le T \le 10^5\),\(2 \le N \le 2 \times 10^5\),\(1 \le K \le 20\),\(\sum N \le 2 \times 10^5\)。

思路

bfs,每k次统计一次答案。同时要使用次大值。因为最大值有可能回不去节点,次大值就要被纳入统计。

代码

#include <bits/stdc++.h>

using namespace std;

//#define int long long

#define pii pair<int,int>

#define f(i,a,b) for(int i=(a);i<=(b);i++)

#define Dl(a) cout << #a << " : " << a << "\n";

#define D(a) cout << #a << " : " << a;

#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}

const int N = 2e5+10;

int t;

int n,k;

vector <int> g[N];

int dis[N][30],vis[N][30];

struct st{

    int u,idx,val,lst;

};

signed main(){

    //freopen(".in","r",stdin);

    //freopen(".out","w",stdout);

    cin >> t;

    while (t--){

        cin >> n >> k;

        f(i,1,n){

            g[i].clear();

            f(j,0,k){

                vis[i][j] = dis[i][j] = 1e9;

            }

        }

        f(i,1,n-1){

            int u,v; cin >> u >> v;

            g[u].push_back(v);g[v].push_back(u);

        }

        queue <st> q;

        dis[1][0] = 0;

        q.push({1,0,0,0});

        while (!q.empty()){

            int u = q.front().u,idx = q.front().idx,val = q.front().val,lst = q.front().lst;

            q.pop();

            for (int v:g[u]){

                if (v != lst){

                    st to = {v,(idx + 1) % k,val,u};

                    if (!idx) to.val ++;

                    if (!to.idx) to.lst = 0;

                    if (to.val < dis[v][to.idx]){

                        q.push(to);

                        dis[v][to.idx] = to.val;

                        vis[v][to.idx] = to.lst;

                    }

                    else if (vis[v][to.idx] && vis[v][to.idx] != to.lst){

                        q.push(to);

                        vis[v][to.idx] = 0;

                    }

                }

            }

        }

        f(i,2,n){

            if (dis[i][0] < 1e9) cout << dis[i][0] << " ";

            else cout << "-1 ";

        }

        cout << "\n";

    }

    return 0;

}