1. 程式人生 > 其它 >「CEOI2014」The Wall 題解

「CEOI2014」The Wall 題解

[CEOI2014] The Wall 題解

題意

\(~~~~\) 求在二維平面上選擇一些有權值的線段並將給定的格子圍住的最小權。

\(~~~~\) \(1\leq n,m\leq 400\)

題解

\(~~~~\) 神仙結論題目。

\(~~~~\) 首先這道題的結論是:將每個方格四周的點按照權值連邊後,所有關鍵格子左上角的點到最左上角的最短路一定被圍住或是邊界的一部分。

\(~~~~\) 如何證明這個結論?考慮某個最優且某條最短路不被圍住的方案:如圖, \(v\) 為一關鍵格子,其中綠線為左上角到這個格子左上角的最短路,陰影部分為被圈住的區域。由於最短路的定義,則我們把選擇紅色線段的部分改為藍色線段一定不會使花費更大,但與此同時被圈住的區域變大了,因此選擇最短路一定是最優的。

\(~~~~\) 知道了這個結論我們可以怎麼做呢?考慮把原先的點繼續拆成左上、右上、右下、左下四個點,同時保留原先的邊。當不在原圖中的兩個點不會跨過所有最短路時這兩個點就可以連一條 \(0\) 權邊。同時注意第一個點拆出的左上不能與其他點連線。最後我們再跑一次從第一個點的右上到第一個點的左下的最短路就可以得到答案了。

\(~~~~\) 比如下圖就是樣例一的轉化後的圖(每個點拆出來的點順時針標號 \(0 \sim 3\)

程式碼

#include <map>
#include <queue>
#include <cassert>
#include <cstdio>
#include <vector>
#include <algorithm>
#define ll long long
#define PII pair<ll,ll>
#define mp(a,b) make_pair(a,b)
using namespace std;
ll n,m;
ll L1[405][405],L2[405][405];
bool vil[405][405];
bool vis[2000005];
map<ll,bool>S[200000];
ll dis[1000005],fa[1000005];
vector < PII > G[1000005];
inline ll ID(ll x,ll y){return (x-1)*(m+1)+y;}
inline ll ID3(ll x,ll y,ll z){return ((x-1)*(m+1)+y-1)*4+z;}
struct cmp{
	bool operator()(const PII x,const PII y){return x.second>y.second;}
};
void Dij(ll N)
{
	priority_queue< PII,vector< PII >,cmp >Q;
	for(ll i=1;i<=N;i++) vis[i]=0,dis[i]=999999999999999999;
	Q.push(mp(1,0));dis[1]=0;
	while(!Q.empty())
	{
		ll u=Q.top().first;Q.pop();
		if(vis[u]) continue; vis[u]=true;
		for(ll i=0;i<G[u].size();i++)
		{
			ll v=G[u][i].first,w=G[u][i].second;
			if(!vis[v]&&dis[v]>dis[u]+w)
			{
				fa[v]=u;
				dis[v]=dis[u]+w;
				Q.push(mp(v,dis[v]));
			}
		}
	}
}
template<typename T>void print(T x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
void Tag(ll u)
{
	while(u!=1)
	{
//		prllf("ban:%d %d\n",fa[u],u);
		S[fa[u]][u]=S[u][fa[u]]=true;
		u=fa[u];
	}
}
void AddEdge(ll u,ll v,ll w){G[u].push_back(mp(v,w)); G[v].push_back(mp(u,w));assert(v>=0);/*prllf("%d %d %d\n",u,v,w);*/}
int main() {	
	scanf("%lld %lld",&n,&m);
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=m;j++) scanf("%d ",&vil[i][j]);
	for(ll i=1;i<=n;i++)
	{
		for(ll j=1,x;j<=m+1;j++)
		{
			scanf("%lld",&x);L1[i][j]=x;
			ll u=ID(i,j),v=ID(i+1,j);
			G[u].push_back(mp(v,x));
			G[v].push_back(mp(u,x));
		}
	}
	for(ll i=1;i<=n+1;i++)
	{
		for(ll j=1,x;j<=m;j++)
		{
			scanf("%lld",&x);L2[i][j]=x;
			ll u=ID(i,j),v=ID(i,j+1);
			G[u].push_back(mp(v,x));
			G[v].push_back(mp(u,x));
		}
	}
	Dij((n+1)*(m+1));
	for(ll i=1;i<=n+1;i++)
		for(ll j=1;j<=m+1;j++) G[ID(i,j)].clear();
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=m;j++)
			if(vil[i][j]) Tag(ID(i,j));
	for(ll i=1;i<=n;i++)
	{
		for(ll j=1;j<=m+1;j++)
		{
			AddEdge(ID3(i,j,2),ID3(i+1,j,1),L1[i][j]);
			AddEdge(ID3(i,j,3),ID3(i+1,j,0),L1[i][j]);
			if(!S[ID(i,j)].count(ID(i+1,j)))
			{
				if(!vil[i][j-1]&&!vil[i][j]) AddEdge(ID3(i,j,2),ID3(i,j,3),0);
				if(!vil[i][j-1]&&!vil[i][j]&&(i!=1||j!=1)) AddEdge(ID3(i+1,j,0),ID3(i+1,j,1),0);
			}
		}
	}
	for(ll i=1;i<=n+1;i++)
	{
		for(ll j=1;j<=m;j++)
		{
			AddEdge(ID3(i,j,1),ID3(i,j+1,0),L2[i][j]);
			AddEdge(ID3(i,j,2),ID3(i,j+1,3),L2[i][j]);
			if(!S[ID(i,j)].count(ID(i,j+1)))
			{
				if(!vil[i-1][j]&&!vil[i][j]) AddEdge(ID3(i,j,1),ID3(i,j,2),0);
				if(!vil[i-1][j]&&!vil[i][j]&&(i!=1||j!=1))AddEdge(ID3(i,j+1,0),ID3(i,j+1,3),0);	
			}
		}
	}
	for(ll i=1;i<=m+1;i++)
	{
		if(i!=1) AddEdge(ID3(1,i,1),ID3(1,i,0),0);
		AddEdge(ID3(n+1,i,2),ID3(n+1,i,3),0);
	}
	for(ll i=1;i<=n+1;i++)
	{
		if(i!=1) AddEdge(ID3(i,1,0),ID3(i,1,3),0);
		AddEdge(ID3(i,m+1,1),ID3(i,m+1,2),0);
	}
	Dij((n+1)*(m+1)*4);
	printf("%lld",dis[3]);
	return 0;
}