省赛前最后的三场组队赛了 形势不妙 感觉一些题是应该要出的:
第一场的H题 uvalive-6852
https://vjudge.net/contest/223856#problem/H
题意:给n个数从1到n 给一个区间a b 表示数字前a个能覆盖 后b个不能 循环下去 问最后被覆盖了(0,1,2,3)次的数字有多少个
n到1e5 m到1e6 当时a和b都是小于等于16的
这道题的关键在于a和b都是小于等于16的 就是说总数不超128 但是常数128还是太大了 所以还要缩减常数 方法就是用一个map【b】【a】 其中map【b】【0-a】中的值都为1 这样你每次查询时直接某个数模b就知道他是否位于哪个有值的区间了
第二场的F题 CodeChef - AMBOXES
题意 给一个大等级盒子 里面很多个小等级盒子 等级为0的盒子里面才有糖果 问要取出一定的糖果需要打开糖果多少次
- 1 ≤ n,m ≤ 300000
- 1 ≤ ai ≤ 109
- 1 ≤ Xi ≤ 1012
这是数据范围
感觉这道题和上面那题很像 关键点都在于要发现一个地方:数据并没有并没有由n推演的那么大 他实际只有xi小于等于1e12 打比赛的时候我们想到非常暴力的就是从树的底部递推 每次用ceil函数叠加父节点个数 然而发现那样如果直接嵌套循环的话复杂度是O(n*m) 实际上对于每次询问我们不需要遍历n次 只需要不断的对当前的节点数相除 当他为1时直接把前缀的长度和加上即可 因为每次我的父节点至少衍生出两个子节点 所以复杂度不会超过O(m*log(n)) 就是只要在原来的超时代码做小小的优化就行了
贴下代码:
#include <set> #include <map> #include <list> #include <deque> #include <cmath> #include <queue> #include <stack> #include <bitset> #include <vector> #include <string> #include <cstdio> #include <cstring> #include <iomanip> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define ll long long const int maxn=300000+500; int tot; int n,m; ll a[maxn],len[maxn],inp[maxn]; ll sum[maxn]; inline void solve() { ll x,ans=0; scanf("%lld",&x); for(int i=tot;i>=1;i--) { ans+=len[i]*x; x=x/a[i]+(x%a[i]==0?0ll:1ll); if(x==1ll) { ans+=sum[i-1]; break; } } printf("%lld\n",ans); } int main() { cin>>n>>m; tot=1;len[tot]=1;a[tot]=1; for(int i=1;i<=n;i++) scanf("%lld",&inp[i]); for(int i=n;i>=1;i--) { ll x=inp[i]; if(x==1ll) len[tot]++; else { ++tot; a[tot]=x; len[tot]=1; } } len[tot]--; sum[0]=0; for(int i=1;i<=tot;i++) sum[i]=sum[i-1]+len[i]; for(int i=1;i<=m;i++) solve(); return 0; }未完。。。