NOI模拟赛 4.26
赛时时间分配
读题
-7:40:读T1,感觉T1非常可做,就想了一会儿,大概有了思路(生成函数+隔板法)
-7:50:读完后两题,T2感觉跟线段树有关,其它暂时没有什么想法
T1
-8:35:打T1,一开始发现生成函数部分时间过不去,25min后发现可以直接用前缀和优化
T3
-9:20:打完T3的30pts,感觉下一档分比较麻烦,且有可能会T,先不打
T2
-9:40:打完T2的30pts
-10:00:大概想出了一个T2可行的做法,开始打
-10:35:码完T2
-10:55:拍了会儿T2,感觉问题不大
T3
-11:25:赶完T3的60pts
后记
T1爆零了,,,发现为了降低常数,有一个循环边界改小了QAQ
T1:BZOJ5215商店购物
题目
在 Byteland一共开着\(n\)家商店,编号依次为\(1\)到 \(n\),其中编号为\(1\)到\(m\)的商店有日消费量上限,第\(i\)家商店的日消费量上限为\(w_i\)。Byteasar每次购物的过程是这样的:依次经过每家商店,然后购买非负整数价格的商品,并在结账的时候在账本上写上在这家商店消费了多少钱。当然,他在这家商店也可以什么都不买,然后在账本上写上一个0。这一天, Byteasar日常完成了一次购物,但是他不慎遗失了他的账本。他只记得自己这一天一共消费了\(k\),请写一个程序,帮助 Byteasar计算有多少种可能的账单。
\(1\leq n,k \leq 5*10^7,0\leq w_i\leq300,1\leq m\leq300\)
简要题解
本题可分为带限制和不带限制两部分分别考虑,后半部分即为隔板法。
对于前半部分这种带限制、但取值连续的计数问题,相较生成函数,使用前缀和背包统计答案效率更高。
Tips
前半部分注意循环边界和枚举顺序
f[0][0]=g[0][0]=1;
for(int i=1;i<=s[m];++i) g[0][i]=1;
for(int i=1;i<=m;++i){
for(int j=0;j<=s[m];j++){
if(j-w[i]-1>=0)f[i][j]=((g[i-1][j]-g[i-1][j-w[i]-1])%mod+mod)%mod;
else f[i][j]=g[i-1][j];
if(j>0)g[i][j]=(1LL*g[i][j-1]+1LL*f[i][j])%mod;
else g[i][j]=f[i][j];
}
}
注意n=m
时需要特判
T2:BZOJ5216公路建设
题目
在Byteland一共有\(n\)个城市,它们之间计划修建\(m\)条双向道路,其中修建第i条道路的费用为\(c_i\)。Byteasar作为Byteland公路建设项目的总工程师,他决定选定一个区间\([l,r]\),仅使用编号在该区间内的道路。他希望选择一些道路去修建,使得连通块的个数尽量少,同时,他不喜欢修建多余的道路,因此每个连通块都可以看成一棵树的结构。为了选出最佳的区间, Byteasar会不断选择 \(q\)个区间,请写一个程序,帮助 Byteasar计算每个区间内修建公路的最小总费用。
\(n\leq 100,m\leq10^5,q\leq15000\)
简要题解
暴力即为每次Kruscal
发现\(n\)非常小,考虑构建线段树,线段树的每个点上存当前区间的最小生成树中的边,这样相邻区间合并时只影响到数量级为\(O(n)\)的边,由于每个区间内存的边集本身有序,合并时不需要排序,直接归并即可,时间复杂度\(O((m+q\log m)n\log n)\)。
tree operator + (tree x,tree y){
tree res;res.num=0;int j=1;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=x.num;i++){
for(;j<=y.num&&e[y.e[j]].w<e[x.e[i]].w;j++)
if(check(y.e[j]))res.e[++res.num]=y.e[j];
if(check(x.e[i]))res.e[++res.num]=x.e[i];
}
for(;j<=y.num;j++)if(check(y.e[j]))res.e[++res.num]=y.e[j];
return res;
}
void build(int x,int l,int r){
if(l==r){
num(x)=1;
e(x,1)=l;
return;
}
int mid=(l+r)>>1;
build(lch,l,mid);
build(rch,mid+1,r);
T[x]=T[lch]+T[rch];
}