NOIP模拟4。

为何3.5h考4道题啊?

T1

  1. 题面
  2. 思路
    对于每个区间和,贪心考虑怎么选,最终取能选的区间最多的即可。
  3. 正解
    同上

T2

  1. 题面
  2. 思路
    \(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;
    }
    
  3. 正解
    同上

T3

  1. 题面
  2. 思路
    考虑朴素\(O(nc)dp\)
    \(dp_{i,j}\)表示前\(i\)段走完后,集尘盒内剩余\(j\)灰尘的最小时间。
    对于每种状态,考虑是否清空。
    #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;
    }
    
    得分:\(20pts\)
  3. 正解
    线段树优化。

T4

  1. 题意
    给定\(n\)个套娃以及它们的容量\(a_i(0\leq a_i\leq n)\)
    求前缀最小套娃数。
  2. 思路
    伪贪心。
    没注意到可以把一个容量为\(1\)的空套娃暂时放在另一个容量为\(1\)的套娃里。
    得分:\(28pts\)
  3. 正解
    结论题。
    \(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\)优化。
  • 算法