題目大意:從前有n座山,山裡都有一座廟,廟裡都有一個老和尚,老和尚專送紀念品,每個紀念品重量為cost[i],價值為val[i]。n座山形成一張有m條邊的有向圖,某山道某某山都有距離dist[i]。主角xx從st點出發,背著個容量為M的背包,想要收集最多的價值。但是主角體弱多病要顧及身體,每次背著重量為wi從某山走到某某山就要耗費dist[i]*wi的能量。最後能價值最多時最少的能量耗費為多少?
解題思路:看上去像神題,但仔細分析就是一個拓撲排序+完全背包,不過細節著實有點蛋痛。我最開始是想先再每個點做一次完全背包,這樣轉移的時候直接轉移就好了。但是這樣似乎很難實現。
設dp[i][j]到達i點背包裡裝容量為j的最大價值,power[i][j]表示價值最大時的最小耗費。按上一段說的一開始就轉移的話,那麼dp[i][j]都會被更新,此時power[i][j]應該是0,因為不知道前面跑了幾萬幾千裡。但是這樣並不靠譜,先不說從st能不能到i,就說能到達的時候,我們怎麼得到一個和dp[i][j]一樣的值,那麼此時power應該更新為多少?還有dp[i][j]要怎麼更新?
上面是我的一次失敗的嘗試,但對後面的分析也有幫助,我只要先進行拓撲排序,然後利用拓撲序向後轉移,轉移到下一個點就做一次完全背包,一個點可能從很多歌點轉移來,優先更新dp[i][j]然後更新power[i][j],轉移得到的dp[i][j]和power[i][j]都是從前序節點轉移而來,如果.而每次轉移的時候還必須對下一個節點進行標記,表示能否從st點而來。
具體的轉移方程和完全背包很像,只是在價值一樣的時候要依據power進行轉移,實現見代碼。
測試數據:
4 4 10 1
1 1
2 3
3 4
4 5
1 2 5
1 3 4
2 4 4
3 4 5
4 4 10 1
3 5
2 2
3 3
1 1
1 2 5
1 3 10
2 4 4
3 4 5
4 4 15 1
4 7
2 3
3 3
1 1
1 2 5
1 3 10
2 4 0
3 4 5
4 4 0 1
4 7
2 3
3 3
1 1
1 2 5
1 3 10
2 4 0
3 4 5
4 4 15 4
4 7
2 3
3 3
1 1
1 2 5
1 3 10
2 4 0
3 4 5
4 4 15 2
4 7
2 3
3 3
1 1
1 2 5
1 3 10
2 4 0
3 4 5
4 3 15 1
2 3
3 3
2 1
1 1
1 3 0
2 3 5
3 4 0
4 3 15 1
2 3
3 3
2 1
1 1
1 3 0
2 3 5
3 4 10
Output:
15 0
16 81
25 60
0 0
15 0
22 0
22 0
22 140
C艹代碼:
[cpp]
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
#define MIN 700
#define MAX 2100
#define int64 long long
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
struct node {
int v,len;
}cur;
vector<node> mmap[MIN];
int n,m,road,x,cost[MIN],val[MIN],flag[MIN];
int st[MIN],top,cnt[MIN],real[MIN],Index;
int64 dp[MIN][MAX],power[MIN][MAX],ans,dist;
void Initial() {
Index = 0;
top = ans = dist = 0;
memset(dp,0,sizeof(dp));
memset(cnt,0,sizeof(cnt));
memset(flag,0,sizeof(flag));
memset(power,-1,sizeof(power));
for (int i = 0; i <= n; ++i)
mmap[i].clear();
}
void Debug_InPut() {
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= m; ++j)
printf("(%lld %lld)%c",dp[i][j],power[i][j],j==m?'\n':' ');
}
void ToooPooo() {
for (int i = 1; i <= n; ++i)
if (cnt[i] == 0) st[++top] = i;
while (top != 0) {
int v = st[top--];
real[++Index] = v;
//printf("%d\n",v);
int size = mmap[v].size();
for (int i = 0; i < size; ++i) {
cur = mmap[v][i];
cnt[cur.v]--;
if (cnt[cur.v] == 0)
st[++top] = cur.v;
}
}
}
void Solve_AC() {
int i,j,k,s,v,size,tp;
for (j = 0; j <= m; ++j) {
//相當於初始化
power[x][j] = 0;
if (j >= cost[x])
dp[x][j] = max(dp[x][j],dp[x][j-cost[x]]+val[x]);
if (dp[x][j] > ans)
ans = dp[x][j],dist = 0;
//printf("%d %lld ans%lld\n",j,dp[x][j],ans);
}
flag[x] = 1;
for (i = 1; i <= n; ++i) {
v = real[i];
if (flag[v] == 0) continue; //flag為0 ,表示不可達
size = mmap[v].size();
for (s = 0; s < size; ++s) {
cur = mmap[v][s];
tp = cur.v,flag[tp] = 1; //可達,tp為下一個節點號
for (j = 0; j <= m; ++j) {
if (dp[tp][j] < dp[v][j]) { //優先根據dp[tp][j]進行轉移
dp[tp][j] = dp[v][j];
power[tp][j] = power[v][j] + cur.len * j;
}
else if (dp[tp][j] == dp[v][j]) {//當dp[tp][j]和dp[v][j]相等才根據power[i][j]轉移
if (power[tp][j] == -1) //第一次到達tp點
power[tp][j] = power[v][j];
else
power[tp][j] = min(power[tp][j],power[v][j] + cur.len * j);
}
//沒有這個if就會出現後面的耗費比前面多,但實際獲得的價值都一樣
if (j && dp[tp][j] == dp[tp][j-1])
power[tp][j] = min(power[tp][j],power[tp][j-1]);
}
for (j = cost[tp]; j <= m; ++j) {
//完全背包
if (dp[tp][j] < dp[tp][j-cost[tp]]+val[tp]) {
dp[tp][j] = dp[tp][j-cost[tp]] + val[tp];
power[tp][j] = power[tp][j-cost[tp]];
}
else if(dp[tp][j] == dp[tp][j-cost[tp]]+val[tp])
power[tp][j] = min(power[tp][j],power[tp][j-cost[tp]]);
}
for (j = 0; j <= m; ++j) {
//更新答案
if (dp[tp][j] > ans)
ans = dp[tp][j],dist = power[tp][j];
else if (dp[tp][j] == ans)
dist = min(dist,power[tp][j]);
}
//printf("cur %d:\n",cur.v),Debug_InPut();
}
}
}
int main()
{
int i,j,k,a,b,c;
while (~scanf("%d%d%d%d",&n,&road,&m,&x)) {
Initial(); www.2cto.com
for (i = 1; i <= n; ++i)
scanf("%d%d",&cost[i],&val[i]);
for (i = 1; i <= road; ++i) {
scanf("%d%d%d",&a,&b,&c);
cnt[b]++;
cur.v = b,cur.len = c;
mmap[a].push_back(cur);
}
ToooPooo();
Solve_AC();
//printf("ans %lld %lld\n",ans,dist);
printf("%lld\n",dist);
}
}