题解洛谷P5100(c++解法)[JOI 2017 Final]足球

此题带球传球控球的状态转换较为复杂,我们可以考虑 拆点 。
首先我们发现最优解一定不会出现一个人重复控球(这个……很容易想到吧)
分析每一个坐标可能出现的状态,分为控球和不控球两种情况
而不控球又分为停留在该坐标和滚动经过该坐标两种情况
而滚动又分为上下左右滚
口胡完毕,总而言之就是一个坐标要拆成六个点(据说可以只拆五个点,但是六个点可能更加便于理解?)
经过简单的分析,我们可以想到用p点的坐标 + k * 场地的点数来表示拆点后的图中某一个点(k为该点的状态序号),可以用x * (w + 1) + y表示一个点的位置(x、y分别表示该点的上下坐标和左右坐标,必须要注意一下坐标可以为0,所以w要加1)
定义k值为0、1、2、3时表示球正在滚动状态(四个方向),k为4时表示球在该位置停止,k为5时表示球在该位置被控制,连边代价即为状态转换的代价。
从滚动到停止不需要花费疲劳度,从控球到将球踢出需要花费b点疲劳度(因为传球的计算公式为A * p + B,我们不妨将B在球传出时就处理了),而从静止到控球则需要离球最近的球员跑过来捡球。可以预处理对于每个位置曼哈顿距离最近的球员到该点的曼哈顿距离,用dt[i][j]存储。
直接上代码:

#include 
#include 
#include 
#include 
#include 
#define INF 0x3f3f3f3f
#define MAX 0x7f7f7f7f7f7f7f7f
using namespace std;
typedef long long ll;
const int W = 505;
const int N = 100005;

struct Node{ int x; ll y; };
struct player{ int x, y; }P[N];
struct Edge{ int to, next; ll weight; }edge[W * W * 36];
struct cmp{ bool operator()(Node x, Node y){ return x.y > y.y; } };

ll dis[W * W * 6], ans = MAX;
bool vis[W][W], vis2[W * W * 6];
int h, w, a, b, c, n, sum, cnt = 1;
int dx[4] = { 1, -1, 0, 0 };
int dy[4] = { 0, 0, 1, -1 };
int head[W * W * 6], dt[W][W];

template<typename T> inline void read(T &x)
{
    x = 0; char ch = getchar(); bool flag = false;
    while(ch < '0' || ch > '9'){if(ch == '-') flag = true; ch = getchar();}
    while('0' <= ch && ch <= '9'){x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    if(flag) x = -x;
}

inline void add(int u, int v, ll w)
{
    edge[cnt].to = v, edge[cnt].weight = w;
    edge[cnt].next = head[u], head[u] = cnt++;
}

inline ll Min(ll x, ll y){ return x < y ? x : y; }
inline int id(int x, int y){ return x * (w + 1) + y; } //用一个数字表示一个平面坐标 

inline void Manhattan_Distance()
{
    queue<player> q;
    for(register int i = 0;i <= h;i++) for(register int j = 0;j <= w;j++) dt[i][j] = INF;
    for(register int i = 1; i <= n; i++){ dt[P[i].x][P[i].y] = 0; q.push(P[i]); }
    while(!q.empty())
    {
        int x = q.front().x, y = q.front().y;
        q.pop(); vis[x][y] = false;
        for(register int i = 0; i < 4; i++)
        {
            int xx = x + dx[i], yy = y + dy[i];
            if(xx >= 0 && xx <= h && yy >= 0 && yy <= w && dt[xx][yy] > dt[x][y] + 1)
            {
                dt[xx][yy] = dt[x][y] + 1;
                if(!vis[xx][yy])
                {
                    player graph;
                    graph.x = xx, graph.y = yy;
                    q.push(graph);  vis[xx][yy] = true;
                }
            }
        }
    }
}

inline void Dijkstra()
{
    priority_queue<Node, vector<Node>, cmp> q;
    for(register int i = 0;i <= W * W * 6;i++) dis[i] = MAX;
    Node graph; graph.x = id(P[1].x, P[1].y) + 4 * sum, graph.y = 0;
    q.push(graph); dis[id(P[1].x, P[1].y) + 4 * sum] = 0;
    while(!q.empty())
    {
        int x = q.top().x;
        q.pop(); if(!vis2[x])
        {
            vis2[x] = true;
            for(register int i = head[x];i != 0;i = edge[i].next)
            {
                int v = edge[i].to;
                if (dis[v] > dis[x] + edge[i].weight)
                {
                    dis[v] = dis[x] + edge[i].weight; Node graph;
                    graph.x = v, graph.y = dis[v];
                    q.push(graph);
                }
            }
        }
    }
}

int main()
{
    read(h), read(w), read(a), read(b), read(c), read(n);
    for(register int i = 1; i <= n; i++) read(P[i].x), read(P[i].y);
    Manhattan_Distance();
    sum = (w + 1) * (h + 1); //因为横纵坐标可以为0,所以要+1
	//通过k * sum的表示方法拆点 
    for(register int i = 0; i <= h; i++)
    {
        for(register int j = 0; j <= w; j++)
        {
            int p = id(i, j);
            for(register int k = 0; k < 4; k++)
            {
                add(p + k * sum, p + 4 * sum, 0); //球从滚动状态(无人控球)到停止 
                add(p + 5 * sum, p + k * sum, b); //球从静止状态(有人控球)到将球传出 
                add(p + 4 * sum, p + 5 * sum, c * (ll)dt[i][j]); //离球最近的球员(曼哈顿距离)跑过来控球 
				int xx = i + dx[k], yy = j + dy[k];
                if(xx >= 0 && yy >= 0 && xx <= h && yy <= w)
                {
                    int q = id(xx, yy);
                    add(p + k * sum, q + k * sum, a); //球从一个位置按照其方向滚到相邻的位置(传球) 
                    add(p + 5 * sum, q + 5 * sum, c); //球从一个位置按照其方向滚到相邻的位置(带球) 
                }
            }
        }
    }
    Dijkstra();
    for(register int i = 0; i < 6; i++)
    {
        ans = Min(ans, dis[id(P[n].x, P[n].y) + i * sum]);
    }
    printf("%lld\n", ans);
    return 0;
}

你可能感兴趣的:(题解洛谷P5100(c++解法)[JOI 2017 Final]足球)