T1:
题解:
将
[
i
,
i
+
s
i
z
−
1
]
[i,i+siz-1]
[i,i+siz−1]取反,差分一下,可以看成在
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(22k∗2k)时间完成。
Code:
#include<bits/stdc++.h>
#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+10−6之内也是最小的。
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。
n≤50,−150≤x,y,z≤150,−100≤vx,vy,vz≤100。
题解:
因为
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<bits/stdc++.h>
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:
题解:
当 n = 2 , a k 1 = 1 n=2,a_{k1}=1 n=2,ak1=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)|}
∑∣yi−y0−k(xi−x0)∣。这样就只剩下
k
k
k一个变量,对于其中的一个绝对值函数显然只会有一个拐点
y
i
−
y
0
x
i
−
x
0
y_i-y_0\over x_i-x_0
xi−x0yi−y0且在两边都是一次函数,所以整个式子可以在某个拐点处取得最值,所以我们就证明了,这条直线一定可以经过 2 个点。
m
3
m^3
m3暴力显然。优化的话,考虑枚举一个确定的点,然后对绝对值函数的变化的时刻排序,一个函数要么是
Y
−
k
∗
X
Y-k*X
Y−k∗X的形式,要么是
k
∗
X
−
Y
k*X-Y
k∗X−Y的形式,记下
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
Code:
#include<bits/stdc++.h>
#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");
}