emmm,验完题之后觉得你们应该能够出个两三题,这场总得来说也没有什么很难的题,结果各位还真就两题签到题啊。。。我以为DP的那题应该可以过挺多人的,结果交都没人交。。。
题目说明:
简单博弈(数论)
一步两步(DP)
点击就送(签到题-前缀和)
区间异或(打表找规律)
直线重合(计算几何)
简单博弈
题目大意:给你一个长度n*2,你有两个字母A,B各n个,问你有多少种方法使得每个位置A的数量大于等于B的数量,答案对1e9取模(PS:你没有看错)
Sample Input
2
Sample Output
2
。。。。首先你要发现这玩意儿是个卡特兰数,即$C_{2n}^{n}-C_{2n}^{n-1}$,但很悲哀的是,模数是1e9,题目也没有什么数据保证,所以你不能取逆元。。。只能老老实实地通过素数分解来计算了。
你可以整合一下就是$\frac{2n*(2n-1)*...*(2n-n+1)}{n!}-\frac{2n*(2n-1)*...*(2n-n+2)}{(n-1)!}$
$=\frac{2n*(2n-1)*...*(2n-n+2)*(2n-n+1)-2n*(2n-1)*...*(2n-n+2)*n}{n!}$
$=\frac{2n*(2n-1)*...*(2n-n+2)}{n!}=\frac{2n!}{(n+1)!n!}$
然后就是快速求出这个东西,如果是暴力求解的话,即对每个数执行质因数分解的话,毫无疑问,在n=1e6面前绝对T了。
那么究竟该怎么做呢?
还记得之前我们第三次周赛做个的一个签到题(数论king)吧,通过这种方法我们就可以快速地求出每个n!中含有的每个素因子的个数。
在第三次周赛的这个题中,我有说过$n/k$表示的是1-n中含有因子k的个数,然后我们继续$k^2$,并将个数累加起来就是素因子k的个数。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9; const int mac=2e6+10; bool vis[mac]; int nb[mac],prim[mac],cnt=0; ll qick(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans=ans*a; a=a*a; b>>=1; } return ans; } 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; } void cal(int n,int mk) { for (int i=1; i<=cnt; i++){ int num=1; if (prim[i]>n) break; while (qick(prim[i],num)<=n) { int p=n/qick(prim[i],num); if (!mk) nb[prim[i]]+=p; else nb[prim[i]]-=p; num++; } } } ll C(int n,int m) { cal(n,0); cal(m+1,1);cal(m,1); ll ans=1; for (int i=1; i<=cnt; i++){ if (nb[prim[i]]){ ans=ans*qick_pow(prim[i],nb[prim[i]])%mod; } } return ans; } int main() { int n; scanf ("%d",&n); int m=sqrt(mac); for (int i=2; i<=m; i++){ if (!vis[i]){ for (int j=i*i; j<mac; j+=i) vis[j]=1; } } for (int i=2; i<=mac; i++) if (!vis[i]) prim[++cnt]=i; printf ("%lld\n",C(2*n,n)); return 0; }
一步两步
题目大意:给你一个正方体,你初始位于0号点,每次你能往相邻的顶点走,问走$n$步回到原点的方案有几种,对$10^9+7$取模。
Sample Input
4
Sample Output
21
emmm,这题很简单,就是个基础的dp,但毒瘤出题人为了让你们遭受社会的毒打,我相信很多没看时限和内存的人要遭到重击。
本来原题没什么花里胡哨的,我们设$dp[n][8]$代表着第$i$步到第$j$个顶点的方案有多少,那么状态转移就很好写了,当然,我们先把号标好,下面是我的个人编号:
每个顶点有旁边的3个顶点转移过来,我们只需要把他们加起来就OK了。
以下是内存充足情况的代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; typedef long long ll; const int mod=1e9+7; ll dp[mac][10]; int main() { int n; scanf ("%d",&n); dp[0][0]=1; for (int i=1; i<=n; i++){ dp[i][0]=(dp[i-1][1]+dp[i-1][3]+dp[i-1][4])%mod; dp[i][1]=(dp[i-1][0]+dp[i-1][2]+dp[i-1][5])%mod; dp[i][2]=(dp[i-1][1]+dp[i-1][3]+dp[i-1][6])%mod; dp[i][3]=(dp[i-1][0]+dp[i-1][2]+dp[i-1][7])%mod; dp[i][4]=(dp[i-1][0]+dp[i-1][5]+dp[i-1][7])%mod; dp[i][5]=(dp[i-1][1]+dp[i-1][4]+dp[i-1][6])%mod; dp[i][6]=(dp[i-1][2]+dp[i-1][5]+dp[i-1][7])%mod; dp[i][7]=(dp[i-1][3]+dp[i-1][4]+dp[i-1][6])%mod; } printf ("%lld\n",dp[n][0]); return 0; }
可以看到没什么难度,但内存改到10MB的时候显然这样写就不合适了,我们需要用到的只是上一步的答案,也就是说实际上我们只需要保存2步的答案就行了,于是$dp[n][8]$就降到了$dp[2][8]$。。当然用long long 会比较方便,我们只需在最后取模就好了,然后对于01的转换很明显异或运算会更加迅速和便于理解
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; typedef long long ll; const int mod=1e9+7; ll dp[2][10]; int main() { int n; scanf ("%d",&n); dp[0][0]=1; int i=1; for (int j=1; j<=n; j++){ dp[i][0]=(dp[i^1][1]+dp[i^1][3]+dp[i^1][4])%mod; dp[i][1]=(dp[i^1][0]+dp[i^1][2]+dp[i^1][5])%mod; dp[i][2]=(dp[i^1][1]+dp[i^1][3]+dp[i^1][6])%mod; dp[i][3]=(dp[i^1][0]+dp[i^1][2]+dp[i^1][7])%mod; dp[i][4]=(dp[i^1][0]+dp[i^1][5]+dp[i^1][7])%mod; dp[i][5]=(dp[i^1][1]+dp[i^1][4]+dp[i^1][6])%mod; dp[i][6]=(dp[i^1][2]+dp[i^1][5]+dp[i^1][7])%mod; dp[i][7]=(dp[i^1][3]+dp[i^1][4]+dp[i^1][6])%mod; i^=1; } printf ("%lld\n",dp[i^1][0]); return 0; }
点击就送
题目大意:计算$\sum_{i=1}^{n}\sum_{j=i}^{n} a[i]*b[j]$
Sample Input
3 1 2 3 1 2 3
Sample Output
25
emmm,没什么好说的,按照题目描述给出的代码的抄一份上去绝对是又T又WA的。。。其实数学好一点的一眼就看出来了怎么做,我们直接将这个式子拆分开来就行了:$\sum_{i=1}^{n}a_{i}\times \sum_{j=i}^{n}b_{j}$
那么答案就很明显了,记录一下$b[i]$的前缀和就可以通过$O(n)$的复杂度求出答案了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; typedef long long ll; int a[mac],b[mac]; ll sum[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%d",&a[i]); for (int i=1; i<=n; i++){ scanf ("%d",&b[i]); sum[i]=sum[i-1]+b[i]; } ll ans=0; for (int i=1; i<=n; i++){ ans+=1LL*a[i]*(sum[n]-sum[i-1]); } printf ("%lld\n",ans); return 0; }
区间异或
题目大意:给你一个区间$[l,r]$问所有子区间长度的异或和为多少
Sample Input
2 4
Sample Output
2
emmm,题目要先看懂,题目问的是子区间长度的异或和,那么针对样例的解释就是长度为1有3个分别是$[2,2],[3,3],[4,4]$那么3个1的异或和为1,有2个长度为2的,那么其异或和为0,1个长度为3的,那么总的异或值就是$1\bigoplus 3=2$
那么很明显,我们先将已知条件列出来就是
长度 数量
1 r-l+1
2 r-l
3 r-l-1
...
r-l+1 1
那么很明显,偶数个的数量我们可以直接忽略了,因为他们为0,那么剩下的就是奇数个的数量,当然我们同样可以在奇数个钟删去最大的偶数个,也就是说,剩下的长度全部只有1个,那么数量就大大缩短了,但还有5e17的数量级。
我们接下来看,剩下的长度是什么,它是由$r-l+1$所确的,即若$r-l+1$为偶数,那么我就是对所有$[1,r-l+1]$中的偶数做异或和,反之,我们就对奇数做异或和。但这个数量级还是非常大,于是我们可以看看连续奇数的异或和与连续偶数的异或和有什么规律,我们可以打个表:
#include <bits/stdc++.h> using namespace std; int main() { int n; scanf ("%d",&n); int sum=0; for (int i=2; i<=n; i+=2){//修改i的初始值为1即为奇数的连续异或和 sum^=i; printf ("%d\n",sum); } return 0; }
然后我们就会发现他们都有一个伪循环节,其长度为4。那么此题就算解决了。。。我们最多只需要计算最后3个数的异或和就完事了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; int main() { ll l,r; cin>>l>>r; ll len=r-l+1; ll ans=0; if (len&1){//如果是奇数的话,奇数所有[1,len]奇数的异或和 ll num=len/2+1; int yu=num%4; if (yu==0) cout<<0<<endl; else { int j=0; for (ll i=len; j<yu; i-=2,j++) ans^=i; cout<<ans<<endl; } } else { ll num=len/2; if (num<=3){ int j=0; for (ll i=2; j<num; i+=2,j++) ans^=i; cout<<ans<<endl; return 0; } int yu=(num-3)%4; if (yu==0) cout<<0<<endl; else { ll j=0; for (ll i=len; j<yu; i-=2,j++) ans^=i; cout<<ans<<endl; } } return 0; }
直线重合
题目大意:给你n组数据,每组数据有两个点$x1,y1,x2,y2$,问这里面有多少条不重复的直线$n<=1e5,(-1e9<=x,y<=1e9)$
Sample Input
2 0 0 1 1 1 1 2 2
Sample Output
1
emmm,验题的时候本来还以为可能细节处理会很麻烦,会WA上几发,结果一发就过了。。。
由于是要计算直线的重合,所以我们要准确的,不能使用double之类的含有误差的。那么判断直线是否重合只需要2个条件就好了,一条直线可以表示为$y=kx+b$(当k存在),然后特判一下(k不存在的情况就好了)这里的$k,b$不一定是整数,所以我们要用分数将其准确化,那么k可以表示为$k=\frac{y1-y2}{x1-x2}$之后我们再约分就好了。同样我们假设$k=\frac{q}{p}$,那么由于$x,y$都是整数,可得$b=\frac{py-qx}{p}$,然后我们用pair嵌套map将其分子分母标记就好了,对于$x1==x2$的情况我们将其特殊化:
if (x1==x2){ qk.first=x1; qk.second=0;//first存分子,second存分母 qb.first=0; qb.second=0; }
接下来就是一些小细节的处理了,(1)当该分数为负的时候可能出现$\frac{-1}{2},\frac{1}{-2}$的情况,我们要将负号全部转到分子上或者分母上就好了
(2)当分子出现0的情况可能会出现$\frac{0}{3},\frac{0}{4}$的情况,所以我们要特殊处理一下,将分母全部转化到值域之外
可能是出题人有点懒,不想造数据。。。所以这些细节不用处理也可以过。。。nmd
然后我去问了一下出题人。。。于是我就遭受了一顿社会的毒打,实际上gcd是会自动处理这些细节的。。。hnb。。。。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const int inf=1e9+7; map<pair<pair<int,int>,pair<int,int>>,bool>q; int gcd(int a,int b) { return b==0?a:gcd(b,a%b); } int main() { int n,cnt=0; scanf ("%d",&n); for (int i=1; i<=n; i++){ pair<int,int>qk,qb; int x1,x2,y1,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); if (x1==x2){ qk.first=x1; qk.second=0; qb.first=0; qb.second=0; } else { int b,a; b=y1-y2; a=x1-x2; int u=gcd(a,b); b/=u; a/=u; if (!b) a=inf; if (1.0*b/a<0) { if (a<0) b=-b,a=-a; } qk.first=b; qk.second=a; b=a*y1-b*x1; u=gcd(b,a); b/=u; a/=u; if (!b) a=inf; if (1.0*b/a<0){ if (a<0) b=-b,a=-a; } qb.first=b; qb.second=a; } if (!q[make_pair(qk,qb)]) {cnt++; q[make_pair(qk,qb)]=1;} } printf ("%d\n",cnt); return 0; }
以下是清爽版AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const int inf=1e9+7; map<pair<pair<int,int>,pair<int,int>>,bool>q; int main() { int n,cnt=0; scanf ("%d",&n); for (int i=1; i<=n; i++){ pair<int,int>qk,qb; int x1,x2,y1,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); if (x1==x2){ qk.first=x1; qk.second=0; qb.first=0; qb.second=0; } else { int b,a; b=y1-y2; a=x1-x2; int u=__gcd(a,b); b/=u; a/=u; qk.first=b; qk.second=a; b=a*y1-b*x1; u=__gcd(b,a); b/=u; a/=u; qb.first=b; qb.second=a; } if (!q[make_pair(qk,qb)]) {cnt++; q[make_pair(qk,qb)]=1;} } printf ("%d\n",cnt); return 0; }