CSUST--3.7排位周赛第三场 (全解)
emmm,没什么比较难的题,都比较签到,我觉得这场应该是你们最接近AK的一次
题目链接:http://acm.csust.edu.cn/contest/74
比赛过后题目无法提交,请到problem中提交
题目说明:
数论king(思维)
厂里数论王(思维+前缀和)
暴力出奇迹(贪心)
打包商品(并查集)
他们说要我出一道签到题(卡特兰数)
数论king
题目大意:$f(x)$表示整数$x$能分解出的因数$5$的个数,$F(x)=f(1)+f(2)+...+f(x)$,问$F(n)$的值
Sample Input
12
Sample Output
2
emmm,纯粹的签到题,我们思考$n/k$可以表示的是1-n中有几个含因子k的数,那么$n/5$可以表示有几个数含因子$5$,接下来我们在5的基础上再乘以一个5即得25,那么$n/25$表示的是$n$中有几个含因子25的数,即含2个5因子的数,但是由于之前我们已经计算过1个5的,所以我们只需要将个数加起来就好了,以此类推。
以下是AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll qick_pow(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans*=a; a*=a; b>>=1; } return ans; } int main() { ll n; cin>>n; ll ml=1; ll ans=0; while (1){ ll nb=n/qick_pow(5LL,ml); if (nb<1LL) break; ans+=nb; ml++; } cout<<ans<<endl; return 0; }
厂里数论王
题目大意:有一个数列为$a$,其前缀和为$sum$,给你一个数$m$,问你是否存在一段区间之和为$m$的倍数,即$sum[i]-sum[j-1]\equiv 0(mod m)$
Sample Input
3 3 1 2 3
Sample Output
YES
emmm,我是思维菜鸡QAQ,刚开始以为是m,忽略了倍数,然后跑了个类似滑动窗口队列,队尾进,如果队列和大于m则队头出。。。果断WA了两发,然后考虑倍数,想想似乎可以将其转换为刚刚的形式,即对每个$a[i]$取模。。。然后过了大概50%的测试点,于是手动写了个测试数据:
3 7
6 6 2
。。。。即使对每个$a[i]$取模了,也会达到倍数的,那么于是就有了个新的想法,对前缀和取模,这样就不会有有到达$m$倍数的了,取完模之后。。。豁然开朗,房屋俨然,有良田美池桑竹之属...$sum[i]-sum[j-1]=0$不就符合题目的要求了,也就是说统计一下取完模之后是否存在一模一样的$sum[i]$就行了
以下是AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; const int mac=5e5+10; typedef long long ll; unordered_map<int,bool>q; int a[mac],sum[mac]; int main() { int n,m,mark=0; scanf ("%d%d",&n,&m); q[0]=1; for (int i=1; i<=n; i++){ scanf ("%d",&a[i]); sum[i]=(sum[i-1]+a[i])%m; if (mark) continue; if (q[sum[i]]) mark=1; q[sum[i]]=1; } if (mark) printf("YES\n"); else printf("NO\n"); return 0; }
暴力出奇迹
题目大意:给你$a,b$两个数列,有函数$f(l,r)=\sum_{l<=i<=r}a[i]\times b[i]$,你的任务是重排列$b$使得$\sum_{1<=l<=r<=n}f(l,r)$最小,问你这个最小值对998244353取模的值
Sample Input
5 1 8 7 2 4 9 7 2 9 3
Sample Output
646
emmm,我们先把$\sum_{1<=l<=r<=n}f(l,r)$拆解出来,可以得到:$\sum_{r=1}^{n}\sum_{l=1}^{r}\sum_{i=l}^{r}a[i]\times b[i]$。我们将其具体化一下就是:
$f(1,1)=f(1,1)$
$f(1,2),f(2,2)=f(1,1)+2*f(2,2)$
$f(1,3),f(2,3),f(3,3)=f(1,1)+2*f(2,2)+3*f(3,3)$
$f(1,4),f(2,4),f(3,4),f(4,4)=f(1,1)+2*f(2,2)+3*f(3,3)+4*f(4,4)$
$f(1,5),f(2,5),f(3,5),f(4,5),f(5,5)=f(1,1)+2*f(2,2)+3*f(3,3)+4*f(4,4)+5*f(5,5)$
那么我们就很容易得到总得答案:$\sum_{i=1}^{n}i\times (n-i+1)a[i]\times b[i]$
接下来我们就需要将这个答案最小化,那么怎么最小化呢?可以看到的是,$i\times (n-i+1)a[i]$是固定的值设为$val_{i}$,那么我们只需要将最小的$b[i]$和最大的$val_{i}$和起来,最大的$b_{i}$和最小的$val_{i}$合并。。以此类推就完事了,所以代码也不难写
以下是AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; typedef long long ll; const ll mod=998244353; ll a[mac],b[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf ("%lld",&a[i]); a[i]=1LL*i*(n-i+1)*a[i]; } sort(a+1,a+1+n); for (int i=1; i<=n; i++) scanf("%lld",&b[i]); sort(b+1,b+1+n); ll sum=0; for (int i=1; i<=n; i++){ sum=(sum+(a[i]%mod)*b[n-i+1]%mod)%mod; } printf("%lld\n",sum); return 0; }
打包商品
题目大意:给你n个商品的$val$值,有$m$次操作,每次操作将$u,v$两件商品打包为$w$,当操作为$s,v$是,打包的将是$s,w$,当包中最大$val$和最小$val$差值大于$k$的时候无法打包,请输出n个商品所属包装中的最小商品编号和$val$的极差(n,m,k<=1e5)
Sample Input
5 4 2 2 1 3 5 4 1 3 2 4 5 4 1 5
Sample Output
1 1 2 0 1 1 4 1 4 1
emmm,一眼并查集缩点。。。没什么好说的,按照他的一步一步来就好了,先查找u,v的祖先,在合并之前判断一下两个祖先合并之后的最大值与最小值只差是否符合条件,然后合并两点,并更新祖先的最大值与最小值。然后这题就差不多结束了。。。最后for一遍,更新商品编号最小值并计算每个点的祖先最大值与最小值之差即可。
写完一发A了之后再看一下代码。。。其实缩不缩点都无所谓了
以下是AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const int inf=1e8+10; int a[mac],father[mac],id[mac]; int min_id[mac],cut[mac]; int find(int x){return x==father[x]?x:father[x]=find(father[x]);} struct node { int max_val,min_val; }project[mac]; int sa[10],k; int ok(int u,int v) { sa[1]=project[u].max_val;sa[2]=project[u].min_val; sa[3]=project[v].max_val;sa[4]=project[v].min_val; sort(sa+1,sa+5); if (abs(sa[1]-sa[4])>k) return 0; return 1; } int main() { int n,m; scanf ("%d%d%d",&n,&m,&k); for (int i=1; i<=n; i++){ scanf ("%d",&a[i]); father[i]=i; project[i]=node{a[i],a[i]}; min_id[i]=inf; } for (int i=1; i<=m; i++){ int u,v; scanf ("%d%d",&u,&v); int fa=find(u),fb=find(v); if (fa!=fb) { if (!ok(fa,fb)) continue; father[fa]=fb; project[find(u)]=node{sa[4],sa[1]}; } } int cnt=0; for (int i=1; i<=n; i++) if (father[i]==i) id[i]=++cnt; for (int i=1; i<=n; i++){ id[i]=id[find(i)]; min_id[id[i]]=min(min_id[id[i]],i); cut[id[i]]=project[find(i)].max_val-project[find(i)].min_val; } for (int i=1; i<=n; i++){ printf ("%d %d\n",min_id[id[i]],cut[id[i]]); } return 0; }
他们说要我出一道签到题
题目大意:给你一个栈的进栈顺序,问出栈序列有多少种,对1e9+7取模
Sample Input
5
Sample Output
42
。。。此题是标准体的弱化版,本来是不用取模的,那就要用到大数,但取模之后就是个签到的卡特兰数了。还有就是你们的牲口学长的原题是$n$的范围为$[1,10^9)$。。。
此题就是求$C(2n,n)-C(2n,n-1)$的值,由于这里只需要计算两个组合数,我们暴力求解会更快一些,即直接计算阶乘即可
以下是AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; ll qick_pow(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } ll C(ll n,ll m) { ll ansn=1,ansm=1; for (int i=0; i<m; i++){ ansn=ansn*(n-i)%mod; ansm=ansm*(m-i)%mod; } return ansn*qick_pow(ansm,mod-2)%mod; } int main() { int n; scanf ("%d",&n); printf ("%lld\n",(C(2LL*n,n)-C(2LL*n,n-1)+mod)%mod); return 0; }