[JLOI2009]神秘的生物

题目链接

题目大意

给定一个 n ∗ n n*n nn的矩阵,从其中选取恰好一个连通块,使选取的格子所对应的权值和最大。

n ≤ 9 n\leq 9 n9

解题思路

由于 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<iostream>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<cstring>
#define MAXN 1000
#define N 9
#define ll long long
#define IT vector<int>::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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值