题意:
在棋盘里,两个格子标为2,两个格子标为3,障碍格标为1,空格标为0。现在要把两个2相连,两个3相连,这两条线不能交叉,问最短线长。
解:
对于标有数字的格子,只有4种连法:上、左、下、右
对于障碍格子,不能连任何线,只有一种连法:空。
对于空格子,有7种连法,其中一种是空。
(上下、左右、上左、上右、下左、下右、空)
只要保证有数字的格子只用4种连法、’2’线和’3’线不相交并且路径最短,就自然保证了只有一条2线,只有一条3线。
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
#define mes(a,x,s) memset(a,x,(s)*sizeof a[0])
#define mem(a,x) memset(a,x,sizeof a)
#define ysk(x) (1<<(x))
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn= 9 ;
const int maxS= 1048576 ;//4^10
const int mod=19993;
int a[maxn+3][maxn+3];
int n,m,cur;
struct HashMap
{
vector<pii>G[mod+10];
void init() { for0(i,mod) G[i].clear(); }
void insert(int state,int val)
{
int p=state%mod;
for0(i,G[p].size())
{
if(G[p][i].first==state)
{
if(val<G[p][i].second) G[p][i].second=val;
return;
}
}
G[p].push_back(make_pair(state,val));
}
}hashmap[2];
struct Code
{
int bit[12],s;
void init(int x)
{
for(int i=0;i<=m;i++)
{
bit[i]=x&3;
x>>=2;
}
}
void getS()
{
s=0;
for(int i=m;i>=0;i--)
{
s=(s<<2)|bit[i];
}
}
void shift()
{
for(int i=m;i>=1;i--) bit[i]=bit[i-1];
bit[0]=0;
}
}code;
void dp1(int x,int y,int state,int val)
{
const int p=code.bit[y-1],q=code.bit[y];
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val);
}
void dp23(int x,int y,int state,int val)
{
const int p=code.bit[y-1],q=code.bit[y];
int now=a[x][y]-1;
if(p&&q) return;
if(p||q)
{
if(p+q!=now) return;
code.bit[y-1]=code.bit[y]=0;
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val+1);
return;
}
int t=a[x][y]-1;
if(y+1<=m&&a[x][y+1]!=1)
{
code.bit[y-1]=0,code.bit[y]=t;
code.getS();
hashmap[cur].insert(code.s,val+1);
}
if(x+1<=n&&a[x+1][y]!=1)
{
code.bit[y-1]=t,code.bit[y]=0;
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val+1);
}
}
void dp0(int x,int y,int state,int val)
{
const int p=code.bit[y-1],q=code.bit[y];
if(!p&&!q)
{
if(y+1<=m&&a[x][y+1]!=1&&x+1<=n&&a[x+1][y]!=1)
{
code.bit[y-1]=code.bit[y]=1;
code.getS();
hashmap[cur].insert(code.s,val+2);
code.bit[y-1]=code.bit[y]=2;
code.getS();
hashmap[cur].insert(code.s,val+2);
}
code.bit[y-1]=code.bit[y]=0;
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val);
return;
}
if(p&&q)
{
if(p!=q) return;
code.bit[y-1]=code.bit[y]=0;
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val+2);
return;
}
int t=p+q;
if(y+1<=m&&a[x][y+1]!=1)
{
code.bit[y-1]=0,code.bit[y]=t;
code.getS();
hashmap[cur].insert(code.s,val+2);
}
if(x+1<=n&&a[x+1][y]!=1)
{
code.bit[y-1]=t,code.bit[y]=0;
if(y==m) code.shift();
code.getS();
hashmap[cur].insert(code.s,val+2);
}
}
void solve()
{
cur=0;
hashmap[cur].init();
hashmap[cur].insert(0,0);
for1(i,n) for1(j,m)
{
cur^=1;
hashmap[cur].init();
for0(k,mod ) for0(l,hashmap[cur^1].G[k].size())
{
pii now=hashmap[cur^1].G[k][l];
int state=now.first;
int val=now.second;
code.init(state);
if(a[i][j]==2||a[i][j]==3) dp23(i,j,state,val);
else if(a[i][j]==1) dp1(i,j,state,val);
else dp0(i,j,state,val);
}
}
int ans=INF;
for0(k,mod) for0(l,hashmap[cur].G[k].size())
{
pii now=hashmap[cur].G[k][l];
ans=min(ans,now.second/2);
}
if(ans==INF) ans=0;
cout<<ans<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
while(cin>>n>>m&&(n||m))
{
for1(i,n) for1(j,m) cin>>a[i][j];
solve();
}
return 0;
}
后记:
为什么此题不用最小表示法,formula1中使用最小表示法,是因为在很多地方都可以新建连通分量,(这些分量到最后要求融合),而在最后一个非障碍格子,需要判断上左插头是不是在同一个连通分量里,进而判断添加添加上左插头是不是能形成一个闭合回路。
在最小表示法中,上左均有插头并且编号相同的情况以为着某个闭合回路要形成了,这种情况只能出现在最后一个非障碍格子里,否则会出现形成多个回路的情况。(而且,对于最后一个非障碍格子,因为下右均是障碍格子或者出界,所以能转移的情况只有上、左有插头,并且只有当两插头编号相同才是合法的)
这个题目因为要求比较特殊,新建连通分量只能在2和3这几个特定的格子里,最主要是要求路径最短,所以形成多个回路一定不是最优解,所以无需用最小表示法,直接用3进制即可。
在formula1中,最后一个非障碍格子里出现上左插头,并且编号不一样的情况似乎绝不会发生,貌似因为这代表前面的插头并没有处理(衔接)好:因为两个连通分量在这个位置第一次接触,那么前面必然没有接触,那么这两根线(插头)的另一端接到哪去了?