给定一个 n ∗ n n*n n∗n的矩阵,从其中选取恰好一个连通块,使选取的格子所对应的权值和最大。
n ≤ 9 n\leq 9 n≤9
由于 n n n特别小,考虑插头dp。
和一般的插头dp不同,这里的边界实际上是边界上的格子。
例如:
用不同的编号代表不同的连通块,相同的标号表示同一个连通块,没有选择用0表示。
这样最多会有5种不同的连通块,为了方便,可以将标号设为0~7
但是,这样转移状态可能会很多,但很显然有很多多余的状态(例如’20102’和’10201’描述的连通块本质是一样的)。
所以我们考虑使用“最小表示法”,让所有状态变为与它等价的状态中字典序最小的那个(比如20102就要变成10201)。
void re_lab(ll &u)
{
memset(hav,0,sizeof(hav));
int t=0;
for(int i=0;i<n;i++)
if((u>>_8[i])&7)
{
int p=1ll*(u>>_8[i])&7;
if(!hav[p]) hav[p]=++t;
u-=p<<_8[i];
u+=hav[p]*1ll<<_8[i];
}
}
接下来考虑贡献。由于题目并没有要求到达最后一个格子,所以只要只有一个连通块的方案都是可行的。而只有一个连通块对应最小表示法上最大的标号是1。
特别的,题目中提到不能不选,所以要特判全负的情况
然后考虑转移。
首先考虑不选。由于所有格子此时不一定联通,我们需要判断它所对应的原来的标号是否是所有标号中的唯一一个,如果是,不选这个格子会导致该标号所对应的的连通块被分割出来,不符合题意。如果不是才可以更新。
对于选的情况,如果两个连通块中有一个是空(0)或者两个标号相同,直接取较大的那个。否则需要合并两个连通块,直接暴力将某个标号变成另一个即可。
状态转移同【模板】插头dp
#include
#include
#include
#include
#include
#define MAXN 1000
#define N 9
#define ll long long
#define IT vector::iterator
#define MP make_pair
using namespace std;
vector<int>ton[MAXN];
ll f[2][1<<(N*2)],g[2][1<<(N*2)],tot[2];
int n;
int hav[10];
ll _8[N<<1];
void re_lab(ll &u)//to the min
{
memset(hav,0,sizeof(hav));
int t=0;
for(int i=0;i<n;i++)
if((u>>_8[i])&7)
{
int p=1ll*(u>>_8[i])&7;
if(!hav[p]) hav[p]=++t;
u-=p<<_8[i];
u+=hav[p]*1ll<<_8[i];
}
}
void insert(int u,ll p,ll v)
{
re_lab(p);
int k=p%MAXN;
for(IT it=ton[k].begin();it!=ton[k].end();it++)
if(g[u][*it]==p){f[u][*it]=max(f[u][*it],v);return;}
ton[k].push_back(++tot[u]);
g[u][tot[u]]=p;
f[u][tot[u]]=v;
}
void init(){_8[0]=0;for(int i=1;i<=n;i++) _8[i]=i*3;}
int get(ll u,int p){return p<0?0:(u>>_8[p])&7;}
void change(ll &u,int a,int b)//change pos 'a' to 'b'
{
u-=((u>>_8[a])&7)*1ll<<_8[a];
u+=b*1ll<<_8[a];
}
void merge(ll &u,int a,int b)//change all 'a' to 'b'
{
for(int i=0;i<=n;i++)
if(((u>>_8[i])&7)==a)
{
u-=a*1ll<<_8[i];
u+=b*1ll<<_8[i];
}
}
bool find(ll u,int a)
{
int p=-1;
for(;u;u>>=3) if((u&7)==a) p++;
return p>0;
}
int val[N][N];
ll ans=-100000000;
void work()
{
int u=0;
insert(u,0,0);
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
u=!u;
for(int l=0;l<=MAXN;l++) ton[l].clear();
memset(f[u],0,sizeof(f[u]));
memset(g[u],0,sizeof(g[u]));
tot[u]=0;
for(int k=1;k<=tot[!u];k++)
{
ll sta=g[!u][k],v=f[!u][k];
bool rt=false;
for(int l=sta;l;l>>=3) if((l&7)==1) rt=true;
for(int l=sta;l;l>>=3) if((l&7)>1) rt=false;
if(rt) ans=max(ans,v);
int p=get(sta,j-1),q=get(sta,j);
ll nsta=sta;
if(!q || find(sta,q))
{
change(nsta,j,0);
insert(u,nsta,v);
}
v+=val[i][j];
if(!p && !q)
{
change(nsta,j,7);
insert(u,nsta,v);
}
else
{
nsta=sta;
if(!q) change(nsta,j,p);
else if(p) merge(nsta,p,q);
insert(u,nsta,v);
}
}
}
}
for(int k=1;k<=tot[u];k++)
{
bool rt=true;
for(int l=g[u][k];l;l>>=3) if((l&7)>1) rt=false;
if(rt) ans=max(ans,f[u][k]);
}
}
int main()
{
scanf("%d",&n);
init();
int z=-10000000;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&val[i][j]),z=max(z,val[i][j]);
if(z<=0){printf("%d\n",z);return 0;}
work();
printf("%lld",ans);
return 0;
}