$5.30\ NOI $模拟
高三大哥最后一次模拟考了,祝他们好运
\(T1\)装箱游戏
显然可以将四种字母之间的空缺当做状态枚举
那么这道题就很显然了
#include<bits/stdc++.h> #define MAXN 305 using namespace std; int n; double f[4][MAXN][MAXN][MAXN],a,b,c,d; bool fl[4][MAXN][MAXN][MAXN]; double dp(int op,int x,int y,int z) { if(x<0||y<0||z<0) return 0; if(x==0&&y==0&&z==0) return 1; if(fl[op][x][y][z]!=0) return f[op][x][y][z]; fl[op][x][y][z]=1; //op记录的是目前是第几种转移的 //op==0 只有AD //op==1 ABD //op==2 ACD //op==3 ABCD //我们把x,y,z分别表示第几个空缺会比较好 if(op==0) { double s1=dp(op,x-1,y,z); double s2=0,s3=0,s4; for(int i=0;i<x;i++) { s2=max(s2,dp(1,i,x-i-1,0)); s3=max(s3,dp(2,i,x-i-1,0)); } s4=dp(op,x-1,y,z); f[op][x][y][z]=s1*a+s2*b+s3*c+s4*d; } else if(op==1) { double s1=dp(op,x-1,y,z); double s2=max(dp(op,x-1,y,z),dp(op,x,y-1,z)); double s3=0; double s4=dp(op,x,y-1,z); for(int i=1;i<=y;i++) { s3=max(s3,dp(3,x,y-i,i-1)); } f[op][x][y][z]=s1*a+s2*b+s3*c+s4*d; } else if(op==2) { double s1=dp(op,x-1,y,z); double s2=0; double s3=max(dp(op,x-1,y,z),dp(op,x,y-1,z)); double s4=dp(op,x,y-1,z); for(int i=1;i<=x;i++) { s2=max(s2,dp(3,i-1,x-i,y)); } f[op][x][y][z]=s1*a+s2*b+s3*c+s4*d; } else { double s1=dp(op,x-1,y,z); double s2=max(dp(op,x-1,y,z),dp(op,x,y-1,z)); double s3=max(dp(op,x,y-1,z),dp(op,x,y,z-1)); double s4=dp(op,x,y,z-1); f[op][x][y][z]=s1*a+s2*b+s3*c+s4*d; } return f[op][x][y][z]; } int main() { scanf("%lld%lf%lf%lf%lf",&n,&a,&b,&c,&d); a/=100.0;b/=100.0;c/=100.0;d/=100.0; printf("%.6f\n",dp(0,n,0,0)); }
\(T2\)库图鲁
考场上猜到了结论,最后的策略必然是走一段路然后到了一个终点停止,证明比较显然
然后暴力可以枚举最终点然后模拟,可获得\(30pts\)
发现不虑中间移动的过程,如果这个\(monster\)可以达到最终点,那么就需要统计贡献,否则不需要
证明的话:考虑相遇的充要条件,同一时刻位于同一点,我们既然保证了相遇,那么我们就可以同步移动到终点,那么相遇问题可以转化为到达问题
问题成功转化为,树上其他点到树上一个点的贡献和,然后可以使用点分治
首先\(u\neq v,val_{v\rightarrow u}=a_v(\lfloor\frac{h-max(d,1)}{k}\rfloor+1)\)
就是考虑有多少个完整的轮数可以到达,\(h\)是总时间,减去距离证明在这一段时间可以到,然后判断有多少个起点即可
比较套路的,考虑\(d_{u\rightarrow v}=dep_u+dep_c-2\times dep_{lca}\)
假设\(lca_{u,v}=rt,rt\)为当前分治位于的根
\(val_{v\rightarrow u}=a_v(\lfloor\frac{h-(dep_u+dep_v-2\times lca)}{k}\rfloor+1)\)
\(val_{v\rightarrow u}=a_v(\lfloor\frac{h-(dep_u+dep_v)}{k}-\frac{2\times dep_{lca}}{k}\rfloor+1),dep_{lca}=0 ...\)
\(val_{v\rightarrow u}=a_v(\lfloor\frac{h-(dep_u+dep_v)}{k}\rfloor+1)\)
然后发现这个向下取整很寄
然后继续拆
\(val_{v\rightarrow u}=a_v(\lfloor\frac{(h-dep_u)-dep_v}{k}\rfloor+1)\)
考虑我们这个东西,维护一下模数相等的部分乘一下就好了
我们需要先把子树内的删掉,然后计算外面的贡献,很神仙
然后贡献的话,可以考虑全部插入一个线段树里面,然后最后查询一遍即可
#include<bits/stdc++.h> #define int long long #define mid ((l+r)>>1) #define P make_pair using namespace std; int n,k,h,q[1000001],val[100001],res=100000000000000000ll; int ans[1000001]; vector<int>road[1000001]; int vis[1000001],siz[1000001],maxn[1000001],root; int read(){ int a=0,b=1; char ch=getchar(); while((ch<'0'||ch>'9')&&(ch!='-')){ ch=getchar(); } if(ch=='-'){ b=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ a=a*10+ch-'0'; ch=getchar(); } return a*b; } struct tree{ int rt,tr[2000001],son[2000001][2],cnt,num[2000001]; void clear() { for(int i=1;i<=cnt;++i) tr[i]=son[i][0]=son[i][1]=num[i]=0; cnt=rt=0; return ; } void add(int &x,int l,int r,int p,int v) { if(r<p||p<l) return ; if(!x) x=++cnt; num[x]+=v,tr[x]+=p*v; if(l==r) return; add(son[x][0],l,mid,p,v); add(son[x][1],mid+1,r,p,v); } int query(int x,int l,int r,int v) { if(l>v) return num[x]*(v+k)-tr[x]; if(r<=v) return num[x]*v-tr[x]; return query(son[x][0],l,mid,v)+query(son[x][1],mid+1,r,v); } }T; void get_root(int x,int f,int alls) { siz[x]=1; maxn[x]=0; for(int i=0;i<road[x].size();++i) { if(road[x][i]==f||vis[road[x][i]]) continue; get_root(road[x][i],x,alls); maxn[x]=max(maxn[x],siz[road[x][i]]); siz[x]+=siz[road[x][i]]; } maxn[x]=max(maxn[x],alls-siz[x]); if(maxn[x]<=maxn[root]) root=x; } int poi[1000001],lons[1000001],son[1000001],cnt; pair<int,int> ques[1000001]; void find_roads(int x,int f,int v,int sy) { ++cnt; poi[cnt]=x;lons[x]=v;//深度 ques[cnt]=P(h-lons[x],x);//我们这个询问要询问 //维护式子的前一半,(h-dep[x])-dep[y] for(int i=0;i<road[x].size();++i) { if(road[x][i]==f||vis[road[x][i]]) continue; find_roads(road[x][i],x,v+1,sy); } } bool comp(int a,int b) { return lons[a]<lons[b]; } void get_ans(int x) { cnt=0; poi[++cnt]=x; //把能到的点全部扫一遍 //我们目前统计的都是跨过根的贡献 lons[x]=0;//dep ques[cnt]=P(h-lons[x],x); //询问 for(int i=0;i<road[x].size();++i) { if(!vis[road[x][i]]) { int lst=cnt; //找到这个子树的范围 find_roads(road[x][i],x,1,road[x][i]); sort(poi+lst+1,poi+cnt+1,comp); sort(ques+lst+1,ques+cnt+1); T.clear(); int posr=lst+1,posl=lst+1,sum=0,valsum=0; for(int j=lst+1;j<=cnt;++j) { int q=ques[j].first; if(q<0) continue; while(posr<=cnt&&lons[poi[posr]]<=q) { T.add(T.rt,0,k,lons[poi[posr]]%k,val[poi[posr]]); sum+=-lons[poi[posr]]*val[poi[posr]]; valsum+=val[poi[posr]]; ++posr; } ans[ques[j].second]-=((q*valsum+sum-T.query(T.rt,0,k,q%k))/k+valsum); } } } T.clear(); sort(poi+1,poi+1+cnt,comp); sort(ques+1,ques+cnt+1); int posr=1,posl=1,sum=0,valsum=0; for(int j=1;j<=cnt;++j) { int q=ques[j].first; if(q<0) continue; while(posr<=cnt&&lons[poi[posr]]<=q) { T.add(T.rt,0,k,lons[poi[posr]]%k,val[poi[posr]]); sum+=-lons[poi[posr]]*val[poi[posr]]; valsum+=val[poi[posr]]; ++posr; } ans[ques[j].second]+=((q*valsum+sum-T.query(T.rt,0,k,q%k))/k+valsum); } } void solve(int x) { vis[x]=true; get_ans(x); for(int i=0;i<road[x].size();++i) { if(!vis[road[x][i]]) { root=0; get_root(road[x][i],x,siz[road[x][i]]); solve(root); } } } void dfs_ans(int x,int f,int v) { if(v>h) return ; for(int i=0;i<road[x].size();++i) if(road[x][i]!=f)dfs_ans(road[x][i],x,v+1); if(h%k==0) ans[x]-=val[x]; res=min(res,ans[x]); } signed main() { maxn[0]=1000000; n=read(),k=read(),h=read(); for(int i=1;i<=n;++i) val[i]=read(); for(int i=1,u,v,w;i<n;++i) { u=read(),v=read(); road[u].push_back(v); road[v].push_back(u); } get_root(1,0,n);//easily solve(root); dfs_ans(1,1,0); cout<<res; }
\(T3\)树
前两问随便线段树维护一下就好了
第三个操作基于一个贪心
我们要求区间的连续两个数绝对值不超过\(k\)
我们要对于点\(i\)
\(a_i+k>=a_{i+1},a_i+k>=a_{i-1}\)
我们对于每个条件从一侧往另一侧扫一遍贪心更改即可,复杂度\(O(nm)\)
考虑优化这个过程
考虑维护差分数组,我们第二个操作最多只会增加两个需改变的点,但每次三操作会减少,我们这个东西有势能保护
我们三操作最后变成了区间赋等差数列即可,范围可以使用二分轻松解决