暑假集训CSP提高模拟3
暑假集训CSP提高模拟3
组题人: @joke3579
\(T1\) P117. abc猜想 \(100pts\)
-
由题,有 \(\dfrac{(a^{b}-a^{b} \bmod c) \bmod c^{2}}{c}\) 即为所求。
- 证明
- 设 \(\left\lfloor \dfrac{a^{b}}{c} \right\rfloor= \dfrac{a^{b}-a^{b} \bmod c}{c}=kc+r\) ,其中 \(r \in [0,c)\) 。
- 移项有 \(a^{b}-a^{b} \bmod c=kc^{2}+rc\) ,且 \(rc \in [0,c^{2})\) 。
- 将 \(rc\) 视作 \(a^{b}-a^{b} \bmod c\) 除以 \(c^{2}\) 的余数,即 \(rc=(a^{b}-a^{b} \bmod c) \bmod c^{2}\) 。
- 将系数 \(c\) 移项,有 \(r=\dfrac{(a^{b}-a^{b} \bmod c) \bmod c^{2}}{c}\) 。
点击查看代码
ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { ll a,b,c,ans,r; scanf("%lld%lld%lld",&a,&b,&c); r=qpow(a,b,c); ans=(qpow(a,b,c*c)-r+c*c)%(c*c)/c; printf("%lld\n",ans); return 0; }
- 证明
\(T2\) P118. 简单的排列最优化题 \(0pts\)
-
等价于求 \(\min\limits_{k=0}^{n-1}\{ \sum\limits_{i=1}^{n-k}|a_{i}-(i+k)|+ \sum\limits_{i=n-k+1}^{n}|a_{i}-(i-(n-k))| \}\) 。
-
部分分
-
\(50pts\) :暴力 \(O(n^{2})\) 枚举所有的 \(k,i\) 并进行判断,及时进行剪枝来减小常数。
点击查看代码
ll a[1000010]; int main() { ll n,ans=0x7f7f7f7f,pos=0,sum=0,i,k; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(k=0;k<=n-1;k++) { sum=0; for(i=1;i<=n-k;i++) { sum+=abs(a[i]-(k+i)); if(sum>=ans) { break; } } for(i=n-k+1;i<=n;i++) { sum+=abs(a[i]-(i-(n-k))); if(sum>=ans) { break; } } if(sum<ans) { ans=sum; pos=k; } } printf("%lld %lld\n",pos,ans); return 0; }
-
-
正解
- 由 [ABC351F] Double Sum 的套路,尝试展开绝对值及 \(\min,\max\) 。
- 将式子拆开有 \(\begin{aligned} & \min\limits_{k=0}^{n-1}\{ \sum\limits_{i=1}^{n-k}|a_{i}-(i+k)|+ \sum\limits_{i=n-k+1}^{n}|a_{i}-(i-(n-k))| \} \\ &=\min\limits_{k=0}^{n-1}\{ \sum\limits_{i=1}^{n-k}( \max(a_{i},i+k)- \min(a_{i},i+k))+ \sum\limits_{i=n-k+1}^{n}( \max(a_{i},i+k-n)- \min(a_{i},i+k-n)) \} \\ &=\min\limits_{k=0}^{n-1}\{ \sum\limits_{i=1}^{n-k}(a_{i}+i+k-2 \min(a_{i},i+k))+ \sum\limits_{i=n-k+1}^{n}(a_{i}+i+k-n-2 \min(a_{i},i+k-n)) \} \\ &=\sum\limits_{i=1}^{n}(a_{i}+i)+\min\limits_{k=0}^{n-1}\{- \sum\limits_{i=1}^{n-k}2 \min(a_{i},i+k)- \sum\limits_{i=n-k+1}^{n}2 \min(a_{i},i+k-n) \} \\ &=\sum\limits_{i=1}^{n}(a_{i}+i)-2 \max\limits_{k=0}^{n-1}\{\sum\limits_{i=1}^{n-k} \min(a_{i},i+k)+\sum\limits_{i=n-k+1}^{n} \min(a_{i},i+k-n) \} \end{aligned}\) 。
- 好像式子推多了,懒得改了,只是常数大点。
- 现在问题来到了怎么求 \(\max\limits_{k=0}^{n-1}\{\sum\limits_{i=1}^{n-k} \min(a_{i},i+k)+\sum\limits_{i=n-k+1}^{n} \min(a_{i},i+k-n) \}\) 。
- 令 \(\begin{cases} x_{i}=a_{i}-i \\ y_{i}=a_{i}+n-i \end{cases}\) ,由于是排列所以 \(\{ x \},\{ y \}\) 均满足内部两两不同,则转化为求 \(\max\limits_{k=0}^{n-1}\{\sum\limits_{i=1}^{n-k}([k \ge x_{i}] \times a_{i}+[k<x_{i}] \times (i+k))+\sum\limits_{i=n-k+1}^{n}([k \ge y_{i}] \times a_{i}+ [k<y_{i}] \times (i+k-n)) \}\) ,前半部分将其拆成 \(\begin{cases} [k \ge x_{i}] \times a_{i} \\ [k<x_{i}] \times i \\ [k<x_{i}] \times k \end{cases}\) 三部分,后半部分同理。
- 将 \(\{ x \},\{ y \}\) 分别插入到权值树状数组里,分别维护 \(\begin{cases} [k \ge x_{i}] \times a_{i}/[k \ge y_{i}] \times a_{i} \\ [k<x_{i}] \times i/[k<y_{i}] \times (i-n) \\ [k<x_{i}]/[k<y_{i}] \end{cases}\) 即可,注意及时消除影响。
- 对于负数整体向右移来处理。
点击查看代码
ll a[3000010],x[3000010],y[3000010],c[6][3000010]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val,ll c[]) { x+=1000001; n+=1000001; for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll ask(ll x,ll c[]) { ll ans=0; x+=1000001; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } int main() { ll n,ans=0,pos=0,sum=0,i,k; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); x[i]=a[i]-i; y[i]=a[i]+n-i; add(2*n,x[i],a[i],c[0]); add(2*n,x[i],i,c[2]); add(2*n,x[i],1,c[4]); } for(k=0;k<=n-1;k++) { sum=0; sum+=ask(k,c[0]); sum+=ask(2*n,c[2])-ask(k,c[2]); sum+=(ask(2*n,c[4])-ask(k,c[4]))*k; sum+=ask(k,c[1]); sum+=ask(2*n,c[3])-ask(k,c[3]); sum+=(ask(2*n,c[5])-ask(k,c[5]))*k; if(sum>ans) { ans=sum; pos=k; } add(2*n,x[n-k],-a[n-k],c[0]); add(2*n,x[n-k],-(n-k),c[2]); add(2*n,x[n-k],-1,c[4]); add(2*n,y[n-k],a[n-k],c[1]); add(2*n,y[n-k],n-k-n,c[3]); add(2*n,y[n-k],1,c[5]); } ans*=-2; for(i=1;i<=n;i++) { ans+=a[i]+i; } printf("%lld %lld\n",pos,ans); return 0; }
\(T3\) P119. 简单的线性做法题 \(5pts\)
-
部分分
- \(1\) :输出样例 \(1\)
- \(2 \sim 4\) :分块/莫队/主席树处理出所有区间的众数出现次数,依次判断。
-
正解
- 做法貌似不少,例如差分后维护高阶前缀和、根号分治、分治,具体的话看 luogu 题解 吧。
- 区间问题考虑分治。
- 大力结论
- 对于一段区间 \([l,r]\) 的绝对众数 \(x\) ,那么对于所有的 \(k \in [l,r)\) 均有 \(x\) 是 \([l,k]\) 或 \((k,r]\) 的绝对众数。
- 所有区间的绝对众数至多有 \(\log n\) 个。
- 令 \(k=mid\) ,然后对不经过 \(mid\) 的区间分治处理。
- 问题来到了如何合并区间(处理经过 \(mid\) 的区间)。考虑枚举绝对众数 \(x\) ,然后求出 \([l,r]\) 中以 \(x\) 作为绝对众数的子区间的个数,桶维护前/后缀和即可。
- 判断一个数是不是一段区间的众数可借鉴判断一个数是不是一段区间的中位数 luogu P2839 [国家集训队] middle 的套路,赋值 \(1/-1\) 后判断整个区间的和是否 \(>0\) ,前缀和维护即可。
- 对于负数整体向右移来处理。
- 手动清空来保障复杂度。
点击查看代码
ll a[500010],b[500010],cnt[500010],vis[1000010],ans=0; vector<ll>pos; void divide(ll l,ll r) { if(l==r) { ans++; return; } else { ll mid=(l+r)/2,sum,lsum,rsum,i,j; divide(l,mid); divide(mid+1,r); for(i=mid;i>=l;i--) { cnt[a[i]]++; if(cnt[a[i]]>(mid-i+1)/2) { if(vis[a[i]]==0) { vis[a[i]]=1; pos.push_back(a[i]); } } } for(i=l;i<=mid;i++) { cnt[a[i]]=0;//不清空的话时间复杂度是对的,但常数大了 } for(i=mid+1;i<=r;i++) { cnt[a[i]]++; if(cnt[a[i]]>(i-(mid+1)+1)/2) { if(vis[a[i]]==0) { vis[a[i]]=1; pos.push_back(a[i]); } } } for(i=l;i<=r;i++) { cnt[a[i]]=vis[a[i]]=0; } for(i=0;i<pos.size();i++) { lsum=rsum=sum=r-l+1;//向右移位 cnt[sum]++;//桶维护前缀和出现次数 for(j=l;j<=mid-1;j++)//没搞清楚为什么倒过来写就 WA 了 { sum+=((a[j]==pos[i])?1:-1); lsum=min(lsum,sum); rsum=max(rsum,sum); cnt[sum]++;//桶维护每个区间长度出现次数 } sum+=((a[mid]==pos[i])?1:-1); for(j=lsum;j<=rsum;j++) { cnt[j]+=cnt[j-1];//维护桶数组的前缀和 } for(j=mid+1;j<=r;j++) { sum+=((a[j]==pos[i])?1:-1); ans+=cnt[min(rsum,sum-1)];//将 [l,j] 拆成 [l,k) 和 [k,j] 两部分,其中 [k,j] 满足和 >0 //因为要>0 所以小于 sum 的才能满足 } for(j=lsum;j<=rsum;j++) { cnt[j]=0; } } pos.clear(); } } int main() { ll n,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } divide(1,n); cout<<ans<<endl; return 0; }
\(T4\) P120. 简单的线段树题 \(100pts\)
-
原题: luogu P4145 上帝造题的七分钟 2 / 花神游历各国 | GSS4 - Can you answer these queries IV | LibreOJ 6281. 数列分块入门 5
-
懒得再复制一遍了,直接贴当时写的 题解 了。
-
一个数最多被开方加向下取整 \(6\) 遍就会变成 \(1\) ,并查集维护下有没有变成 \(1\) ,然后对于区间进行暴力单点修改,随便一个单点修改、区间查询的数据结构维护就行了。
点击查看代码
ll a[1000010],f[10000010],c[1000010]; ll find(ll x) { return (f[x]==x)?x:f[x]=find(f[x]); } ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll ask(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } int main() { ll n,m,pd,l,r,i,j; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); add(n,i,a[i]); f[i]=i; } f[n+1]=n+1; scanf("%lld",&m); for(i=1;i<=m;i++) { scanf("%lld%lld%lld",&pd,&l,&r); if(pd==0) { for(j=l;j<=r;j=find(f[j+1])) { add(n,j,-a[j]); a[j]=sqrtl(a[j]);//怕被卡精度遂写的 sqrtl() add(n,j,a[j]); if(a[j]==1) { f[j]=f[j+1]; } } } else { printf("%lld\n",ask(r)-ask(l-1)); } } return 0; }
-
势能线段树做法类似 牛客 NC275139 寿命修改 ,还需要卡常,懒得写了。
总结
- \(T1\)
- 对取模运输与进制的相互转化关系认识不够。
- \(T2\)
- 处理 \(k\) 与 \(\{ x \},\{ y \}\) 时将两者搞反了,说明对权值树状数组的认识仍不够。
- 赛时式子推多了。
- 空间开大加上不会算空间,显示 \(RE\) 实际上是 \(MLE\) 了,挂了 \(100pts\) 。
- \(T3\)
- 从部分分启发到根号分治的做法一直不是很掌握,可能是因为经常不打部分分导致的(?)。
后记
-
各题题目背景
-
\(T2\) 下发文件里的大样例造假了,中途才换,导致写的暴力对着假数据拍了半天没拍出来。
-
\(T3\) 下发文件里的大样例造假了,中途才换,还换了两次,第三次 @Delov 因怕被骂不想再换了。最后发现贺的 \(std\) 假了, 相应造的数据也假了(换大样例时顺带换了数据),结果发现第一组数据是真的。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18313664,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。