NOIP模拟16:「Star Way To Heaven·God Knows·Loost My Music」
T1:Star Way To Heaven
基本思路:
最小生成树。
假如我们将上边界与下边界看作一个点,然后从上边界经过星星向下边界连边,会发现,他会形成一条线将整个矩形分为左右两个部分。
并且很明显从左边界走到右边界一定会经过这条线的某一部分。
为了与星星和上下边界尽可能远,我们直接走两点连边的中点,这应该很好理解。
他要求最小距离,所以我们可以跑最小生成树。
然后在最小生成树的边里拣大的输出他的\(1/2\)即可。
我们将所有星星连边,并与上下边界连边,与上下边界连边的权值是\(y\)以及\(m-y\)。
然后,我们还面临一个问题,就是我们生成的是树,对于一个完全图来说,这棵树不可能退化成链,就是说,他至少有两支,而我们要的是这棵树上最大权值最小的那一支上的最大值,总不可能求出树后在\(DFS\)吧。
考虑最小生成树的原理:贪心。
他每次都会选权值最小加入生成树,那么当代表下界的点第一次被加入生成树时直接return即可,之前得到的那一支就是我们要的,记得在之前不断对边取max即可。
最后输出时记得除以2.
代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
const int SIZEK=6006;
int k;
double n,m;
struct Star{double x,y;}star[SIZEK];
bool operator<(Star a_,Star b_)
{
if(a_.x==b_.x) return a_.y<b_.y;
return a_.x<b_.x;
}
double ans=-999999;
int read()
{
rr int x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read*10)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
struct edge{int f,t;double w;}a[SIZEK*SIZEK/2];
bool operator<(const edge a_,const edge b_){return a_.w<b_.w;}
int cnt;
double len(int i,int j)
{
double l;
l=(star[i].x-star[j].x)*(star[i].x-star[j].x);
l+=(star[i].y-star[j].y)*(star[i].y-star[j].y);
l=sqrt(l);
return l;
}
int f[SIZEK];
int find(int x)
{
if(f[x]==x)
return x;
return f[x]=find(f[x]);
}
void join(int x,int y)
{
if(f[x]!=f[y])
f[f[y]]=f[x];
}
};
using namespace STD;
int main()
{
int x=scanf("%lf%lf",&n,&m);
k=read();
for(rr int i=1;i<=k;i++) x=scanf("%lf%lf",&star[i].x,&star[i].y),f[i]=i;
f[k+1]=k+1,f[k+2]=k+2;
//最近对优化连边
sort(star+1,star+1+k);
for(rr int i=1;i<=k;i++)
for(rr int j=i+1;j<=min(k,i+500);j++)
{
a[++cnt].f=i;
a[cnt].t=j;
a[cnt].w=len(i,j);
}
//
for(rr int i=1;i<=k;i++)
{
a[++cnt].f=i;
a[cnt].t=k+1;
a[cnt].w=star[i].y;
a[++cnt].f=i;
a[cnt].t=k+2;
a[cnt].w=m-star[i].y;
}
sort(a+1,a+1+cnt);
for(rr int i=1;i<=cnt;i++)
{
if(find(k+1)==find(k+2)) break;
int f=a[i].f;
int t=a[i].t;
if(find(f)!=find(t))
{
join(f,t);
ans=max(ans,a[i].w);
}
}
printf("%.9lf",ans/2.00);
}
改题途中:
正解用的是Prim,因为这样的话就不用把边真正建出来了,但是我更熟悉kruscal。所以用的kruscal。但是MLE了。
后来战神教我用点对减少连边我才A的。
这也从一个侧面印证了kruscal在稠密图上的局限性。
T2:God Knows
基本思路:
数据结构优化DP
我们可以发现,与边\((i,p[i])\)相交的边\((j,p[j])\)一定满足:
i<j&&p[i]>p[j] 或 i>j&&p[i]<p[j]
如果您急于看正解,接下来这一段可以直接跳过
先说一个考场上想的稀奇思路:
我们可以给所有相交的的边的i与j连边,然后问题转化成了以最小的代价将整个图里的边盖。
为什么这个思路是值得考虑的呢?
我们可以发现,我们发现,将图中的边删去只需要将与他相交的边删去或将它上的点删去即可。
这很像将图中边覆盖时至少要将他的一个端点选中。
所以,这个思路值得考虑。
但是他在这个题中实际上是不可行的。因为图的边完全覆盖是NP问题。。。。。
就是下面链接里提到的广播问题。。。。。。。
不知道啥是NP问题??戳这里
总之1s内是解决不了了。
但是假如图退化为树,就可以树规了。
这个思路还有一个可能的实现就是将建出的图中的点改为边,边改为点,然后跑最小欧拉路。
至于建边,归并排序的逆序对算法也许可以做到\(O(nlogn)\)实现。
至于是否真的可以,作者没有精力实现。
留给读者思考。
如果您将他证伪或解除,请您告诉我。
这个思路只是作为一个启发提出,希望也许可以得到一些其他的灵感。
然后说正解。
第一行已经说了,数据结构优化DP。
我们可以发现,这道题与线性DP中的有好城市问题类似。
传送门:友好城市
我们观察可以发现答案的边一定是不相交的,那么他们的端点一定成上升序列。
因为只有边没有被删去的点才会有贡献。
所以相交的边的点一定只有其一做出贡献。
我们可以求出图里所有的极长上升子序列。
然后取他们c的和的最小值,即可。
复杂度\(O(n^2)\)。
考虑优化。
用线段树维护p的范围,线段树里插入的是i。
那么我们每次在线段树里查询1->p[i]-1即可。
还要考虑相交的线都可能对同一点作出贡献。所以我们要循环查询p[j]->p[i]-1,将j更新为查询结果。
因为我是从小到大插入的i所以查询到的结果一定与之前的j相交或平行,而平行的话也是最大i,所以一定可以贡献。
上代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
#define inf INT_MAX
const int SIZE=2e5+4;
int n,ans=inf;
int p[SIZE],cost[SIZE];
int c[SIZE];
int read()
{
rr int x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read*10)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}
bool vis[SIZE];
class Line_tree
{
private:
#define lc id<<1
#define rc id<<1|1
int maxn[SIZE<<2];
public:
Line_tree(){}
void insert(int id,int l,int r,int pos,int v)
{
// if(id>=(SIZE<<2))
if(l==r){maxn[id]=v;return;}
int mid=(l+r)>>1;
if(pos<=mid) insert(lc,l,mid,pos,v);
else insert(rc,mid+1,r,pos,v);
maxn[id]=max(maxn[lc],maxn[rc]);
}
int query(int id,int l,int r,int st,int en)
{
if(st<=l&&r<=en) return maxn[id];
int mid=(l+r)>>1;
rr int ret=-inf;
if(st<=mid) ret=max(ret,query(lc,l,mid,st,en));
if(mid<en) ret=max(ret,query(rc,mid+1,r,st,en));
return ret;
}
}t;
};
using namespace STD;
int main()
{
n=read();
for(rr int i=1;i<=n;i++) p[i]=read();
for(rr int i=1;i<=n;i++)
{
cost[i]=read();
c[i]=inf;
}
int id=0;
for(rr int i=1;i<=n;i++)
{
id=0;
if(p[i]-1>=1)
id=t.query(1,1,n,1,p[i]-1);
while(id)
{
c[i]=min(c[i],c[id]+cost[i]);
vis[id]=1;
if(p[id]+1>p[i]-1)
break;
id=t.query(1,1,n,p[id]+1,p[i]-1);
}
if(c[i]==inf) c[i]=cost[i];
t.insert(1,1,n,p[i],i);
}
for(rr int i=1;i<=n;i++)
if(!vis[i])
ans=min(ans,c[i]);
printf("%d",ans);
}
最后在解释下为什么是极大上升序列吧,因为他可以保证平行,并且使得每一组相交线里都会有一条直线被选中
T3:Loost My Music
斜率优化DP·可持久化栈(主席树)·二分·凸包
将这个题的式子取反。会得到:
\[-\frac{c_{u}-c_{v}}{depth_{u}-depth_{v}} \]假如我们以c为y轴,depth为x轴,会发现,这其实是相邻两点连线的斜率取反。
因此,我们可以考虑用单调栈维护下凸包。然后将栈顶与当前值计算即可。
注意当回溯后要继续向下搜,所以要恢复栈状态,因此考虑可持久化栈。
上代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
const int SIZE=5e5+4;
int n;
int top[SIZE];
int fa[SIZE],to[SIZE];
int dire[SIZE],head[SIZE];
double val[SIZE],depth[SIZE];
double ans[SIZE];
inline void add(int f,int t)
{
static int num1=0;
to[++num1]=t;
dire[num1]=head[f];
head[f]=num1;
}
namespace Prisident_tree
{
struct node
{
int id;
node *l,*r;
}root[SIZE];
void build(node &now,int l,int r)
{
if(l==r) {if(l==1) now.id=1;return;}
rr int mid=(l+r)>>1;
now.l=new node;
now.r=new node;
build(*now.l,l,mid),build(*now.r,mid+1,r);
}
void insert(node &before ,node &now,int l,int r,int pos,int id)
{
if(l==r){now.id=id;return;}
rr int mid=(l+r)>>1;
if(pos<=mid)
{
now.r=before.r;
now.l=new node;
insert(*before.l,*now.l,l,mid,pos,id);
}
else
{
now.l=before.l;
now.r=new node;
insert(*before.r,*now.r,mid+1,r,pos,id);
}
}
int query(rr node now,rr int l,rr int r,rr int pos)
{
if(l==r) return now.id;
rr int mid=(l+r)>>1;
if(pos<=mid)
return query(*now.l,l,mid,pos);
else
return query(*now.r,mid+1,r,pos);
}
};
using namespace Prisident_tree;
int read()
{
rr int x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read*10)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
inline double rate(rr int id1,rr int id2){return (val[id1]-val[id2])/(depth[id1]-depth[id2]);}
int find(int f,int now)
{
int l=1,r=top[f];
rr double a1,a2;
while(l<r)
{
rr int mid=(l+r)>>1;
rr int id1=query(root[f],1,n,mid);
rr int id2=query(root[f],1,n,mid+1);
a1=rate(id2,id1);
a2=rate(now,id2);
if(a1>=a2) r=mid;
else l=mid+1;
}
return l;
}
void dfs(int now)
{
if(depth[now]==1.00)
{
ans[now]=rate(now,1);
insert(root[fa[now]],root[now],1,n,2,now);
top[now]=2;
}
else
{
if(now!=1)
{
int pos=find(fa[now],now);
top[now]=pos+1;
int id=query(root[fa[now]],1,n,pos);
ans[now]=rate(now,id);
insert(root[fa[now]],root[now],1,n,top[now],now);
}
}
for(rr int i=head[now];i;i=dire[i])
{
depth[to[i]]=depth[now]+1.00;
dfs(to[i]);
}
}
};
using namespace STD;
int main()
{
n=read();
for(rr int i=1;i<=n;i++) int x=scanf("%lf",val+i);
for(rr int i=2;i<=n;i++) fa[i]=read(),add(fa[i],i);
build(root[1],1,n);
dfs(1);
for(rr int i=2;i<=n;i++) printf("%.10lf\n",-ans[i]);
}
2021.7.16 现役