T1.Climb
题目大意:给你一棵树,每条边有一个长度,另外有一些节点可以直接到达深度小于等于它的节点,长度计算为(left|dep_u-dep_v
ight| imes k)。求每一个点到根节点的最短路径。
考场上思路还是比较清晰的,就是题目没讲清是否可以通过边向子节点走。大概写了一个小时,又调了半小时代码,最后还是A掉了。
还是讲讲解法吧,这题可以用到一些动态规划的思想,因为这个节点可以到比它深度小或者等于的节点而不用考虑是否为它的祖先节点,所以要一层一层转移,BFS就变成了一个很好的选择,容易维护比这个点深度小的最优节点。转移也比较简单,因为不用考虑向子节点走,所以部分转移方程为(f[u]=f[v]+val_{uv}),再就是考虑不走树上的边直接转移了。
因为每下一层,所有遍历过的节点到的这一层的代价又要加上一个(k),所以转移决策点是不会变的,即最小的那个还是最小,只需要再跟新加入的一层进行比较就好了。
最后要注意的一点是可以通过本层的节点转移过来,所以有一些没有被遍历过的节点可能会成为决策点,用一个数组保存这一层的信息就好了,总的时间复杂度为(O(n))。
小结:
解题关键:想到决策点不变和同层可以转移(我就是第二个开始没有考虑导致Bug满天飞
芝士点:动态规划
#include<bits/stdc++.h>
#define re register
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
inline int read()
{
re int x=0,f=1;
re char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=500005;
struct edge{int v,net,val;}e[N];
int dep[N],n,cnt,hd[N],d,st[N],top,last;
long long mi,f[N],k;
int up[N];
inline void add(int u,int v,int val){e[++cnt].v=v,e[cnt].net=hd[u],e[cnt].val=val,hd[u]=cnt;}
queue <int> q;
inline void first()
{
for(;!q.empty();)q.pop();
memset(f,63,sizeof(f));
f[1]=0,dep[1]=1;q.push(1),d=1;
for(re int u;!q.empty();)
{
u=q.front(),q.pop();
for(re int v,i=hd[u];i;i=e[i].net)
{
v=e[i].v;
if(dep[v])continue;
dep[v]=dep[u]+1;
if(dep[v]>last)
{
for(re int j=1;j<=top;j++)
if(up[st[j]]!=1)f[st[j]]=min(f[st[j]],mi+(dep[st[j]]-d)*k);
top=0,last=dep[v];
}
f[v]=min(f[v],f[u]+e[i].val);
if(up[v]!=1)f[v]=min(f[v],mi+(dep[v]-d)*k);
if(mi+(dep[v]-d)*k>f[v])mi=f[v],d=dep[v];
st[++top]=v;
q.push(v);
}
}
}
int main()
{
n=read(),k+=read();
for(re int i=2,v,val,whe;i<=n;i++)
{
scanf("%d%d%d",&v,&val,&up[i]);
add(v,i,val);
}
first();
for(re int i=1;i<=n;i++)printf("%lld
",f[i]);
return 0;
}
T2.H
好寒酸的题目
题目大意:给出一个数轴上的(n)个点,每个点有一个速度,求拿走(k)个点时,不相撞的最大时间。
这题部分分还是给的很足的,有(60\%)的数据(k=0),同时还有(90\%)的数据(nle 1000),打模拟赛时没时间了,打了一个(O(n^3))的算法,都没来得及考虑优化。
先讲讲(60\%)的数据吧,还是很简单的,因为(k=0),所以暴力枚举两个节点之间碰撞的最小时间,(60)就拿到了。但我写的是动态规划,我猜(90)分要加一些优化之类的能拿到。
直接上正解吧,也挺简单的,用一个很多题都适用的方法,二分答案就可以完成。一开始给所有点按位置排一个序,二分一个时间后,计算每个点运动这段时间后每个点会在哪里。如果一个节点的下标小于另一个,但运动后的位置大于另一个节点运动后的位置,说明它俩会相遇,也就是要删除一个。对整体来看的话,也就是删除一些节点后,使整个序列单调上升。所以最长上升子序列就是答案,所以(O(nlogn))求最长上升子序列的长度。加上二分答案的(O(logn))时间复杂度,所以整体也就是(O(nlog^2n))的时间复杂度,可以AC。
小结:
解题关键:想到二分答案即最长上升子序列与这题的关系。
芝士点:二分答案,动态规划
#include<bits/stdc++.h>
#define re register
#define eps 1e-8
using namespace std;
inline int read()
{
re int x=0,f=1;
re char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=500005;
struct node{int p,v;}e[N];
int n,k,len=0;
double b,f[N];
inline bool cmp(node x,node y){return x.p<y.p;}
inline int find(double x)
{
re int l=1,r=len;
for(re int mid=(l+r)>>1;l<r;mid=(l+r)>>1)
{
if(x>f[mid])l=mid+1;
else r=mid;
}
if(x>f[l]&&l>=len) return ++len;
return l;
}
inline bool check(double x)
{
memset(f,-63,sizeof(f)),len=0;
for(re int i=1,y;i<=n;i++)
{
b=e[i].p+x*e[i].v;
y=find(b);
f[y]=b;
}
return n-len<=k;
}
int main()
{
n=read(),k=read();
for(re int i=1;i<=n;i++)e[i].p=read(),e[i].v=read();
sort(e+1,e+n+1,cmp);
re double l=0.0,r=200000.0;
for(re double mid=(l+r)/2;abs(r-l)>eps;mid=(l+r)/2)
{
if(check(mid))l=mid;
else r=mid;
}
if(abs(l-200000.0)<=eps)puts("Forever");
else printf("%.4lf",l);
return 0;
}
T3.Pancake
题目大意:给你一些数,你可以把其中一个数分成两个,使这两个数相加等于开始的那个数,总共可以分(k)次,现在有(q)次操作,每一次都是其中一个:
求每此操作完后所以数的平方和。
考场时间不够,就打了(25)分暴力,算法也还有缺陷,但一下午连题解都没看懂,只能咕咕咕啦~~
为了好看,贴一个(O(qk))的算法代码@_@
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=105005;
int n,T,k,a[N],ans;
priority_queue<int>q;
int main()
{
scanf("%d%d%d",&n,&T,&k);
for(re int i=1;i<=n;i++)scanf("%d",&a[i]);
for(re int i=1;i<=n;i++)q.push(a[i]);
for(re int i=1,u;i<=k;i++)
{
u=q.top(),q.pop();
if(u&1)q.push(u/2),q.push(u/2+1);
else q.push(u/2),q.push(u/2);
}
for(re int u;!q.empty();)
{
u=q.top();q.pop();
ans+=u*u;
}
printf("%d
",ans);
for(re int type,x;T--;)
{
scanf("%d",&type);
if(type==1)k++;
else if(type==2)k--;
else
{
scanf("%d",&x);
a[++n]=x;
}
for(re int i=1;i<=n;i++)q.push(a[i]);
for(re int i=1,u;i<=k;i++)
{
u=q.top(),q.pop();
if(u&1)q.push(u/2),q.push(u/2+1);
else q.push(u/2),q.push(u/2);
}
ans=0;
for(re int u;!q.empty();)
{
u=q.top();q.pop();
ans+=u*u;
}
printf("%d
",ans);
}
return 0;
}