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 x y t:在城市 \(x\) 和 \(y\) 之间新建一条双向道路,通行时间为 \(t\)。2 x:在城市 \(x\) 新建一个机场。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;
}
浙公网安备 33010602011771号