T1 海龟
题目大意:给你n个点,依次连接形成一条折线,问这条折线经过了多少个整点
真的不是刀剑里的那个海龟
考试的时候写了两个程序,一个是枚举矩阵里的点带进函数,一个是枚举x算出y,后者写挂了,但是对拍的时候考试用的电脑没法用fc,人工对比耗费了不少时间而且还没对比出来,于是把两个程序混合起来只有60分
想法很简单,每次枚举一条线段覆盖的最小矩阵的所有点,带进直线方程看看在不在直线上,用bool数组标记就好了.
正解好像是用gcd模拟
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 |
#include<bits/stdc++.h> using namespace std; int x=0,y=0; int n; bool vis[1003][1003]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ int now_x,now_y; scanf("%d%d",&now_x,&now_y); vis[now_x][now_y]=1; if(now_x==x&&now_y==y)continue; int s_x,b_x,s_y,b_y; s_x=min(x,now_x);b_x=max(x,now_x); s_y=min(y,now_y);b_y=max(y,now_y); if(now_x==x){ for(int j=s_y;j<=b_y;j++){ vis[x][j]=1; } } else if(now_y==y){ for(int j=s_x;j<=b_x;j++){ vis[j][y]=1; } } else if(n<=1000){ double k=abs(now_y-y)*1.0/abs(now_x-x)*1.0; if((x<now_x&&y>now_y)||(now_x<x&&now_y>y))k=-k; double b=y-k*x; for(int j=s_x;j<=b_x;j++){ for(int z=s_y;z<=b_y;z++){ if(fabs(z-k*j-b)<1e-5){ vis[j][z]=1; } } } } x=now_x,y=now_y; } int cnt=0; for(int i=0;i<=1000;i++){ for(int j=0;j<=1000;j++){ if(vis[i][j]==1){ cnt++; } } } cout<<cnt<<endl; return 0; } |
T2 子集
题目大意:求有n个元素的集合里有多少个元素数目不超过k的子集数目,对m取"膜",不保证m是质数
骗我写一发逆元组合数,然而只过了暴力的分..
前40分是杨辉三角递推
后40分是逆元(但是要有技巧的统计答案)
先上80分代码:
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 |
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=200005; int n,k; ll mod,ans; ll dat[MAXN],inv[MAXN],fac[MAXN],f[2003][2003]; ll qpow(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 prework(){ for(int i=1;i<=2000;i++)f[i][0]=f[i][i]=1; for(int i=2;i<=2000;i++)for(int j=1;j<=i;j++)f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod; } int main(){ scanf("%d%d%lld",&n,&k,&mod); if(n<=2000){ prework(); ans=1; for(int i=1;i<=k;i++)ans=(ans+f[n][i])%mod; printf("%lld\n",ans); } else { ans=1; fac[0]=dat[0]=1; for(int i=1;i<=k;i++){ fac[i]=fac[i-1]*i%mod; dat[i]=dat[i-1]*(n-i+1)%mod; inv[i]=qpow(fac[i],mod-2); ans=(ans+dat[i]*inv[i]%mod)%mod; } printf("%lld\n",ans); } return 0; } |
我们首先来抽象化一下这道题到底要求什么,显然,其实它就是求一个式子:
\(\displaystyle \sum_{i\in[0,k]} C^i_n mod m\)
我们之前用费马小定理为什么会失败呢?没错,就是m不保证是质数!
于是我们要用到费马小定理的一般情况的定理-欧拉定理
对于一般的组合式公式我们可以进行以下化简:\(C_n^m=\frac{n!}{m!(n-m)!}=\frac{n(n-1)\cdots (n-m+1)}{m!}\)
由于我们要求的式子n是不变的,所以分子每次可以用上次的分子递推(上面80分的程序也用了这种方法)
而对于我们的正解,我们甚至用ans来保存上一项的答案(可以直接用到当前项)
对于每一个分子,我们将mod的质因子提取出来并计数(加法),剩下来的一定与mod互质,与ans相乘.
对于每一个分母,我们将mod的质因子提取出来并计数(减法),剩下来的一定与mod互质,与ans相除(乘上逆元).
这里的逆元用的是欧拉定理,即\(a^{\varphi(n)}\equiv 1(mod\ n)\),其中a,n互质
我们不能直接将ans统和计入答案,我们还必须讲每个用ans乘上每个质因子才能计入答案统和
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 |
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=200005; int n,k; ll mod,ans=1; ll p[MAXN],c[MAXN],cnt; int phi; void divide(int n){ cnt=0;phi=n; for(int i=2;i<=sqrt(n);i++){ if(n%i==0){ phi=phi/i*(i-1); p[++cnt]=i; while(n%i==0)n/=i; } } if(n>1){phi=phi/n*(n-1);p[++cnt]=n;} } ll qpow(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 cheng(int x) { for(int i=1;i<=cnt;i++) { while(x%p[i]==0) { c[i]++; x/=p[i]; } } ans=1ll*ans*x%mod; } void chu(int x) { for(int i=1;i<=cnt;i++) { while(x%p[i]==0) { c[i]--; x/=p[i]; } } ans=1ll*ans*qpow(x,phi-1)%mod; } int main(){ scanf("%d%d%lld",&n,&k,&mod); ll su=0; divide(mod); for(int i=0;i<=k;i++){//从0开始,因为有空集 if(i>=1){cheng(n-i+1);chu(i);} int t=ans; for(int j=1;j<=cnt;j++){ t=1ll*t*qpow(p[j],c[j])%mod; } su=(su+t)%mod; } printf("%lld\n",su); return 0; } |