NOIP模拟4。
为何3.5h考4道题啊?
T1
- 题面
- 思路
对于每个区间和,贪心考虑怎么选,最终取能选的区间最多的即可。 - 正解
同上
T2
- 题面
- 思路
设\(f_{u,d}\)表示在\(u\)的子树中,距离\(u\)为\(d\)的灭火器有多少个点能分配。
设\(g_{u,d}\)表示在\(u\)的子树中,距离\(u\)为\(d\)的灭火器需求的点数。
具体见代码及注释。#include<bits/stdc++.h> using namespace std; using ll=long long; const int N=1e5+3,K=23; int n,s,k; int f[N][K],g[N][K]; int ans; vector<int>e[N]; void dfs(int u,int fa){ for(int v:e[u]){ if(v==fa) continue; dfs(v,u); for(int d=1;d<=k;d++){ f[u][d]=min(f[u][d]+f[v][d-1],n);//累加子树中距离j的剩余灭火器容量 g[u][d]+=g[v][d-1];//累加子树中距离j的未覆盖节点数 } } g[u][0]++;//自己与自己的距离为0,且未被覆盖 if(g[u][k]){//必须在u位置放灭火器(超出k距离就无法覆盖了) int cnt=ceil(1.0*g[u][k]/s);//计算需要放多少个灭火器 f[u][0]=min(n,s*cnt); ans+=cnt; } for(int i=0;i<=k;i++){//用距离u为i的灭火器覆盖距离u为k-i的未覆盖节点 int j=k-i; int cover=min(g[u][j],f[u][i]);//能覆盖多少 f[u][i]-=cover;//灭火器剩余容量减少 g[u][j]-=cover;//未覆盖节点数减少 } for(int i=0;i<k;i++){//用距离u为i的灭火器覆盖距离u为k-i-1的未覆盖节点(留一位给父亲) int j=k-1-i; int res=min(g[u][j],f[u][i]); f[u][i]-=res; g[u][j]-=res; } return ; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>s>>k; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } dfs(1,0); //根节点特殊处理:最后匹配根节点的所有灭火器和未覆盖节点 //根节点没有父节点,必须自己处理完所有未覆盖节点 for(int i=0;i<=k;i++){ for(int j=0;i+j<=k;j++){ int cover=min(f[1][i],g[1][j]);//能覆盖多少 f[1][i]-=cover;//灭火器剩余容量减少 g[1][j]-=cover;//未覆盖节点数减少 } } int res=0; for(int i=0;i<=k;i++) res+=g[1][i];//统计根节点剩余的所有未覆盖节点 ans+=ceil(1.0*res/s);//剩余未覆盖节点,需要额外加灭火器 cout<<ans; return 0; } - 正解
同上
T3
- 题面
- 思路
考虑朴素\(O(nc)dp\)。
设\(dp_{i,j}\)表示前\(i\)段走完后,集尘盒内剩余\(j\)灰尘的最小时间。
对于每种状态,考虑是否清空。
得分:\(20pts\)。#include<bits/stdc++.h> using namespace std; using ll=long long; const int N=2e5+3; int n,c,b,a[N],v[N]; ll dp[N],tmp[N]; int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>c>>b; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>v[i]; for(int i=0;i<c;i++) dp[i]=1e17;//初始化 for(int i=1;i<=n;i++){ for(int j=0;j<=c;j++) tmp[j]=1e17;//初始化 for(int j=0;j<=c;j++){//遍历上一步的所有状态,j表示上一步集尘盒剩余容量 if(dp[j]==1e17) continue;//不可达,跳过 ll used=min(j,(ll)v[i]);//将剩余空间用完 ll dust=v[i]-used;//还剩余灰尘量 ll cost=a[i];//走完一段的时间 int rest=j-used;//用完空间后,新的剩余容量 if(dust>0){//还有灰尘剩余,需要多次往返+清空 int full=(dust+c-1)/c;//计算需要多少次满载 cost+=1ll*full*a[i]+1ll*full*b;//增加耗时:吸尘+清空 rest=(c-(dust%c))%c;//最终剩余容量 } tmp[rest]=min(tmp[rest],dp[j]+cost);//1.不清空,保留最终剩余容量now tmp[c]=min(tmp[c],dp[j]+cost+b);//2.清空,容量回满 } swap(dp,tmp); } ll ans=1e17; for(int j=0;j<=c;j++) ans=min(ans,dp[j]+b);//最后要清空一次 cout<<ans; return 0; } - 正解
线段树优化。
T4
- 题意
给定\(n\)个套娃以及它们的容量\(a_i(0\leq a_i\leq n)\)。
求前缀最小套娃数。 - 思路
伪贪心。
没注意到可以把一个容量为\(1\)的空套娃暂时放在另一个容量为\(1\)的套娃里。
得分:\(28pts\)。 - 正解
结论题。
令\(cnt_i\)表示\(a_j<i\)的\(j\)的数量。
结论:答案为\(max_{i=1}^n\lceil \frac{cnt_i}{i}\rceil\)。
每次暴力统计答案的时间复杂度为\(O(n^2)\),考虑优化。
离线,按照值域扫描线。
考虑最后取\(i\)时,对答案的贡献。
设值为\(i\)的有\(x_i\)个数,若\(i\)比\(i-1\)优,则必须满足\(\frac{a}{i-1}<\frac{a+x_i}{i}\)。
可以得到\(a<x_ii-x_i\Rightarrow\frac{a+x_i}{i}<x_i\)。
所以对于\(i\),只有\(x_i\)个位置需要被更新,相当于要维护:
\(O(n)\)次插入;
\(O(n)\)次求第\(ij\)小。
使用树状数组倍增或线段树维护,时间复杂度\(O(nlogn)\)。
总结
- 解题思路
大致没什么问题了吧。
朴素算法\(\Rightarrow\)优化。 - 算法
浙公网安备 33010602011771号