codeforces每日一题16-30
目录:
16.91B.Queue(树状数组/线段树)
17.431C.K-Tree(树形DP+容斥)
18.1084D. The Fair Nut and the Best Path(树形DP)
19.1187C. Vasya And Array(构造)
20.768B. Code For 1(分治)
21.46D. Parking Lot(线段树区间合并)
22.1118F1. Tree Cutting (Easy Version)(DFS)
23.723D. Lakes in Berland(DFS)
24.1283D.Christmas Trees(BFS)
25.B. Kay and Snowflake(树的重心)
26.913C. Party Lemonade(二进制,DP)
27.727D. T-shirts Distribution(贪心)
28.698B. Fix a Tree(并查集)
29.731C. Socks(bfs)
30.30C. Shooting Gallery(概率DP)
91B.Queue(树状数组/线段树)
题意:
给一个序列,询问对于每个i后面离他最远的比且他小的位置
思路:
先离散化一下并且建立树状数组存储1-Ai 中坐标的最大值,然后从右到左遍历数组,每次先询问再加点
#include<iostream> #include<algorithm> #include<cstring> #define lowbit(x) (x&(-x)) using namespace std; const int maxn=1e5+10; int a[maxn],b[maxn],ans[maxn],n,c[maxn]; void add(int x,int val) { while(x<=n){ c[x]=max(c[x],val); x+=lowbit(x); } } int query(int x) { int ans=0; while(x>=1){ ans=max(ans,c[x]); x-=lowbit(x); } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+1+n); int len=unique(b+1,b+1+n)-b-1; for(int i=n;i>=1;i--){ int pos=lower_bound(b+1,b+1+len,a[i])-b; if(query(pos-1)==0) ans[i]=-1; else ans[i]=query(pos-1)-i-1; add(pos,i); } for(int i=1;i<=n;i++) cout<<ans[i]<<" "; return 0; }
431C.K-Tree(树形DP+容斥)
题意:
给一颗K叉树,父亲到儿子每条边的边权从左到右分别为1-k,问有多少条路径的权值和为n并且至少包含一条权值大于等于d的边
思路:
可以容斥一下,先求不含限制条件的路径和,再求路径只含[1,d-1]的路径和,相减一下即为答案
定义dp[i][j] 为走到第i层且路径和为j的方案数,那么状态转移方程就为dp[i][j]=∑dp[i-1][j-k] (k为路径权值)
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn=105; const int mod=1e9+7; ll dp1[maxn][maxn],dp2[maxn][maxn];//i行和为j的个数 int main() { int n,k,d; cin>>n>>k>>d; for(int i=1;i<=k;i++) dp1[1][i]=1; for(int i=2;i<=n;i++){//第2-n层 for(int j=1;j<=k;j++){//从长度为j的边转移而来 for(int k=j+1;k<=n;k++){//总长为K dp1[i][k]+=dp1[i-1][k-j]; dp1[i][k]%=mod; } } } for(int i=1;i<d;i++) dp2[1][i]=1; for(int i=2;i<=n;i++){//第2-n层 for(int j=1;j<d;j++){//从长度为j的边转移而来 for(int k=j+1;k<=n;k++){//总长为K dp2[i][k]+=dp2[i-1][k-j]; dp2[i][k]%=mod; } } } ll ans=0; for(int i=1;i<=n;i++){ ans+=dp1[i][n]-dp2[i][n]; ans%=mod; } cout<<(ans+mod)%mod<<endl; return 0; }
1084D. The Fair Nut and the Best Path(树形DP)
题意:
一棵树,每个点有点权与边权,,现在求一个路径,路径上点的权值和减去边的权值和最大
思路:
利用树形结构进行dp,初始化每个点的价值为自身价值,对于每个节点都判断更新它的值或者不更新,从底向上进行dfs递归,更新出最优ans,从一个节点到另一个节点的收益为(目标节点价值-路上消耗的价值)
#include<iostream> #include<algorithm> #include<vector> using namespace std; typedef long long ll; const int maxn=3e5+10; ll ans=0,dp[maxn],val[maxn]; vector<pair<int,ll> > a[maxn]; void dfs(int x,int fa) { ans=max(dp[x],ans); for(int i=0;i<a[x].size();i++){ int u=a[x][i].first; if(u==fa) continue; dfs(u,x); ll v=dp[u]-a[x][i].second; if(v<0) continue; ans=max(dp[x]+v,ans); dp[x]=max(dp[x],val[x]+v); } } int main() { ll n,f,t,v; scanf("%lld",&n); for(int i=1;i<=n;i++){ scanf("%lld",&val[i]); dp[i]=val[i]; } for(int i=1;i<=n-1;i++){ scanf("%lld%lld%lld",&f,&t,&v); a[f].push_back({t,v}); a[t].push_back({f,v}); } dfs(1,0); cout<<ans<<endl; }
1187C. Vasya And Array(构造)
题意:
给你m个条件,每个条件包含t,l,r,t=1时,l-r为一个不递减序列;t=0时,l-r不为不递减序列,要你还原序列,不能则输出NO
思路:
构造,我们定义一个flag数组,值为1则代表Ai ≥ Ai+1,对于t=1的要求,我们暴力打上标记
之后,检查每一个t=0的区间,如果区间内flag都为1,则输出NO
最后输出答案,先假设Ans1=n,如果flag为1,那么Ansi = Ansi-1,否则,Ansi =Ansi-1 -1
#include<iostream> #include<algorithm> using namespace std; const int maxn=2005; struct node{ int t,l,r; }a[maxn]; int flag[maxn]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d%d",&a[i].t,&a[i].l,&a[i].r); a[i].l--,a[i].r--; if(a[i].t==1){ for(int j=a[i].l;j<a[i].r;j++) flag[j]=1; } } bool judge; for(int i=1;i<=m;i++){ if(a[i].t==0){ judge=false; for(int j=a[i].l;j<a[i].r;j++){ if(flag[j]!=1) judge=true; } if(!judge){ cout<<"NO"<<endl; return 0; } } } int ans=n; cout<<"YES"<<endl<<n<<" "; for(int i=1;i<n;i++){ if(flag[i-1]) cout<<ans<<" "; else printf("%d ",--ans); } return 0; }
768B. Code For 1(分治)
题意:
给一个n每次分成n/2,n%2,n/2三部分替代在原来的位置上,知道n为1或0位置,询问最后l-r位置上的数字和
思路:
先求出整个区间长度,然后我们要解决L到R之间有多少个1的问题,不妨引入一个中值mid,每次解决[L,mid-1],[mid,mid],[mid+1,R],同时记录拆分到这一步时n的值,问题规模不断缩小,最后n变为1或0时,就可以直接返还答案了。总结出递归条件
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; ll N,L,R; ll dfs(ll n,ll l,ll r) { if(n==0||l>R||r<L||l>r) return 0; if(n==1) return 1; ll mid=(l+r)>>1; return dfs(n%2,mid,mid)+dfs(n/2,l,mid-1)+dfs(n/2,mid+1,r); } int main() { scanf("%lld%lld%lld",&N,&L,&R); ll n=N,len=1; while(n>1){ len=len*2+1; n/=2; } ll ans=dfs(N,1,len); cout<<ans<<endl; }
46D. Parking Lot(线段树区间合并)
题意:
有一个从0 - L-1停车场,每次有长度为len的车要停入停车场,如果不是停在头尾两端的话,停车与前车跟后车的距离也有要求,并且每次停车的位置要尽可能的靠前。还有可能有已经停好的车辆离开,每次停车返回车位的坐标
思路:
与POJ3667Hotel类似去线段树区间合并问题,线段树的节点存的是该区间空余的最大位置
这道题只需每辆车的长度变成b+f+车长,把整个停车场也加上b和f,那么就不用考虑空间了,直接做就行
#include<iostream> #include<algorithm> #include<cstdio> #include<vector> #define ls (rt<<1) #define rs (rt<<1|1) using namespace std; const int maxn=1e5+10; struct node{ int lsum,rsum,sum,cover; }hotel[maxn<<2]; int sl[maxn],sr[maxn]; struct Segment_Tree{ void pushUp(int rt,int m) { hotel[rt].lsum=hotel[ls].lsum; hotel[rt].rsum=hotel[rs].rsum; if(hotel[rt].lsum==m-(m>>1)) hotel[rt].lsum+=hotel[rs].lsum; if(hotel[rt].rsum==(m>>1)) hotel[rt].rsum+=hotel[ls].rsum; hotel[rt].sum=max(hotel[ls].rsum+hotel[rs].lsum,max(hotel[ls].sum,hotel[rs].sum)); } void pushDown(int rt,int len) { if(hotel[rt].cover!=-1){ hotel[rs].cover=hotel[ls].cover=hotel[rt].cover; hotel[ls].lsum=hotel[ls].rsum=hotel[ls].sum=hotel[ls].cover?0:len-(len>>1); hotel[rs].lsum=hotel[rs].rsum=hotel[rs].sum=hotel[rs].cover?0:(len>>1); hotel[rt].cover=-1; } } void build(int l,int r,int rt) { hotel[rt].lsum=hotel[rt].rsum=hotel[rt].sum=r-l+1; hotel[rt].cover=-1; if(l==r) return; int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); } void update(int L,int R,int C,int l,int r,int rt) { if(L<=l&&r<=R){ hotel[rt].cover=C; hotel[rt].sum=hotel[rt].lsum=hotel[rt].rsum=hotel[rt].cover?0:r-l+1; return; } int m=(l+r)>>1; pushDown(rt,r-l+1); if(L<=m) update(L,R,C,l,m,ls); if(R>m) update(L,R,C,m+1,r,rs); pushUp(rt,r-l+1); } int query(int w,int l,int r,int rt) { if(l==r) return l; pushDown(rt,r-l+1); int mid=(l+r)>>1; if(hotel[ls].sum>=w) return query(w,l,mid,ls); else if(hotel[ls].rsum+hotel[rs].lsum>=w) return mid-hotel[ls].rsum+1; return query(w,mid+1,r,rs); } }T; int main() { int L,b,f,m,n,op,len; scanf("%d%d%d%d",&L,&b,&f,&m); n=L+b+f-1; T.build(0,n,1); for(int t=1;t<=m;t++){ scanf("%d%d",&op,&len); if(op==1){ if(hotel[1].sum<len+b+f){ cout<<-1<<endl; continue; } int s=T.query(len+b+f,0,n,1); sl[t]=s+b; sr[t]=s+b+len-1; cout<<s<<endl; T.update(s+b,s+len+b-1,1,0,n,1); } else{ T.update(sl[len],sr[len],0,0,n,1); } } return 0; }
1118F1. Tree Cutting (Easy Version)(DFS)
题意:
一棵树,部分点被染成两种颜色,现在问你有多少条边删去后能将一颗树分成两颗各只包含一种不同颜色的树
思路:
dfs两遍,第一遍求出每个子树(包含根)包含每种颜色的多少,第二遍,枚举边看是否符合条件即可
#include<iostream> #include<algorithm> #include<vector> #include<cstdio> using namespace std; const int maxn=3e5+10; int ans=0,c[maxn],dp[maxn][3],num1,num2; vector<int> a[maxn]; void dfs(int x,int fa) { if(c[x]==1) dp[x][1]=1; if(c[x]==2) dp[x][2]=1; for(int i=0;i<a[x].size();i++){ int u=a[x][i]; if(fa==u) continue; dfs(u,x); dp[x][1]+=dp[u][1]; dp[x][2]+=dp[u][2]; } } void dfs1(int x,int fa) { for(int i=0;i<a[x].size();i++){ int u=a[x][i]; if(u==fa) continue; if(dp[u][1]==num1&&dp[u][2]==0) ans++; if(dp[u][2]==num2&&dp[u][1]==0) ans++; dfs1(u,x); } } int main() { int n,u,v; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&c[i]); if(c[i]==1) num1++; else if(c[i]==2) num2++; } for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } dfs(1,0); dfs1(1,0); cout<<ans<<endl; return 0; }
723D. Lakes in Berland(DFS)
题意:
给一张图,包含‘.’跟‘*’,只在矩形内部(有一部分边上的成为河)的.连通块成为湖,问要将最少多少个.变成*才能使图中只有k个湖,并且输出最后的图
思路:
先进行第一遍DFS,把河处理掉,然后进行第二次DFS,求出湖的个数每个湖的大小以及每个湖的位置(我是用结构体里再开二维数组),然后按湖的大小排序累加起要删去湖的大小就行了
#include<iostream> #include<algorithm> #include<cstring> #include<vector> using namespace std; const int maxn=60; char ma[maxn][maxn],ansm[maxn][maxn]; struct node{ char mp[maxn][maxn]; int cnt; }a[2500]; int n,m,k,cnt=0; int movex[4]={0,0,1,-1,}; int movey[4]={1,-1,0,0}; int cmp(node a,node b){ return a.cnt<b.cnt;} void dfs1(int x,int y) { int x1,y1; ma[x][y]='*'; for(int i=0;i<4;i++){ x1=x+movex[i]; y1=y+movey[i]; if(x1>1&&x1<n&&y1<m&&y1>1&&ma[x1][y1]=='.') dfs1(x1,y1); } return; } int dfs(int x,int y) { int ans=1,x1,y1; ma[x][y]='*'; a[cnt].mp[x][y]='*'; for(int i=0;i<4;i++){ x1=x+movex[i]; y1=y+movey[i]; if(x1>1&&x1<n&&y1<m&&y1>1&&ma[x1][y1]=='.') ans+=dfs(x1,y1); } return ans; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++){ cin>>(ma[i]+1); for(int j=1;j<=m;j++) ansm[i][j]=ma[i][j]; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(ma[i][j]=='.'&&(i==1||j==1||j==m||i==n)) dfs1(i,j); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(ma[i][j]=='.'){ a[cnt].cnt=dfs(i,j); cnt++; } } } sort(a,a+cnt,cmp); int ans=0; for(int i=0;i<cnt-k;i++) ans+=a[i].cnt; cout<<ans<<endl; for(int i=0;i<cnt-k;i++){ for(int j=1;j<=n;j++){ for(int k=1;k<=m;k++){ if(a[i].mp[j][k]=='*') ansm[j][k]='*'; } } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) cout<<ansm[i][j]; cout<<endl; } }
1283D.Christmas Trees(BFS)
题意:
有m个人,n颗圣诞树,现在给出n颗圣诞树的坐标,让你求出每个的坐标,使得每个人到离他最近的圣诞树的距离和最小
思路:
从n颗树左右的位置进行bfs每次都向两边扩展,用map记录该坐标是否已经有人或者圣诞树就好了
#include<iostream> #include<algorithm> #include<map> #include<vector> #include<queue> using namespace std; const int maxn=2e5+10; typedef long long ll; const int high=2e9,low=-2e9; ll n,m,ans,b[maxn]; map<int,int> s; vector<int> a; queue<int> q; void bfs() { int cnt=0; while(!q.empty()){ int x=q.front(); q.pop(); ans+=s[x]; a.push_back(x); cnt++; if(cnt==m) return; if(x+1<=high&&!s.count(x+1)){ s[x+1]=s[x]+1; q.push(x+1); } if(x-1>=low&&!s.count(x-1)){ s[x-1]=s[x]+1; q.push(x-1); } } } int main() { scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++){ scanf("%lld",&b[i]); s[b[i]]=1; } sort(b+1,b+1+n); for(int i=1;i<=n;i++){ int temp=b[i]; if(temp+1<=high&&!s.count(temp+1)){ s[temp+1]=1; q.push(temp+1); } if(temp-1>=low&&!s.count(temp-1)){ s[temp-1]=1; q.push(temp-1); } } bfs(); cout<<ans<<endl; for(int i=0;i<m;i++) cout<<a[i]<<" "; }
25.B. Kay and Snowflake(树的重心)
题意:
给你一棵树,让你求出以每个节点为根的子树的重心
思路:
每次找到x的最大子树v,以x为根的树的重心一定在以v为根的子树的重心与x的连线上新的重心一定在最大子树的重心到x的连线中
#include<iostream> #include<algorithm> #include<cstring> #include<vector> using namespace std; const int maxn=3e5+10; int fa[maxn],siz[maxn],ans[maxn]; vector<int> a[maxn]; void dfs(int x) { siz[x]=1; ans[x]=x; int mx=0; for(int i=0;i<a[x].size();i++){ int u=a[x][i]; dfs(u); siz[x]+=siz[u]; if(siz[u]>siz[mx]) mx=u; } if(2*siz[mx]>siz[x]){ int v=ans[mx]; while(2*(siz[x]-siz[v])>siz[x]) v=fa[v]; ans[x]=v; } } int main() { int n,m,temp; scanf("%d%d",&n,&m); for(int i=2;i<=n;i++){ scanf("%d",&fa[i]); a[fa[i]].push_back(i); } dfs(1); while(m--){ scanf("%d",&temp); cout<<ans[temp]<<endl; } }
913C. Party Lemonade(二进制,DP)
题意:
n瓶汽水,每瓶的容量为2i-1,价格为Ci,现在至少要买L升,问最少花费是多少
思路:
首先需要对价格进行一个预处理,如果val[i]*2<val[i+1],那么是没有意义的去购买第i+1种饮料
因此,在进行预处理之后,他的性价比呈现不递减的趋势,即后一种饮料的性价比大于等于前一种饮料的性价比
首先如果饮料的容量小于等于需求的容量,那么先购买几瓶,直道需求的量小于当前种类的饮料容量,为什么这么做?因为之前已经说过越是后面的性价比越高,尽可能的多买
此时当需求容量小于当前种类饮料容量的时候,就会有两种可能,要么在购买一瓶当前种类的饮料,要么购买比当前种类容量更小的那几种饮料
#include<iostream> #include<algorithm> #define inf 4e18+10; typedef long long ll; using namespace std; const int maxn=32; ll c[maxn]; int main() { int n,l; scanf("%d%d",&n,&l); for(int i=0;i<n;i++){ scanf("%lld",&c[i]); if(i!=0) c[i]=min(c[i-1]*2,c[i]); } ll sum=0,ans=inf; for(int i=n-1;i>=0;i--){ ll v=(1<<i); int n=l/v; sum+=n*c[i]; l%=v; ans=min(ans,sum+(l>0)*c[i]); } cout<<ans<<endl; return 0; }
727D. T-shirts Distribution(贪心)
题意:
有六种尺码的衣服每种衣服的数量都不一样,n个人,有的人只能穿一种尺码的衣服,有的人可以穿两种连续的尺码的衣服,问是否所有人都有合适的衣服穿
思路:
只能穿一种尺码的人直接将衣服领走,如果不够则直接输出NO
之后将要穿两种衣服的人按照小的那个尺码排序,如果有小号的衣服则优先选小号的,没有的则选大号的,遇到不符合的情况就退出
#include<iostream> #include<algorithm> #include<map> using namespace std; const int maxn=1e5+10; int a[7],c[7],n; map<string,int> s; string temp,x,y,ans[maxn]; struct p{ string ans,x,y; int flag,pos; }b[maxn]; int cmp1(p a,p b){ if(a.flag==b.flag){ return s[a.x]<s[b.x]; } return a.flag>b.flag; } int cmp2(p a,p b){return a.pos<b.pos;} void read() { for(int i=1;i<=6;i++) scanf("%d",&a[i]); scanf("%d",&n); for(int j=1;j<=n;j++){ cin>>temp; int pos=-1,flag=0; for(int i=0;i<temp.size();i++){ if(temp[i]==','){ pos=i; flag=1; break; } } b[j].pos=j; if(!flag) a[s[temp]]--,b[j].ans=temp; else{ x=temp.substr(0,pos); y=temp.substr(pos+1); b[j].x=x,b[j].y=y; b[j].flag=2; } } } int main() { s["S"]=1,s["M"]=2,s["L"]=3,s["XL"]=4,s["XXL"]=5,s["XXXL"]=6; read(); sort(b+1,b+1+n,cmp1); for(int i=1;i<=6;i++){ if(a[i]<0){ cout<<"NO"<<endl; return 0; } } for(int i=1;i<=n;i++){ if(b[i].flag!=2) continue; if(a[s[b[i].x]]-1>=0){ b[i].ans=b[i].x; a[s[b[i].x]]--; } else{ if(a[s[b[i].y]]-1>=0){ b[i].ans=b[i].y; a[s[b[i].y]]--; } else{ cout<<"NO"; return 0; } } } sort(b+1,b+1+n,cmp2); cout<<"YES"<<endl; for(int i=1;i<=n;i++) cout<<b[i].ans<<endl; return 0; }
698B. Fix a Tree(并查集拆环)
题意:
给一个数组,Ai对应i的父亲,一开始数组表示的不是一颗完整的数,现在要你最小次数改变Ai,使得其变成一个完整的树
思路:
不能构成一颗完整的树可能出现的情况有:
1.有多个根,解决的方法很简单,直接将其指向你所认定的答案的根就好了
2.出现环,这个的解决方法就是利用并查集判环(学到了新姿势,利用并查集判无向图的环),然后就其破坏掉,也就是将其中一个节点指向根就行了
#include<iostream> #include<algorithm> using namespace std; const int maxn=2e5+10; int fa[maxn],a[maxn]; int find(int x){return (x==fa[x])?x:(fa[x]=find(fa[x]));} void uni(int x,int y) { int f1=find(x),f2=find(y); if(f1!=f2) fa[f1]=f2; } int main() { int n,root=-1,ans=0; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); fa[i]=i; if(i==a[i]) root=i; } for(int i=1;i<=n;i++){ if(i==a[i]&&i!=root){ a[i]=root; uni(i,root); ans++; } else if(a[i]!=i){ if(find(i)==find(a[i])){ ans++; if(root==-1) root=i; a[i]=root; uni(i,root); } else uni(i,a[i]); } } cout<<ans<<endl; for(int i=1;i<=n;i++) cout<<a[i]<<" "; return 0; }
731C. Socks(bfs)
题意:
有n双袜子,每个袜子都有不同的颜色,现在按照要求每天要调出两只袜子,有可能颜色不同,现在问你要最少改变多少只袜子的颜色才能使每天袜子的颜色都相同
思路:
在每天要求的两双袜子间连一条边,所以在一个连通块内的所有袜子颜色应该是相同的
对于每个未访问到的点进行bfs遍历,每次遍历统计出连通块内个数最多的颜色,该连通块对答案的贡献就为,连通块大小减去个数最多的颜色的数量
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<queue> using namespace std; const int maxn=2e5+10; int vis[maxn],c[maxn]; vector<int> a[maxn]; int bfs(int x) { int cnt=0; queue<int> q; map<int,int> m; vis[x]=1; q.push(x); while(!q.empty()){ cnt++; int u=q.front(); q.pop(); m[c[u]]++; for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(vis[v]) continue; vis[v]=1; q.push(v); } } int mx=0; for(map<int,int> ::iterator it=m.begin();it!=m.end();it++) if(it->second>mx) mx=it->second; return cnt-mx; } int main() { int n,m,k,u,v,ans=0; scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) scanf("%d",&c[i]); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } for(int i=1;i<=n;i++) if(!vis[i]) ans+=bfs(i); cout<<ans; return 0; }
30C. Shooting Gallery(概率DP)
题意:
在t时候会在(xi,yi)位置出现一个靶子,且击中的可行性为Pi,一开始你可以选择任意坐标,之后每移动1距离需要花费1单位时间,现在问你能击中的最大期望是多少
思路:
先将所有靶子按出现的时间排序,然后对于每个靶子遍历之前出现的所有靶子看看能否转移即可
#include<iostream> #include<algorithm> #include<cmath> using namespace std; const int maxn=1e3+10; typedef long long ll; struct node{ int x,y,t; double p; }p[maxn]; bool cmp(node x,node y){return x.t<y.t;} double dp[maxn]; ll pow(ll x){return x*x;} bool judge(int x,int y){ return pow(p[x].t-p[y].t)>=pow(p[x].x-p[y].x)+pow(p[x].y-p[y].y); } int main() { int n; double ans=0; scanf("%d",&n); for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y>>p[i].t>>p[i].p; sort(p+1,p+1+n,cmp); ans=dp[1]=p[1].p; for(int i=2;i<=n;i++){ dp[i]=p[i].p; for(int j=1;j<i;j++){ if(judge(i,j)) dp[i]=max(dp[i],dp[j]+p[i].p); } ans=max(ans,dp[i]); } printf("%.10f",ans); return 0; }