[ZOJ3989]Triangulation

CXXXV.[ZOJ3989]Triangulation

神题。

这个数据范围很难不让人想到状压DP。于是我们考虑应该怎么设计状态。

考虑一组三角剖分的形态:其必定是在所有点所构成的凸包内部划分出很多三角形。这也就表明,任何一组三角剖分一定包含所有凸包上的边。

我们可以想到一个比较简洁的DP:设 \(f(\mathbb S)\) 表示在点集 \(\mathbb S\) 所构成的凸包内部的所有点的三角剖分。但是这样子跑的话,每一组三角剖分都会被统计多次,不能不重复地计数。

于是我们另辟蹊径。我们令初始三角剖分上的边仅包含所有点集的下凸壳上的所有边。之后,维护当前剖分的上边界,每次往上边界上添加一个新的三角形,得到新的上边界,这样持续不断直到填成了整个点集的上凸壳

但是,出于不重复计数的考虑,我们必须对该上边界做出限制。

我们强制该上边界上的点的 \(x\) 坐标严格递增。这样,只需知道点集就能唯一还原出边界,而不必存储点集内部的顺序了。

同时,为了处理有点 \(x\) 坐标相同的情形,我们将所有点随机旋转一个角度,这样便消除了 \(x\) 坐标相同的可能(如果仍相同,继续转即可)。于是以下默认所有点 \(x\) 坐标互不相同。

接着考虑如何添加三角形。

我们发现,其可以分为两种情形:

  1. 对于边界上连续的三个点 \(A,B,C\),满足 \(B\)\(AC\) 下方,且 \(\triangle_{ABC}\) 内部无点,此时便可以连边 \(AC\),将 \(B\) 从边界上删除。

  2. 对于边界上连续的两个点 \(A,B\),存在一个 \(C\)\(AB\) 上方,且 \(C\)\(x\) 坐标介于 \(A,B\) 之间,且 \(\triangle_{ABC}\) 内部无点,此时便可以连边 \(AC,BC\),并将 \(C\) 插入上边界。

为了不重复计算,我们每次仅加入上边界上最左的三角形。也就是说,如果一条边 \(AB\) 在某一轮转移中没有生长出三角形来,则其之后也一定不会再长三角形。同时,对于情形1,我们将其算作右边的 \(BC\) 边长出的三角形,这样便统一了条件。

这样,我们便可以设 \(f(\mathbb S,i)\) 表示现在上边界是集合 \(\mathbb S\),且 \(\mathbb S\) 中前 \(i\) 条边都已经无法再生长的状态。这时,考虑第 \(i+1\) 条边,处理其长或不长的状态即可。

需要注意的是,因为 \(x\) 坐标最大最小的两个点无论何时都必在上边界上,所以可以在状态中不记录它们,这样剩下的所有状态都是可能出现的状态了。且因为没有明确的转移顺序,采取bfs转移。

通过预处理一大坨东西(三角形内部有没有点啦、情形2的哪些 \(C\) 合法啦,之类的),我们可以做到 \(O(n^4+n2^n)\) 的复杂度,其中 \(n^4\) 是预处理。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,ord[20],a[20][20],rev[20],f[1<<16][17],in[1<<16],b[20];
ll g[1<<16][17];
const double eps=1e-13;
const double pi=acos(-1);
int cmp(double x){
	if(x>eps)return 1;
	if(x<-eps)return -1;
	return 0;
}
struct Vector{
	double x,y;
	Vector(){}
	Vector(double X,double Y){x=X,y=Y;}
	friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
	friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
	friend Vector operator *(const Vector &u,const double &v){return Vector(u.x*v,u.y*v);}
	friend Vector operator /(const Vector &u,const double &v){return Vector(u.x/v,u.y/v);}
	friend double operator &(const Vector &u,const Vector &v){return u.x*v.y-u.y*v.x;}//cross times
	friend double operator |(const Vector &u,const Vector &v){return u.x*v.x+u.y*v.y;}//point times
	friend bool operator <(const Vector &u,const Vector &v){return u.x<v.x;}
	double operator ~()const{return sqrt(x*x+y*y);}//the modulo of a vector
	double operator !()const{return atan2(y,x);}//the angle of a vector
	void print(){printf("(%lf,%lf)",x,y);}
	void rotate(double ang){
		double modu=~*this;
		double angl=!*this;
		angl+=ang;
		x=cos(angl)*modu,y=sin(angl)*modu;
	}
}p[20];
typedef Vector Point;
struct Line{
	Point x,y;
	Vector z;
	Line(){}
	Line(Point X,Point Y){x=X,y=Y,z=Y-X;}
	friend Point operator &(const Line &u,const Line &v){return u.x+u.z*((v.z&(u.x-v.x))/(u.z&v.z));}
};
typedef Line Segment;
bool CMP(int u,int v){return p[u].x<p[v].x;}
bool lower[20],upper[20];
int stk[20],tp,LO,UP;
queue<int>q;
void trans(int i,int j,int I,int J,int k){
	if(!--in[I])q.push(I);
	if(f[I][J]>k)f[I][J]=k,g[I][J]=g[i][j];
	else if(f[I][J]==k)g[I][J]+=g[i][j];
}
vector<int>v[20][20];
bool abv[20][20][20],ins[20][20][20];//if above, true.
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n),LO=UP=0,memset(f,0x3f,sizeof(f)),memset(in,0,sizeof(in)),memset(ins,true,sizeof(ins));
		for(int i=0;i<n;i++)scanf("%lf%lf",&p[i].x,&p[i].y),ord[i]=i,lower[i]=upper[i]=false;
		while(true){
			double ang=(1.0*rand()/RAND_MAX)*2*pi;
			for(int i=0;i<n;i++)p[i].rotate(ang);
			sort(ord,ord+n,CMP);
			bool ok=true;
			for(int i=1;i<n;i++)if(!cmp(p[i].x-p[i-1].x)){ok=false;break;}
			if(ok)break;
		}
		for(int i=0;i<n;i++)rev[ord[i]]=i;
		for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&a[rev[i]][rev[j]]);
		sort(p,p+n);
//		for(int i=0;i<n;i++)p[i].print();puts("");
		
		for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)for(int k=i+1;k<j;k++)abv[i][j][k]=(cmp((p[i]-p[k])&(p[j]-p[k]))==1);
		
		stk[++tp]=0;
		for(int i=1;i<n;i++){
			while(tp>=2&&cmp((p[stk[tp-1]]-p[stk[tp]])&(p[i]-p[stk[tp]]))!=-1)tp--;
			stk[++tp]=i;
		}
		for(int i=1;i<=tp;i++)lower[stk[i]]=true,LO|=1<<stk[i];
		LO=(LO^(1<<(n-1)))>>1,f[LO][0]=0,g[LO][0]=1;
		for(int i=1;i<tp;i++)f[LO][0]+=a[stk[i]][stk[i+1]];
		tp=0;
		
		stk[++tp]=0;
		for(int i=1;i<n;i++){
			while(tp>=2&&cmp((p[stk[tp-1]]-p[stk[tp]])&(p[i]-p[stk[tp]]))!=1)tp--;
			stk[++tp]=i;
		}
		for(int i=1;i<=tp;i++)upper[stk[i]]=true,UP|=1<<stk[i];
		UP=(UP^(1<<(n-1)))>>1;
		tp=0;
		
//		printf("STA:%d %d\n",LO,UP);
//		for(int i=0;i<n;i++)printf("(%d %d)",lower[i],upper[i]);puts("");
		
		for(int i=0;i<n;i++)for(int j=i+1;j<n;j++){
			for(int k=i+1;k<j;k++){
				if(abv[i][j][k]){
					bool ok=true;
					for(int l=i+1;l<k;l++)if(abv[i][j][l]&&!abv[i][k][l]){ok=false;break;}
					if(!ok)continue;
					for(int l=k+1;l<j;l++)if(abv[i][j][l]&&!abv[k][j][l]){ok=false;break;}
					if(!ok)continue;
					v[i][j].push_back(k);					
				}else{
					ins[i][j][k]=false;
					for(int l=i+1;l<k;l++)if(!abv[i][j][l]&&abv[i][k][l]){ins[i][j][k]=true;break;}
					for(int l=k+1;l<j;l++)if(!abv[i][j][l]&&abv[k][j][l]){ins[i][j][k]=true;break;}
				}
			}
		}
		
		for(int i=0;i<(1<<(n-2));i++){
			int len=0;b[len++]=0;
			for(int j=0;j<n-2;j++)if(i&(1<<j))b[len++]=j+1;
			b[len++]=n-1;
//			printf("%d:",i);for(int j=0;j<len;j++)printf("%d ",b[j]);puts("");
			for(int j=0;j<len;j++){
				if(j&&j+1<len&&!ins[b[j-1]][b[j+1]][b[j]])in[i^(1<<(b[j]-1))]++;
				if(j)for(auto k:v[b[j-1]][b[j]])in[i|(1<<(k-1))]++;
			}
		}
		q.push(LO);
		while(!q.empty()){
			int i=q.front();q.pop();
//			printf("SS:%d\n",i);
			int len=0;b[len++]=0;
			for(int j=0;j<n-2;j++)if(i&(1<<j))b[len++]=j+1;
			b[len++]=n-1;
			for(int j=0;j<len;j++){
				if(j&&j+1<len&&!ins[b[j-1]][b[j+1]][b[j]])trans(i,j,i^(1<<(b[j]-1)),j-1,f[i][j]+a[b[j-1]][b[j+1]]);
				if(j)for(auto k:v[b[j-1]][b[j]])trans(i,j-1,i|(1<<(k-1)),j-1,f[i][j-1]+a[b[j-1]][k]+a[b[j]][k]);
				if(j+2<len)trans(i,j,i,j+1,f[i][j]);
			}
		}
		printf("%d %lld\n",f[UP][__builtin_popcount(UP)],g[UP][__builtin_popcount(UP)]);
		
		for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)v[i][j].clear();
	}
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14601549.html