比赛时1个小时也没有写出来,在这题上卡了大跟头……
算法三进制DP,用SPFA转移。我的做法是把初始状态(1个扔到队列里)然后转移。注意转移的时候要枚举子集。
代码如下:
typedef long long LL;
const int N = 50;
const int INF = 100000000;
const int K = 8;
int dist[N][N];
int dp[N][1 << K][1 << K], inq[N][1 << K][1 << K];
int pos[N], tick[N], tms[K], ft[K];
bool checkMin(int& now, int val)
{
if(now > val)
{
now = val;
return true;
}
return false;
}
int SPFA(int n, int s, int k)
{
REP(i, 0, n)
{
REP(j, 0, (1 << k))
{
REP(p, 0, (1 << k))
{
dp[i][j][p] = INF;
}
}
}
memset(inq, 0, sizeof(inq));
queue<pair<int, pair<int, int> > > q;
int reach = pos[s];
int hash = tick[s];
dp[s][0][hash] = 0;
inq[s][0][hash] = 1;
q.push(make_pair(s, make_pair(0, hash)));
for(int state = reach; state > 0; state = ((state - 1) & reach))
{
int plus = 0;
for(int j = 0; j < k; j++)
{
if((state & (1 << j)))
plus += (hash & (1 << j)) ? ft[j] : tms[j];
}
int TT = hash | state;
if(checkMin(dp[s][state][TT], plus) && !inq[s][state][TT])
{
inq[s][state][TT] = 1;
q.push(make_pair(s, make_pair(state, TT)));
}
}
while(!q.empty())
{
int now = q.front().first;
reach = q.front().second.first;
hash = q.front().second.second;
q.pop();
for(int i = 0; i < n; i++)
{
if(dist[now][i] == INF) continue;
int nxtS = reach | pos[i];
int nxtT = hash | tick[i];
if(checkMin(dp[i][reach][nxtT], dp[now][reach][hash] + dist[now][i]) && !inq[i][reach][nxtT])
{
q.push(make_pair(i, make_pair(reach, nxtT)));
inq[i][reach][nxtT] = 1;
}
for(int state = nxtS; state > reach; state = ((state - 1) & nxtS))
{
int plus = 0;
if((state & reach) != reach) continue;
for(int j = 0; j < k; j++)
{
if((state & (1 << j)) && (reach & (1 << j)) == 0)
plus += (nxtT & (1 << j)) ? ft[j] : tms[j];
}
int TT = nxtT | state;
if(checkMin(dp[i][state][TT], dp[now][reach][hash] + plus + dist[now][i]) && !inq[i][state][TT])
{
q.push(make_pair(i, make_pair(state, TT)));
inq[i][state][TT] = 1;
}
}
}
inq[now][reach][hash] = 0;
}
return dp[0][(1 << k) - 1][(1 << k) - 1];
}
int main()
{
int t, n, m, kk;
scanf("%d", &t);
REP(cas, 0, t)
{
scanf("%d %d %d", &n, &m, &kk);
CC(pos, 0);
CC(tick, 0);
REP(i, 0, n) REP(j, 0, n) dist[i][j] = INF;
REP(i, 0, m)
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
a--, b--;
dist[a][b] = dist[b][a] = min(dist[b][a], c);
}
REP(i, 0, kk)
{
int p, ni, fi;
scanf("%d %d %d %d", &p, &tms[i], &ft[i], &ni);
p--;
pos[p] |= (1 << i);
REP(j, 0, ni)
{
scanf("%d", &fi);
fi--;
tick[fi] |= (1 << i);
}
}
REP(k, 0, n)
{
REP(i, 0, n) REP(j, 0, n)
dist[i][j] = min(dist[i][k] + dist[k][j], dist[i][j]);
}
printf("Case #%d: %d\n", cas + 1, SPFA(n, 0, kk));
}
return 0;
}
看了一下GXX的代码,通过枚举二进制状态再转移的方法更加方便,代码更简洁,而且速度也比我的多。
链接:http://acmicpc.info/archives/543
先枚举了
for (int attrMask = 0; attrMask < (1 << k); ++ attrMask) {
for (int passMask = 0; passMask < (1 << k); ++ passMask) {
再做最短路
算是一种以前没有学过的方法吧