CQU Weekly Training Contest 5 - Graph Theory & Dp

本周讲了图论中最短路以及最小生成树MST的基本算法~ 也介绍了图论中一类的特殊的问题“差分约束”~  可以通过Bellman-Ford求解~ 


本周练习题题解如下:


Pro A

题意:求次短路 

具体思路: 有点dp的意味 再开一个数组在更新最短路的时候 也同时更新次短路

也要保证次短路一定比最短路 具体细节见code

参考code:

//
//  POJ 3255 Roadblocks
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, r, dp[5005], dp2[5005];
struct Edge {
	int to, cost;
};
vector<Edge> G[N];
typedef pair<int, int> Sta; //距离, 节点标号

int dijkstra() {
	priority_queue<Sta, vector<Sta>, greater<Sta> > pq;
	memset(dp, 0x3f, sizeof dp); memset(dp2, 0x3f, sizeof dp2);
	dp[1] = 0; pq.push(Sta(0, 1));
	while(!pq.empty()) {
		Sta p = pq.top(); pq.pop();
		int u = p.second, d = p.first;
		if(d > dp2[u]) continue;
		for(int i = 0; i < G[u].size(); ++i) {
			Edge &e = G[u][i];
			int d2 = d + e.cost;
			if(dp[e.to] > d2) {
				swap(dp[e.to], d2);
				pq.push(Sta(dp[e.to], e.to));
			}
			if(dp2[e.to] > d2 && dp[e.to] < d2) {
				dp2[e.to] = d2;
				pq.push(Sta(dp2[e.to], e.to));
			}
		}
	}
	return dp2[n];
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	//ios_base::sync_with_stdio(0);

	cin >> n >> r;
	for(int i = 1; i <= r; ++i) {
		int x, y, v; cin >> x >> y >> v;
		G[x].push_back((Edge) {y, v});
		G[y].push_back((Edge) {x, v});
	}
	cout << dijkstra() << endl;
	return 0;
}


Pro B

题意:求牛1-N 其实最最大距离就是1-N的满足条件最短路
具体思路
由最短路性质可知 对于所有的边(u, v)  均有d[u] + cost[u][v] >= d[v]
那么由题目已知  必须顺序排序 但是可以多牛共存一点
1. 即 d[i+1] >= d[i] → d[i+1] + 0 >= d[i] (从i+1连一条权为0的边到i)
2. 喜欢的牛 B - A <= D → A + D >= B (从A连一条权为D的边到B)
3. 讨厌的牛 B - A >= D → B - D >= A (从B连一条权为-D的边到A)
由于存在负权 我们可以用Bellman-Ford来算最短路 O(EV)的复杂度题目规模完全够

参考code

//
//  POJ 3169 Layout
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, l, d, dp[1005];
struct Edge {
	int to, cost;
};
vector<Edge> G[21005];

int BellmanFord() {
	memset(dp, 0x3f, sizeof dp); dp[1] = 0;
	for(int k = 1; k <= n; ++k) {
		for(int i = 1; i <= n; ++i) {
			for(int j = 0; j < G[i].size(); ++j) {
				Edge &e = G[i][j];
				dp[e.to] = min(dp[e.to], dp[i] + e.cost);
				//printf("dp[%d]: %d\n",e.to, dp[e.to]);
			}
		}
	}
	return dp[n];
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	scanf("%d%d%d", &n, &l, &d);
	int a, b, c;
	for(int i = 1; i < n; ++i)
		G[i + 1].push_back((Edge) {i, 0});
	for(int i = 1; i <= l; ++i) {
		scanf("%d%d%d", &a, &b, &c);
		G[a].push_back((Edge) {b, c});
	}
	for(int i = 1; i <= d; ++i) {
		scanf("%d%d%d", &a, &b, &c);
		G[b].push_back((Edge) {a, -c});
	}
	int ans = BellmanFord();
	if(dp[1] < 0) ans = -1;
	else if(ans == INF)  ans = -2;
	printf("%d\n", ans);
	return 0;
}

Pro C

题意:求某牛到其他牛的最小平均度

具体思路: 多源最短路径 那么Floyd 并且300个点 完全可行 预处理参演同一场电影的牛互相之间度为1

传递的度为2 仔细想想本题牛之间的度  只有度1 度2 和没度 三种情况

参考code

由于没有初始化dp[i][i] = 0 后面相加的时候跳过了i==j的情况 - - 但是你们别忘记了 不然你懂的

//
//  POJ 2139 Six Degrees of Cowvin Bacon
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, m, a[305], dp[305][305];

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	scanf("%d%d", &n, &m);
	memset(dp, 0x3f, sizeof dp);
	for(int i = 1; i <= m; ++i) {
		int t; scanf("%d", &t);
		for(int j = 1; j <= t; ++j) scanf("%d", a + j);
		for(int j = 1; j <= t; ++j)
			for(int k = j + 1; k <= t; ++k)
				dp[a[j]][a[k]] = dp[a[k]][a[j]] = 1;
	}

	for(int k = 1; k <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

	int ans = INF;
	for(int i = 1; i <= n; ++i) {
		int t = 0;
		for(int j = 1; j <= n; ++j) {
			if(i == j) continue;
			t += dp[i][j];
		}
		ans = min(ans, t);
	}
	printf("%d\n", ans * 100 / (n - 1));
	return 0;
}

Pro D

题意


具体思路: 这里给出一种 把边权 Kruskal求最大生成森林即可

参考code

//
//  POJ 3723  Conscription
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, m, r;
struct Edge {
	int u, v, cost;
	bool operator<(const Edge& rhs) const {
		return cost < rhs.cost;
	}
} G[50005];

int par[20005], rank[20005];

void init(int n) {
	for(int i = 0; i < n; ++i) {
		par[i] = i;
		rank[i] = 0;
	}
}

int find(int x) {
	if(par[x] == x) return x;
	return par[x] = find(par[x]);
}

void unite(int x, int y) {
	x = find(x); y = find(y);
	if(rank[x] < rank[y]) par[x] = y;
	else {
		par[y] = x;
		if(rank[x] == rank[y]) ++rank[x];
	}
}

bool same(int x, int y) {
	return find(x) == find(y);
}

int kruskal() {
	int ret = 0;
	sort(G + 1, G + 1 + r);
	init(n + m);
	for(int i = 1; i <= r; ++i) {
		Edge &e = G[i];
		if(!same(e.u, e.v)) {
			ret += e.cost;
			unite(e.u, e.v);
		}
	}
	return ret;
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	int t; scanf("%d", &t);
	while(t--) {
		scanf("%d%d%d", &n, &m, &r);
		for(int i = 1; i <= r; ++i) {
			int x, y, v; scanf("%d%d%d", &x, &y, &v);
			G[i] = (Edge) {x, y + n, -v};
		}
		int ans = 10000 * (n + m) + kruskal();
		printf("%d\n", ans);
	}
	return 0;
}

Pro E

题意:求最小生成树

具体思路: prim裸题没啥好说的 由于V^2条边 可以用邻接矩阵写

参考code

//
//  POJ 1258  Agri-Net
//
//  Created by TaoSama on 2015-03-21
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, a[105][105], minCost[105];
bool used[105];
typedef pair<int, int> Sta;

int prim() {
	int ret = 0;
	memset(minCost, 0x3f, sizeof minCost);
	memset(used, false, sizeof used);
	priority_queue<Sta, vector<Sta>, greater<Sta> > pq;
	pq.push(Sta(0, 1)); minCost[1] = 0;
	while(!pq.empty()) {
		Sta p = pq.top(); pq.pop();
		int u = p.second;
		if(used[u] || p.first > minCost[u]) continue;
		used[u] = true; ret += minCost[u];
		for(int i = 1; i <= n; ++i) {
			if(minCost[i] > a[u][i]) {
				minCost[i] = a[u][i];
				pq.push(Sta(minCost[i], i));
			}
		}
	}
	return ret;
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	while(scanf("%d", &n) == 1) {
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				scanf("%d", &a[i][j]);

		printf("%d\n", prim());
	}
	return 0;
}

Pro F

题意:拆掉一些可能是环的图中的最小的一些边

具体思路:很多种写法啦~ 一是用所有权和减去最大生成森林~ - - 二是把那些不在生成树的边加起来

参考code

第一种写法

//
//  AOJ 2224 Save your cat
//
//  Created by TaoSama on 2015-03-21
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, m, par[10005], rank[10005];

struct Edge {
	int u, v;
	double cost;
	bool operator < (const Edge& rhs) const {
		return cost < rhs.cost;
	}
};
struct Point {
	int x, y;
} a[10005];
vector<Edge> G;

void init(int n) {
	for(int i = 1; i <= n; ++i) {
		par[i] = i;
		rank[i] = 0;
	}
}

int find(int x) {
	if(par[x] == x) return x;
	return par[x] = find(par[x]);
}

void unite(int x, int y) {
	x = find(x); y = find(y);
	if(x == y) return;
	if(rank[x] < rank[y]) {
		par[x] = y;
	} else {
		par[y] = x;
		if(rank[x] == rank[y]) ++rank[x];
	}
}

bool same(int x, int y) {
	return find(x) == find(y);
}

double kruskal() {
	double ret = 0;
	sort(G.begin(), G.end());
	init(n);
	for(int i = 0; i < G.size(); ++i) {
		Edge &e = G[i];
		if(!same(e.u, e.v)) {
			ret += e.cost;
			unite(e.u, e.v);
		}
	}
	return ret;
}

/* 我就草了!!! 再也不用prim了
double prim() {
	double ret = 0;
	priority_queue<Sta, vector<Sta>, greater<Sta> > pq;
	memset(used, false, sizeof used);
	minCost[1] = 0; pq.push(Sta(0, 1));
	while(!pq.empty()) {
		Sta p = pq.top(); pq.pop();
		int u = p.second;
		if(used[u] || p.first > minCost[u]) continue;
		used[u] = true; ret += minCost[u];
		for(int i = 0; i < G[u].size(); ++i) {
			Edge &e = G[u][i];
			if(minCost[e.to] > e.cost) {
				minCost[e.to] = e.cost;
				pq.push(Sta(minCost[e.to], e.to));
			}
		}
	}
	return ret;
} */

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	while(scanf("%d%d", &n, &m) == 2) {
		double sum = 0; G.clear();
		for(int i = 1; i <= n; ++i)
			scanf("%d%d", &a[i].x, &a[i].y);
		for(int i = 1; i <= m; ++i) {
			int x, y; scanf("%d%d", &x, &y);
			double cost = sqrt((a[x].x - a[y].x) * (a[x].x - a[y].x) +
			                   (a[x].y - a[y].y) * (a[x].y - a[y].y));
			sum += cost;
			G.push_back((Edge) {x, y, -cost});
		}
		double ans = sum + kruskal();
		printf("%.3f\n", ans);
	}
	return 0;
}

Pro G

题意:求来回最短路

具体思路:经典的最短路题目 由于所有的路都是单向的 所以来回的路必然不同(题目在迷惑人Orz)

不管是去还是回 一个终点唯一  一个起点唯一 求两次最短路就好了

对于出发 等于从终点出发到其他点的最短路 这时将边取反

对于回去 等于从终点出发到其他点的最短路 这时边不变

参考code

//
//  POJ 3268 Silver Cow Party
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, m, s, dp[1005], dp2[1005];
struct Edge {
	int to, cost;
};
vector<Edge> G[N], Gr[N];
typedef pair<int, int> Sta;

void dijkstra(vector<Edge> *G, int *dp) {
	priority_queue<Sta, vector<Sta>, greater<Sta> > pq;
	pq.push(Sta(0, s)); dp[s] = 0;
	while(!pq.empty()) {
		Sta p = pq.top(); pq.pop();
		int u = p.second, d = p.first;
		if(d > dp[u]) continue;
		for(int i = 0; i < G[u].size(); ++i) {
			Edge &e = G[u][i];
			if(dp[e.to] > d + e.cost) {
				dp[e.to] = d + e.cost;
				//printf("dp[%d]: %d\n", e.to, dp[e.to]);
				pq.push(Sta(dp[e.to], e.to));
			}
		}
	}
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	scanf("%d%d%d", &n, &m, &s);
	for(int i = 1; i <= m; ++i) {
		int x, y, v; scanf("%d%d%d", &x, &y, &v);
		G[x].push_back((Edge) {y, v});
		Gr[y].push_back((Edge) {x, v});
	}
	memset(dp, 0x3f,sizeof dp);
	memset(dp2, 0x3f,sizeof dp2);
	dijkstra(G, dp); dijkstra(Gr, dp2);
	int ans = -INF;
	for(int i = 1; i <= n; ++i)
		ans = max(ans, dp[i] + dp2[i]);
	printf("%d\n", ans);
	return 0;
}

Pro H

题意

有N个房间,每到达一个房间会获得或者减少体力值,
初始位于1号房间,拥有100点体力值,房间可以重复通过来获取体力值。到达某个房间的体力值必须大于0才能获取当前房间的体力值

具体思路

正圈且此点连通必然可以到达终点  如果没有正环但是到达终点 体力大于0  也可以

这里把边权取反 求负圈 100个点并用Floyd判断连通性

参考code

//
//
//
//  Created by TaoSama on 2015-03-29
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, dp[105];
bool con[105][105];
struct Edge {
    int u, v, cost;
};
vector<Edge> G;

bool BellmanFord() {
    memset(dp, 0x3f, sizeof dp); dp[1] = -100;
    for(int i = 1; i <= n; ++i) {
        for(int j = 0; j < G.size(); ++j) {
            Edge &e = G[j];
            if(dp[e.v] > dp[e.u] + e.cost && dp[e.u] + e.cost < 0) {
                dp[e.v] = dp[e.u] + e.cost;
                if(i == n && con[1][e.v] && con[e.v][n])
                    return true;
            }
        }
    }
    return false;
}

int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
    ios_base::sync_with_stdio(0);

    while(cin >> n && n != -1) {
        G.clear();
        memset(con, false, sizeof con);
        for(int i = 1; i <= n; ++i) con[i][i] = true;
        for(int i = 1; i <= n; ++i) {
            int c, t, x; cin >> c >> t;
            while(t--) {
                cin >> x;
                G.push_back((Edge) {i, x, -c});
                con[i][x] = true;
            }
        }
        for(int k = 1; k <= n; ++k)
            for(int i = 1; i <= n; ++i)
                for(int j = 1; j <= n; ++j)
                    con[i][j] |= con[i][k] && con[k][j];

        if(BellmanFord() || dp[n] < 0) cout << "winnable" << endl;
        else cout << "hopeless" << endl;
    }
    return 0;
}

Pro I

题意

快递到了:你是某个岛国(ACM-ICPC Japan)上的一个苦逼程序员,你有一个当邮递员的好基友利腾桑遇到麻烦了:全岛有一些镇子通过水路和旱路相连,走水路必须要用船,在X处下船了船就停在X处。而且岛上只有一条船,下次想走水路还是得回到X处才行;两个镇子之间可能有两条以上的水路或旱路;邮递员必须按照清单上的镇子顺序送快递(镇子可能重复,并且对于重复的镇子不允许一次性处理,比如ABCB的话B一定要按顺序走两次才行)。

测试数据有多组:

N M

x1 y1 t1 sl1

x2 y2 t2 sl2

xM yM tM slM

R

z1 z2 … zR

N (2 ≤ N ≤ 200) 是镇子的数量,M (1 ≤ M ≤ 10000) 是旱路和水路合计的数量。从第2行到第M + 1行是路径的描述,路径连接xi  yi两地,路径花费 ti (1 ≤ ti ≤ 1000)时间,sli 为L时表示是旱路,S时表示是水路。可能有两条及以上路径连接两个镇子,并且路径都是双向的。

M + 2行的R是利腾需要去的镇子的数量,M + 3是利腾需要去的镇子的编号。

初始状态利腾和船都在第一个镇子,且肯定有方法达到需要去的镇子。

测试数据为0 0的时候表示终止。

Sample Input

3 3
1 2 5 L
1 2 7 S
2 3 11 S
3
1 2 3
5 5
1 2 15 L
2 3 10 L
4 5 7 L
1 3 30 S
3 4 100 S
5
1 3 5 4 1
0 0

Output for the Sample Input

18
269

具体思路

Floyd 预处理 水路和陆路的两点最短距离
这题我没想出来 - - 我dp太弱
具体思路请看注释

参考code

//
//  AOJ 2200 Mr. Rito Post Office
//
//  Created by TaoSama on 2015-03-20
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int sea[205][205], land[205][205], dp[1005][205];
int n, m, r, a[1005];

int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
    ios_base::sync_with_stdio(0);

    while(cin >> n >> m && (n + m)) {
        for(int i = 1; i <= n; ++i)
            for(int j = 1; j <= n; ++j)
                sea[i][j] = land[i][j] = i == j ? 0 : INF;

        for(int i = 1; i <= m; ++i) {
            int x, y, t; char c;
            cin >> x >> y >> t >> c;
            if(c == 'S') sea[x][y] = sea[y][x] = min(sea[x][y], t);
            else land[x][y] = land[y][x] = min(land[x][y], t);
        }

        cin >> r;
        for(int i = 1; i <= r; ++i)  cin >> a[i];

        //Floyd
        for(int k = 1; k <= n; ++k) {
            for(int i = 1; i <= n; ++i) {
                for(int j = 1; j <= n; ++j) {
                    sea[i][j] = min(sea[i][j], sea[i][k] + sea[k][j]);
                    land[i][j] = min(land[i][j], land[i][k] + land[k][j]);
                }
            }
        }

        //dp[i][j]:= 到达i镇子 船停在j镇子的最小时间
        memset(dp, 0x3f, sizeof dp);
        dp[1][a[1]] = 0;
        for(int i = 1; i <= r; ++i) {
            for(int j = 1; j <= n; ++j) {
                //一定可以走陆路
                dp[i][j] = min(dp[i][j], dp[i - 1][j] + land[a[i - 1]][a[i]]);
                for(int k = 1; k <= n; ++k) //枚举水路船停的位置
                    //从i-1镇子回到船在的j  开船到k镇子船丢在那里  陆路从k到i镇子
                    //三个INF 可能加爆。。。。。
                    dp[i][k] = min((long long)dp[i][k], (long long)dp[i - 1][j] +
                                   land[a[i - 1]][j] + sea[j][k] + land[k][a[i]]);
            }
        }
        cout << *min_element(dp[r], dp[r] + n + 1) << endl;
    }
    return 0;
}

Pro J

题意

买东西 你有某些种类硬币 每个有有限个 售货员有无限个 你给钱 售货员找钱

求 过程中最少的交换硬币的总和


具体思路

多重背包 + 完全背包



参考code

//
//
//
//  Created by TaoSama on 2015-03-30
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;

int n, t, dpp[25000], dpn[15000], c[105], v[105];

int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
    ios_base::sync_with_stdio(0);

    cin >> n >> t;
    for(int i = 1; i <= n; ++i) cin >> v[i];
    for(int i = 1; i <= n; ++i) cin >> c[i];

    memset(dpp, 0x3f, sizeof dpp);
    dpp[0] = 0;
    for(int i = 1; i <= n; ++i) {
        int k = 1;
        while(c[i] > 0) {
            int t = min(c[i], k);
            for(int j = t + 120 * 120; j >= t * v[i]; --j)
                dpp[j] = min(dpp[j], dpp[j - t * v[i]] + t);
            c[i] -= k; k <<= 1;
        }
    }
    /*for(int i = 0; i <= t + 120 * 120; ++i)
        if(dpp[i] != INF) printf("dpp[%d]: %d\n", i, dpp[i]);*/

    memset(dpn, 0x3f, sizeof dpn);
    dpn[0] = 0;
    for(int i = 1; i <= n; ++i)
        for(int j = v[i]; j <= 120 * 120; ++j)
            dpn[j] = min(dpn[j], dpn[j - v[i]] + 1);
    /*for(int i = 0; i <= 120 * 120; ++i)
        if(dpn[i] != INF) printf("dpn[%d]: %d\n", i, dpn[i]);*/

    int ans = INF;
    for(int i = 0; i <= 120 * 120; ++i)
        ans = min(ans, dpp[t + i] + dpn[i]);

    if(ans == INF) ans = -1;
    cout << ans << endl;
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值