1. 程式人生 > >題解:[ZJOI2014]璀燦光華

題解:[ZJOI2014]璀燦光華

找到 距離 坐標 回溯 turn 編號 最短路 ges 方法

原題鏈接

題目描述

金先生有一個女朋友沒名字。她勤勞勇敢、智慧善良。金先生很喜歡她。為此,金先生用\(a^3\)\(1 \times 1 \times 1\)的獨特的水晶制作了一個邊長為\(a\)的水晶立方體,他要將這個水晶立方體送給他見過最單純善良的她。

由於水晶立方體太太,不好運送,金先生還是將它拆開來送出。他相信拼好這個水晶立方難不倒聰明的她。

沒名字收到了禮物後果然不一會兒就根據說明將水晶立方體拼好了。沒名字發現,有 nnn 塊水晶在漆黑安靜的夜晚會隨機以等概率向上下左右前後六個方向的一個發出光。被光照到的水晶顯得格外好看。沒名字給每一塊不會發光的水晶定義了一個好看程度。水晶立方體在夜晚中的好看程度就是每塊被光照到的水晶的好看程度之和。沒名字想知道,水晶立方體在夜晚中的好看程度的最小值和最大值。

輸入格式

第一行是 \(a\),表示水晶立方體的邊長。
接下來 \(a^3\)?? 行,每行若幹整數。
第一個數\(g_i\)表示第\(i\)塊水晶的好看程度。如果 \(g_i=0\),代表這塊水晶會發光。接下來 \(3\sim 6\)個整數,代表與這塊水晶有共同面的水晶編號。

輸出格式

兩個整數,代表水晶立方體在夜晚好看程度的最小值與最大值。

樣例

樣例輸入

2
0 7 2 3
0 8 1 4
4 5 4 1
8 6 3 2
16 3 6 7
32 4 5 8
1 1 8 5
2 2 7 6

樣例輸出

0 12

數據範圍與提示

對於所有數據,\(1<a\le 70,?gi<1000000,?n\le 8\)

題解

首先,介紹一下c++ stl裏的神物——stringstream

這東西能像cin那樣讀入,但是是從字符串中讀入,所以我們就不用打快讀了(雖然慢了一點,但開氧氣之後海星)。

頭文件<sstream>

大概就是這樣讀:

for(int i = 1; i <= n; ++i)
{
    getline(cin, tmp);
    stringstream ss(tmp);
    ss >> dep[i];
    int aa;
    while(ss >> aa)
        add_edge(i, aa);
}

題目中的照射不僅只照到了與該水晶相鄰的水晶,它把整條射線上的水晶全照到了。

所以我們只能考慮建出這個立方體後重新建圖。

我的思路是這樣的:

先找到一個度為3的點作為一個角的點,用bfs求出其道個點的最短路。記以該點為原點的第\(i\)個點坐標為\((x_{1, i}, y_{1, i}, z_{1, i})\),最短路為\(dist[0][i]\),顯然有\(dist[0][i] = x_{1, i} - 1+ y_{1, i} - 1 + z_{1, i} - 1\)

我們任選一個平面,要求包含剛才那個點。我們找到在該平面上該點對角線上的點,其實就是隨便找一個度為3(在角上)且與原點距離\(2(n-1)\)(在該平面上最遠)的點。以該點為原點跑一遍最短路,記\(dist[1][i]\)

由於我們設計最短路時,可以先走到該點正下方,再往上爬,同樣,我們可以設計成先走\(y\),再走\(x\),最後走\(z\)

技術分享圖片

我們發現\(dist[0][i] +dist[1][i]=2(n-1)\),我們把它減去,就只剩下\(2z\)了,於是\(z\)就求出來了:

/**
 ddn是度數
 di是原點
 poi記錄點的x, y, z
 這裏的n已經是點的個數了
 tn才是邊長
 由於從1開始,所以有些地方微調了一下
 */
for(di[0] = 1; ddn[di[0]] > 3; ++di[0]);
bfs(0);
for(int i = 1; i <= n; ++i)
{
    if(ddn[i] == 3 && dist[0][i] == ((tn-1)<<1))
    {
        di[1] = i;
        break;
    }
}
bfs(1);
for(int i = 1; i <= n; ++i)
    poi[i].z = (dist[0][i] + dist[1][i] - ((tn-2)<<1)) >> 1;

我們可以用同樣的方法把\(x\)解出來,最後的\(y\)只要減一下就可以了。因為我的坐標是從1開始的,所以我對坐標做了一些微調,從0開始就沒有這個問題。

由於之前bfs的數據我們還能用,所以我們只需再一遍bfs即可

for(int i = 1; i <= n; ++i)
    if(poi[i].z == tn && dist[0][i] == dist[1][i] && ddn[i] == 3)
        di[2] = i;
bfs(2);
for(int i = 1; i <= n; ++i)
{
    poi[i].x = (dist[0][i] + dist[2][i] - ((tn-1)<<1)) >> 1;
    poi[i].y = (dist[0][i] - poi[i].x - poi[i].z) + 1;
    poi[i].x++;
    poi[i].y++;
    mmap[poi[i].x][poi[i].y][poi[i].z] = i;
}

於是我們就建好圖了。

後面的大力dfs也沒什麽好說的。

下面把ac代碼貼一下,由於stringstream在沒有氧氣的情況下極慢,所以必須開氧氣。

// luogu-judger-enable-o2
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>//stringstream 
#include <queue>

using namespace std;

const int maxm = 2058000;//這裏的最大邊數我是把點數加起來乘了6 
const int maxn = 75*75*75;
const int inf = 0x3f3f3f3f;

int n;//總個數 
int tn;//邊長 

struct Edge
{
    int to, nxt;
} e[maxm<<1];

int first[maxn];

int cnt;
inline void add_edge(int f, int t)
{
    e[++cnt].nxt = first[f];
    first[f] = cnt;
    e[cnt].to = t;
}

int dirx[] = {1, -1, 0, 0, 0, 0};
int diry[] = {0, 0, 1, -1, 0, 0};
int dirz[] = {0, 0, 0, 0, 1, -1};

int dep[maxn];//"好看程度" 
int vis[maxn];
int minn = inf;
int maxx = -inf;
int ll[10];//會發光的水晶的編號 
int ddn[maxn];//度數 
int zl;//這是個ddn的cnt 
int mmap[73][73][73]; 

struct zb
{
    int x, y, z;
} poi[maxn];

#define pan (x > 0 && x <= tn && y > 0 && y <= tn && z > 0 && z <= tn)
#define nxxt x += dirx[i], y += diry[i], z += dirz[i]

inline int getans(int i, zb a)//能加多少"好看程度"
{
    int ans = 0;
    int x = a.x, y = a.y, z = a.z;
    for(; pan; nxxt)
        if(!vis[mmap[x][y][z]]++)//由於待會兒回溯時要刪除,所以懶到家的我就直接用++,--代替記錄了 
            ans += dep[mmap[x][y][z]];
    return ans;
}

inline void delvis(int i, zb a)//回溯時把dfs前加的vis刪除 
{
    int x = a.x, y = a.y, z = a.z;
    for(; pan; nxxt)
        vis[mmap[x][y][z]]--;
}

inline void dfs(int now, int ans)//大力枚舉所有情況 
{
    if(now > zl)
    {
        minn = min(minn, ans);
        maxx = max(maxx, ans);
        return;
    }
    for(int i = 0; i < 6; ++i)
    {
        dfs(now+1, ans + getans(i, poi[ll[now]]));
        delvis(i, poi[ll[now]]);
    }
}

int dist[4][maxn];
int di[4];
bool viss[maxn];

inline void bfs(int id)//求最短路 
{
    memset(viss, 0, sizeof(viss));
    queue<int> q;
    int from = di[id];
    viss[from] = true;
    q.push(from);
    while(!q.empty())
    {
        int now = q.front();
        q.pop();
        for(int i = first[now]; i; i = e[i].nxt)
        {
            int to = e[i].to;
            if(!viss[to])
            {
                viss[to] = true;
                dist[id][to] = dist[id][now] + 1;
                q.push(to);
            }
        }
    }
}

int main() 
{
    ios:: sync_with_stdio(false);
    cin >> n;
    tn = n;
    string tmp;
    getline(cin, tmp);
    n *= n * n;
    for(int i = 1; i <= n; ++i)
    {
        getline(cin, tmp);
        stringstream ss(tmp);
        ss >> dep[i];
        int aa;
        if(!dep[i])
        {
            vis[i] = true;
            ll[++zl] = i;
        }
        while(ss >> aa)
        {
            add_edge(i, aa);
            ddn[i]++;
        }
    }
    for(di[0] = 1; ddn[di[0]] > 3; ++di[0]);
    bfs(0);
    for(int i = 1; i <= n; ++i)
    {
        if(ddn[i] == 3 && dist[0][i] == ((tn-1)<<1))
        {
            di[1] = i;
            break;
        }
    }
    bfs(1);
    for(int i = 1; i <= n; ++i)//得到z 
    {
        poi[i].z = (dist[0][i] + dist[1][i] - ((tn-2)<<1)) >> 1;
        if(poi[i].z == tn && dist[0][i] == dist[1][i] && ddn[i] == 3)
            di[2] = i;
    }
    bfs(2);
    for(int i = 1; i <= n; ++i)//得到x, y 
    {
        poi[i].x = (dist[0][i] + dist[2][i] - ((tn-1)<<1)) >> 1;
        poi[i].y = (dist[0][i] - poi[i].x - poi[i].z) + 1;
        poi[i].x++;
        poi[i].y++;
        mmap[poi[i].x][poi[i].y][poi[i].z] = i;
    }
    dfs(1, 0);
    cout << minn << ' ' << maxx << endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}

題解:[ZJOI2014]璀燦光華