Preface 这是2023.7.12 的比赛,由于$$codeforces$$崩了所以本场$$unrated$$;
从这场开始好像有点适应了,但只签到了$$A$$,一时间没有想到打表;
题目也都是后面补的,感觉跟榜做难度还是挺合理的;
#A. Lucky Numbers (easy) 题意: 给定询问$$x$$,找到不小于$$x$$的,只有相等数量的$$7,4$$构成的数;
思路: 注意到上来按位数直接构造很可能会陷入死胡同,因为$$1≤x≤10^9$$,所以本身这样的数并不多,我们直接打表即可;
AC code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #define local #pragma GCC optimize(2) #include <bits/stdc++.h> #include <iostream> using namespace std;typedef long long ll;int main () { #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif set<ll> a; a.insert (47 );a.insert (74 ); for (int i=0 ;i<=3 ;i++){ for (int j=i+1 ;j<=3 ;j++){ ll x=3 *pow (10 ,i)+3 *pow (10 ,j); a.insert (7777 -x); } } for (int i=0 ;i<=5 ;i++){ for (int j=i+1 ;j<=5 ;j++){ for (int k=j+1 ;k<=5 ;k++){ ll x=3 *pow (10 ,i)+3 *pow (10 ,j)+3 *pow (10 ,k); a.insert (777777 -x); } } } for (int i=0 ;i<=7 ;i++){ for (int j=i+1 ;j<=7 ;j++){ for (int k=j+1 ;k<=7 ;k++){ for (int p=k+1 ;p<=7 ;p++){ ll x=3 *pow (10 ,i)+3 *pow (10 ,j)+3 *pow (10 ,k)+3 *pow (10 ,p); a.insert (77777777 -x); } } } } for (int i=0 ;i<=9 ;i++){ for (int j=i+1 ;j<=9 ;j++){ for (int k=j+1 ;k<=9 ;k++){ for (int p=k+1 ;p<=9 ;p++){ for (int q=p+1 ;q<=9 ;q++){ ll x=3 *pow (10 ,i)+3 *pow (10 ,j)+3 *pow (10 ,k)+3 *pow (10 ,p)+3 *pow (10 ,q); a.insert (7777777777 -x); } } } } } int n; cin>>n; for (auto iter=a.begin ();iter!=a.end ();iter++){ ll y=*iter; if (y>=n) { cout<<y<<endl; break ; } } return 0 ; }
#B. Sad powers 题意: 给定 $$q$$ 个询问,每次询问$$[l,r]$$ 这个区间内满足 $$x=a^p,a>0,p>1$$ 的 $$x$$ 的数量;
思路: 一眼转化为区间$$[1,r],[1,l-1]$$相减,但之后好像是个结论题,之前做过这样的上界估计,当时就是把容斥原理减去的项丢掉了;
现在要精确的把数量算出来,我们还原容斥原理:记$$\mu[k]$$表示$$k$$的莫比乌斯函数,那么 $$ res(x)=1+\sum_{k=2}^{\infty}(-\mu[k])(\left \lfloor \sqrt[k]{x} \right \rfloor-1 ) $$ 简单证明一下:对于$$a^k\in [2,x] \Leftrightarrow a\in [2,\left \lfloor \sqrt[k]{x} \right \rfloor] $$,那么满足条件的$$x$$数量就为$$\left \lfloor \sqrt[k]{x} \right \rfloor-1$$,再分$$k$$的素因子归纳讨论前面的符号(如果$$k$$有平方因子说明计算$$k=\prod_{prime\ p|k}p$$的那一步时已经计算过了一次了,不需要重复计算,对于只有偶数个素因子相乘的$$k$$被重复计算一次,需要减去,对于只有奇数个素因子相乘的$$k$$被重复减去一次,需要加上);
经过以上抽象证明以后,接下来就是卡常技巧和卡精度技巧了;
由于$$x\le 10^{18}$$,所以开long long
,pow(x,1.0/k)
对一些刚好开根的数可能会算错 而导致WA (难蚌),所以算完要进行验证一下;
看完一圈题解 下来我这个确实不是正解,但懒得把正解复制下来了,思路是把非平方数暴力存下来,大概在$$10^6$$个数左右;
AC code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #define local #include <bits/stdc++.h> #include <iostream> using namespace std;const int maxn=64 ;int vis[maxn]={0 };int mu[maxn]={0 };int prime[maxn]={0 };int find (long long x,long long k) { long long y=pow (x,1.0 /k); long long z=pow (y,k); if (k>=10 ) return y; if (z<=x){ long long t=pow (y+1 ,k); if (t>x) return y; else { long long s=pow (y+2 ,k); if (s>x) return y+1 ; else return y+2 ; } } else { long long r=pow (y-1 ,k); if (r<=x) return y-1 ; else { long long j=pow (y-2 ,k); if (j<=x) return y-2 ; else return y-3 ; } } return y; } int cnt=0 ;void mobius () { vis[1 ]=1 ; mu[1 ]=1 ; for (int i=2 ;i<maxn;i++){ if (!vis[i]) { prime[++cnt]=i; mu[i]=1 ; } for (int j=1 ;j<=cnt;j++){ if (i*prime[j]>maxn) break ; vis[i*prime[j]]=1 ; if (i%prime[j]!=0 ) { mu[i*prime[j]]=-mu[i]; } else { mu[i*prime[j]]=0 ; } } } } int res (long long x) { if (x==0 ) return 0 ; if (x==1 ||x==2 ||x==3 ) return 1 ; int tmp=1 ; int c=0 ; for (int k=2 ;k<maxn;k++){ if (c==1 ) break ; if (mu[k]==0 ) continue ; c=find (x,1LL *k); tmp+=mu[k]*(c-1 ); } return tmp; } int main () { ios::sync_with_stdio (0 );cin.tie (0 );cout.tie (0 ); #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif int q; mobius (); cin>>q; while (q--){ long long l,r; cin>>l>>r; int res2=res (l-1 ); int res1=res (r); int ans=res1-res2; cout<<ans<<endl; } return 0 ; }
#C. Destruction of a Tree 题意: 思路: AC code:
#D. Time to Raid Cowavans 题意: 给定长度为$$n$$的数列$$w$$,给定$$p$$组询问$$(a,b)$$,回答$$\sum_{a\le i \le n,b|i-a}a_i$$的值;
思路: 根号分治加离线;
首先观察数据范围不考虑在线做法,离线处理所有$$b$$值相同的询问;
划分界限$$T$$,对于$$b>T$$,由于和式个数不会很多,直接暴力即可;对于$$b<T$$,由于模$$b$$的余数不会很多,预处理所有可能按模数跳跃的前缀和复杂度不会太高,对于询问找到两个合适的前缀和作差即可;
复杂度为$$O(plogp+\sum_{every\ question \ b>T}{\frac{b}{T}}+\sum_{b<T}{n})=O(O(p)*\frac{n}{T}+Tn)$$,取$$T=\sqrt n$$能过;
AC code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #define local #include <bits/stdc++.h> #include <iostream> using namespace std;typedef long long ll;const int maxn=3e5 +5 ;int w[maxn];ll ans[maxn]; struct question { int a,b,id; }q[maxn]; ll s[maxn]; void init (int b,int n) { for (int i=1 ;i<=b;i++)s[i]=w[i]; for (int i=b+1 ;i<=n;i++)s[i]=s[i-b]+1LL *w[i]; } int main () { ios::sync_with_stdio (0 );cin.tie (0 );cout.tie (0 ); #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif int n,p; cin>>n; int sn=sqrt (n); for (int i=1 ;i<=n;i++) cin>>w[i]; cin>>p; for (int i=1 ;i<=p;i++) cin>>q[i].a>>q[i].b,q[i].id=i; sort (q+1 ,q+p+1 ,[](question&l,question&r){ return l.b<r.b; }); for (int i=1 ;i<=p;i++){ if (q[i].b>=sn) { int k=q[i].id; for (int j=q[i].a;j<=n;j+=q[i].b){ ans[k]+=1LL *w[j]; } } else { int k=q[i].id; int x=(n-q[i].a)*1.0 /q[i].b; if (q[i].b!=q[i-1 ].b)init (q[i].b,n); ans[k]=(q[i].a>q[i].b)?s[q[i].a+x*q[i].b]-s[q[i].a-q[i].b]:s[q[i].a+x*q[i].b]; } } for (int i=1 ;i<=p;i++) cout<<ans[i]<<endl; return 0 ; }
#E. Report 题意: 给定长度为$$n$$的数列,一次操作是指将一个前缀升序或者降序重新排列,求出$$m$$次操作后的序列;
思路: 观察数据范围,应该排除在线和暴力做法;
对于所有的操作$$(t,r)$$,若$$i<j,r_i\le r_j$$,那么不论$$t$$的取值,后面的操作会严格覆盖掉前面的操作,可以称前面的操作时无效的,我们维护一个单调栈,找到确实有效的操作;
对于这些操作会将数列分割成一个个的小区间,有的区间上升有的区间下降,我们利用双指针扫描一遍原来数组(有效操作最长的哪个区间)的升序即可;
AC code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #define local #pragma GCC optimize(2) #include <bits/stdc++.h> #include <iostream> using namespace std;const int maxn=2e5 +5 ;int a[maxn];int ans[maxn];int t[maxn];int r[maxn];stack<int > stk; int opt[maxn];bool cmp1 (int l,int r) { return l<r; } bool cmp2 (int l,int r) { return l>r; } int main () { ios::sync_with_stdio (0 );cin.tie (0 );cout.tie (0 ); #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif int n,m; cin>>n>>m; for (int i=1 ;i<=n;i++) cin>>a[i]; for (int i=1 ;i<=m;i++) cin>>t[i]>>r[i]; int top=1 ; while (top<=m){ if (stk.empty ()) stk.push (top); else { while (!stk.empty ()&&r[stk.top ()]<=r[top]) stk.pop (); stk.push (top); } top++; } int sz=stk.size (); for (int i=sz;i>0 ;i--){ opt[i]=stk.top (); stk.pop (); } int L=1 ,R=r[opt[1 ]],now=1 ; for (int i=R+1 ;i<=n;i++) ans[i]=a[i]; sort (a+1 ,a+R+1 ); for (int i=R;i>0 ;i--){ if (now+1 <=sz&&i<=r[opt[now+1 ]]) now++; if (t[opt[now]]==1 ) ans[i]=a[R--]; else ans[i]=a[L++]; } for (int i=1 ;i<=n;i++) cout<<ans[i]<<" " ; return 0 ; }
#F. Renting Bikes 题意: 给定$$n$$个男孩的私房钱,总公共预算$$a$$,$$m$$辆单车的租金,最大化租用的单车数量,并计算此时支出了的私房钱;
思路: 先粗略估计租用单车的上界$$R$$:让钱最多的$$R$$个男孩租用最便宜的$$R$$辆单车,并且预算(差点就)花光的边界情况;此时意味着在多租借一辆就会爆炸,给男孩私房钱降序排序和单车租金升序排序,找到临界情况;
然后再精细的调整$$r$$的值,从$$R$$开始下降,采用田忌赛马的办法,假设租用了$$r$$辆单车,让钱最多的$$r$$个男孩租用最便宜的$$r$$辆单车,而且钱越多的男孩承担越贵的租金,并小心的计算此时的真实挪用的最小预算(让这些男孩充分用私房钱);
得出$$r$$后,男孩们多出的私房钱当然是所有租用的单车的租金减去总公共预算了;
Ac code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #define local #include <bits/stdc++.h> #include <iostream> using namespace std;const int maxn=1e5 +5 ;int p[maxn];int b[maxn];typedef long long ll;int n,m,a;ll sum[maxn]; ll cap[maxn]; ll dp (int r) { ll res=0 ; for (int i=1 ;i<=r;i++){ res+=(p[i]>b[r+1 -i])?(p[i]-b[r+1 -i])*1LL :0 ; } return res; } ll cost (int r) { if (r==0 ) return 0 ; ll res=0 ; for (int i=1 ;i<=r;i++) { res+=p[i]*1LL ; } return max (0LL ,res-a*1LL ); } int main () { #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif cin>>n>>m>>a; for (int i=1 ;i<=n;i++) cin>>b[i]; for (int i=1 ;i<=m;i++) cin>>p[i]; sort (b+1 ,b+n+1 ,[](int l,int r){return l>r;}); sort (p+1 ,p+m+1 ); for (int i=1 ;i<=m;i++) sum[i]=sum[i-1 ]+p[i]*1LL ; for (int i=1 ;i<=n;i++) cap[i]=cap[i-1 ]+b[i]*1LL ; int r=0 ; while (r<=n&&r<=m&&cap[r]+a>=sum[r]) r++; r--; while (r<=n&&r<=m&&dp (r)>a) r--; cout<<r<<" " <<cost (r)<<endl; return 0 ; }
#G. Full Binary Tree Queries 题意: 思路: 大细节模拟题,跳了跳了;
AC code:
#H. Vladik and Complicated Book 题意: 给定长度为$$n$$的置换,询问$$m$$个区间,对区间内的数进行升序排序, 考察某个下标是否发生变化,询问结束后还原置换;
思路: 直接根据题意模拟即可;
注意复杂度和值域范围,排序不选择sort
,应该采用线性复杂度的桶排序;
AC code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #define local #include <bits/stdc++.h> #include <iostream> using namespace std;const int maxn=1e4 +10 ;int p[maxn];int cpy[maxn];int buc[maxn];int main () { #ifdef local freopen ("in.txt" ,"r" ,stdin); freopen ("out.txt" ,"w" ,stdout); #endif int n,m; scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=n;i++) scanf ("%d" ,&p[i]); for (int i=1 ;i<=n;i++) cpy[i]=p[i]; while (m--){ int l,r,x; scanf ("%d%d%d" ,&l,&r,&x); if (x<l||x>r) { puts ("Yes" ); continue ; } memset (buc,0 ,sizeof (buc)); for (int i=l;i<=r;i++)buc[p[i]]++; int id=0 ;int cnt=0 ; for (int i=1 ;i<=n;i++) { cnt+=buc[i]; if (cnt==x-l+1 ) {id=i;break ;} } if (id==p[x]) puts ("Yes" ); else puts ("No" ); } return 0 ; }
![](https://kytolly-1318202921.cos.ap-chengdu.myqcloud.com/202307310221042.png