模拟赛20200215【区间异或(差分+bfs+状压),变化边权最小生成树,线性回归方程(绝对值函数、偏导数求最值)】

T1:

模拟赛20200215【区间异或(差分+bfs+状压),变化边权最小生成树,线性回归方程(绝对值函数、偏导数求最值)】_第1张图片
在这里插入图片描述

题解:

[ i , i + s i z − 1 ] [i,i+siz-1] [i,i+siz1]取反,差分一下,可以看成在 i i i 异或1,在 i + s i z i+siz i+siz 异或1。
于是问题转化为使差分数组与原数组的差分数组相同,差分数组的范围为 [ 1 , n + 1 ] [1,n+1] [1,n+1]
原数组中 x x x为1,即在差分数组中 x x x 异或 1, x + 1 x+1 x+1 异或 1。可以看出1的个数 ≤ 2 k \le2k 2k个。
在对 i i i i + s i z i+siz i+siz进行改动时,差分数组中要求为1的位置会被改动奇数次,要求为 0 0 0的位置会被改动偶数次,可以将改动的过程看做从一个1经过多次改动走到另一个1,中间经过的状态不变。那么问题相当于求差分数组中的1两两配对的最小代价。
求任意两个1的距离可以用 bfs \text{bfs} bfs O ( k n m ) O(knm) O(knm)时间完成,配对可以用状压DP在 O ( 2 2 k ∗ 2 k ) O(2^{2k}*2k) O(22k2k)时间完成。

Code:

#include
#define maxn 10005
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,k,siz[105],seq[maxn],a[25],cnt,dis[maxn],w[25][25],f[1<<20];
queue<int>q;
void BFS(int t){
	memset(dis,-1,(n+2)<<2);
	dis[a[t]]=0,q.push(a[t]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=1,v;i<=m&&(v=u+siz[i])<=n+1;i++) if(dis[v]==-1) dis[v]=dis[u]+1,q.push(v);
		for(int i=1,v;i<=m&&(v=u-siz[i])>=1;i++) if(dis[v]==-1) dis[v]=dis[u]+1,q.push(v);
		//can't mark at 0.
	}
	for(int i=0;i<cnt;i++) w[t][i]=dis[a[i]]>=0?dis[a[i]]:inf;
}
int main()
{
	freopen("password.in","r",stdin);
	freopen("password.out","w",stdout);
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1,x;i<=k;i++) scanf("%d",&x),seq[x]^=1,seq[x+1]^=1;
	for(int i=1;i<=n+1;i++) if(seq[i]) a[cnt++]=i;
	for(int i=1;i<=m;i++) scanf("%d",&siz[i]);
	sort(siz+1,siz+1+m),m=unique(siz+1,siz+1+m)-siz-1;
	for(int i=0;i<cnt;i++) BFS(i);
	memset(f,0x3f,sizeof f),f[0]=0;
	for(int s=0;s<1<<cnt;s++) if(f[s]!=inf){
		for(int i=0,k=-1;i<cnt;i++) if(!(s>>i&1)){
			if(k==-1) {k=i;continue;}
			f[s|1<<k|1<<i]=min(f[s|1<<k|1<<i],f[s]+w[k][i]);
		}
	}
	printf("%d\n",f[(1<<cnt)-1]!=inf?f[(1<<cnt)-1]:-1);
}

T2:

给出三维空间中 n n n个点 ( x i , y i , z i ) (x_i,y_i,z_i) (xi,yi,zi),以及它们的速度 ( v x i , v y i , v z i ) (vx_i,vy_i,vz_i) (vxi,vyi,vzi)
两点之间的边权为它们的欧氏距离。
求最小生成树的边集在运动过程中会变化多少次。
保证任意两点不相撞,任意时刻最小生成树唯一,且在 t t t时刻变得最小的生成树在 t + 1 0 − 6 t+10^{-6} t+106之内也是最小的。
n ≤ 50 , − 150 ≤ x , y , z ≤ 150 , − 100 ≤ v x , v y , v z ≤ 100 。 n≤50,-150≤x,y,z≤150,-100≤vx,vy,vz≤100。 n50,150x,y,z150,100vx,vy,vz100

题解:

因为 n n n很小,所以总的边数 n 2 / 2 n^2/2 n2/2也是很小的,最小生成树会变说明某些边的相对大小关系发生了变化。
我们考虑一条边什么时候边权会比另外一条边小,就会得到 n 4 n^4 n4个的事件点,事件点的求法就是解一个一元二次方程,不多说。暴力的话,直接考虑相邻的 2 个事件点之间的时段,生成树的形态不会变了,所以可以 O ( n 2 ) O(n^2) O(n2)再求一下最小生成树,复杂度就是 O ( n 6 ) O(n^6) O(n6)
考虑 n 4 n^4 n4 对的事件点,我们先对其进行排序,然后一次事件点干的事情是:交换了一对边的大小关系。如果两条边都不在最小生成树中或都在最小生成树中,就不必重构。这样重构的次数大概是O(跑得过) O ( n 3 ) O(n^3) O(n3)级别的(吗?不会证)。
值得注意的一点是,根据题意,同一个时刻是可以发生多次的边的交换的,
这时候,答案只算一次,这个情况需要特殊考虑,把交换时刻相同的边拿出来乱
搞即可。
写代码时值得注意的另一点是,带入时刻求最小生成树时,如果 T T T时刻是发生交换的时刻,那么要带入 T + e p s T+eps T+eps

Code:

#include
using namespace std;
const int N = 53, M = N*N;
const double eps = 1e-6;
int n,A[N][6],cl,cE,cnt,f[N],ans=1;
bool ont[N][N];
inline double sqr(double x){return x*x;}
struct node{
	int x,y; double w;
	bool operator < (const node &p)const{return w<p.w;}
	void calc(double t){w=0;for(int i=0;i<3;i++) w+=sqr(A[x][i]+t*A[x][i+3]-A[y][i]-t*A[y][i+3]);}
	void equation(double &a,double &b,double &c){
		a=b=c=0;
		for(int i=0;i<3;i++) a+=sqr(A[x][i+3]-A[y][i+3]),b+=2*(A[x][i]-A[y][i])*(A[x][i+3]-A[y][i+3]),c+=sqr(A[x][i]-A[y][i]);
	}
}L[M],pre[M],now[M],tmp[M],E[M*M];//E:event.
bool cmp(int i,int j){return L[i].w<L[j].w;}
bool cmpx(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;}
inline void Ins_time(double t,int i,int j){if(t<=0) return; E[++cE]=(node){i,j,t};}
inline bool Ontree(node p){return ont[p.x][p.y];}
int find(int x){return !f[x]?x:f[x]=find(f[x]);}
void Kruskal(){
	static int id[M];
	for(int i=1;i<=cl;i++) id[i]=i;
	sort(id+1,id+1+cl,cmp);
	for(int o=1,i,x,y;o<=cl;o++){
		i=id[o];
		if((x=find(L[i].x))!=(y=find(L[i].y)))
			f[x]=y,pre[++cnt]=L[i],ont[L[i].x][L[i].y]=ont[L[i].y][L[i].x]=1;
	}
}
void Re_Kruskal(double t){
	for(int i=1;i<=n;i++) f[i]=0;
	memset(ont,0,sizeof ont);
	for(int i=1;i<=cnt;i++) now[i].calc(t);
	sort(now+1,now+1+cnt);
	int num=0,x,y;
	for(int i=1;i<=cnt;i++)
		if((x=find(now[i].x))!=(y=find(now[i].y)))
			f[x]=y,tmp[++num]=now[i],ont[now[i].x][now[i].y]=ont[now[i].y][now[i].x]=1;
}
int main()
{
	freopen("system.in","r",stdin);
	freopen("system.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) for(int j=0;j<6;j++) scanf("%d",&A[i][j]);
	for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) L[++cl]=(node){i,j},L[cl].calc(0);
	double a1,b1,c1,a2,b2,c2,a,b,c,delta;
	for(int i=1;i<cl;i++){
		L[i].equation(a1,b1,c1);
		for(int j=i+1;j<=cl;j++){
			L[j].equation(a2,b2,c2);
			a=a1-a2,b=b1-b2,c=c1-c2;
			if(fabs(a)<eps){
				if(fabs(b)<eps) continue;
				Ins_time(-c/b,i,j);
			}
			else{
				delta=b*b-4*a*c;
				if(delta<0) continue;
				Ins_time((-b+sqrt(delta))/(2*a),i,j),Ins_time((-b-sqrt(delta))/(2*a),i,j);
			}
		}
	}
	Kruskal();
	sort(E+1,E+1+cE),sort(pre+1,pre+n,cmpx);
	for(int i=1;i<=cnt;i++) now[i]=pre[i];
	for(int i=1,last=0,flg=0;i<=cE;i++){
		bool f1=Ontree(L[E[i].x]),f2=Ontree(L[E[i].y]);
		if(f1!=f2) now[++cnt]=!f1?L[E[i].x]:L[E[i].y],flg=1;
		if(E[i].w-E[last].w>=eps&&flg){
			Re_Kruskal(E[i].w+eps);
			sort(tmp+1,tmp+n,cmpx);
			int d=0;
			for(int j=1;j<n;j++){
				if(pre[j].x!=tmp[j].x||pre[j].y!=tmp[j].y) d=1;
				pre[j]=now[j]=tmp[j];
			}
			ans+=d,last=i,flg=0,cnt=n-1;
		}
	}
	printf("%d\n",ans);
}

T3:

模拟赛20200215【区间异或(差分+bfs+状压),变化边权最小生成树,线性回归方程(绝对值函数、偏导数求最值)】_第2张图片
模拟赛20200215【区间异或(差分+bfs+状压),变化边权最小生成树,线性回归方程(绝对值函数、偏导数求最值)】_第3张图片

题解:

n = 2 , a k 1 = 1 n=2,a_{k1}=1 n=2ak1=1时,将 x 2 x_2 x2看做斜率 k k k x 1 x_1 x1看做截距 b b b,这个问题就是给出一系列点 ( a k 2 , b k ) (a_{k2},b_k) (ak2,bk),求一条尽可能拟合的直线 y = k x + b y=kx+b y=kx+b。当 t k = 2 t_k=2 tk=2时就是高中学过的线性回归方程。

对于前30%的数据,要使 ∑ ∣ y i − ( k x i + b ) ∣ \sum|y_i-(kx_i+b)| yi(kxi+b)最小。
不妨考虑固定 k,那么我们可以移动这条直线使其至少经过给定的一个点
枚举其为 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),原式表达为 ∑ ∣ y i − y 0 − k ( x i − x 0 ) ∣ \sum{|y_i-y_0-k(x_i-x_0)|} yiy0k(xix0)。这样就只剩下 k k k一个变量,对于其中的一个绝对值函数显然只会有一个拐点 y i − y 0 x i − x 0 y_i-y_0\over x_i-x_0 xix0yiy0且在两边都是一次函数,所以整个式子可以在某个拐点处取得最值,所以我们就证明了,这条直线一定可以经过 2 个点。 m 3 m^3 m3暴力显然。优化的话,考虑枚举一个确定的点,然后对绝对值函数的变化的时刻排序,一个函数要么是 Y − k ∗ X Y-k*X YkX的形式,要么是 k ∗ X − Y k*X-Y kXY的形式,记下 k k k的系数和常数即可计算。

对于剩余的 20%数据,由于是平方,所以我们可以把绝对值去掉,这个函数
是一个连续的函数,一种思路是我们三分 k 再三分 b,然后再计算答案=_=不多说。

这里提供另外一个思路:注意到对于任意一个变量,假如其他变量都已经确定了(看做常数),那么整个式子关于这个变量的导数显然为 0 (极值点导数为0)。那么我们就得到了一个线性方程。对于每个变量我们都可以得到一个方程,所以总共有 N 个方程,直接进行高斯消元即可(在此题中如果存在自由元说明存在另一个变量和它等价,可以令其为0 ),复杂度是 O ( n 2 m + n 3 ) O(n^2m+n^3) O(n2m+n3), n n n 为变量的个数,这里就 2 个。(其实就是偏导数)
x 1 x_1 x1为例:
∑ ( a k 1 x 1 + a k 2 x 2 + . . . − b k ) 2 = ∑ a k 1 2 x 1 2 + 2 a k 1 ( a k 2 x 2 + . . . − b k ) x 1 + ( a k 2 x 2 + . . . − b k ) 2 \sum (a_{k1}x_1+a_{k2}x_2+...-b_k)^2=\sum a_{k1}^2x_1^2+2a_{k1}(a_{k2}x_2+...-b_k)x_1+(a_{k2}x_2+...-b_k)^2 (ak1x1+ak2x2+...bk)2=ak12x12+2ak1(ak2x2+...bk)x1+(ak2x2+...bk)2
它关于 x 1 x_1 x1的导数为
∑ 2 a k 1 2 x 1 + 2 a k 1 ( a k 2 x 2 + a k 3 x 3 + . . . − b k ) = 0 \sum 2a_{k1}^2x_1+2a_{k1}(a_{k2}x_2+a_{k3}x_3+...-b_k)=0 2ak12x1+2ak1(ak2x2+ak3x3+...bk)=0

模拟赛20200215【区间异或(差分+bfs+状压),变化边权最小生成树,线性回归方程(绝对值函数、偏导数求最值)】_第4张图片

Code:

#include
#define maxm 100005
using namespace std;
typedef long double LD;
const double eps = 1e-7;
int n,m;
LD A[maxm][10],B[maxm],x[maxm],y[maxm],Ans=1e20;
struct node{
	LD X,Y,k;
	bool operator < (const node &p)const{return k<p.k;}
}P[maxm];
void solve1(){
	for(int i=1;i<=m;i++) x[i]=A[i][2],y[i]=B[i];
	for(int i=1;i<=m;i++){
		int cnt=0; LD X=0,Y=0;
		for(int j=1;j<=m;j++) if(i!=j){
			if(fabs(x[j]-x[i])<eps) Y+=fabs(y[j]-y[i]);
			else{
				P[++cnt].k=(y[j]-y[i])/(x[j]-x[i]);
				if(x[j]>x[i]){
					X-=x[j]-x[i],P[cnt].X=x[j]-x[i];
					Y+=y[j]-y[i],P[cnt].Y=y[i]-y[j];
				}
				else{
					X+=x[j]-x[i],P[cnt].X=x[i]-x[j];
					Y-=y[j]-y[i],P[cnt].Y=y[j]-y[i];
				}
			}
		}
		sort(P+1,P+1+cnt);
		if(!X) Ans=min(Ans,Y);
		for(int j=1;j<=cnt;j++){
			Ans=min(Ans,X*P[j].k+Y);
			X+=2*P[j].X, Y+=2*P[j].Y;
		}
	}
	printf("%.10Lf\n",Ans);
}
LD a[1005][1005];
inline bool no(LD x){return fabs(x)<eps;}
void solve2(){
	for(int i=1;i<=n;i++)
		for(int k=1;k<=m;k++){
			for(int j=1;j<=n;j++)
				a[i][j]+=A[k][i]*A[k][j];
			a[i][n+1]+=A[k][i]*B[k];
		}
	for(int i=1;i<=n;i++){
		if(no(a[i][i])) for(int j=i;j<=n;j++) if(!no(a[j][i])) {swap(a[i],a[j]);break;}
		if(no(a[i][i])) continue;
		for(int j=1;j<=n;j++) if(i!=j&&!no(a[j][i])){
			LD t = a[j][i]/a[i][i];
			for(int k=i;k<=n+1;k++) a[j][k]-=t*a[i][k];
		}
	}
	for(int i=1;i<=n;i++)
		if(no(a[i][i])) x[i]=0;
		else x[i]=a[i][n+1]/a[i][i];
	for(int i=1;i<=m;i++){
		LD s=0;
		for(int j=1;j<=n;j++) s+=A[i][j]*x[j];
		Ans+=(B[i]-s)*(B[i]-s);
	}
	printf("%.10Lf\n",Ans);
}
int main()
{
	freopen("optimum.in","r",stdin);
	freopen("optimum.out","w",stdout);
	int T; scanf("%*s%d",&T);
	if(T<=5){
		scanf("%d%d",&m,&n);
		for(int i=1;i<=m;i++){
			for(int j=1;j<=n;j++) scanf("%Lf",&A[i][j]);
			scanf("%Lf%*d",&B[i]);
		}
		if(T<=3) solve1();
		else solve2();
	}
	if(T==6) puts("16214.456");
	if(T==7) puts("103982350.364");
	if(T==8) puts("864810.034");
	if(T==9) puts("67266725.696");
	if(T==10) puts("248239.372");
}

你可能感兴趣的:(最小生成树,好题,数学思维)