学习内容
数学
排列组合
高中数学的排列组合+代码实现
排列省略
组合数求法
1.对于Cnm
递推求组合O(n2)
for(int i=1;i<=n;++i) c[i][0]=1,c[i][i]=1;
for(int i=1;i<=n;++i)
for(int i=2;i<=n;++i)
c[i][j]=c[i][i-j]=c[i-1][j]+c[i-1][j-1];
2.对于Cnm%p
(1) 同上递推
(2) 1<=m<=n<=106,n<=p<=109,p为质数,可以预处理出jc[i]%p,利用乘法逆元求。复杂度O(n+logn)。
oid calJc() //求maxn以内的数的阶乘
{
Jc[0] = Jc[1] = 1;
for(LL i = 2; i < maxn; i++)
Jc[i] = Jc[i - 1] * i % mod;
}
void exgcd(LL a, LL b, LL &x, LL &y) //拓展欧几里得算法
{
if(!b) x = 1, y = 0;
else
{
exgcd(b, a % b, y, x);
y -= x * (a / b);
}
}
LL niYuan(LL a, LL b) //求a对b取模的逆元
{
LL x, y;
exgcd(a, b, x, y);
return (x + b) % b;
}
LL C(LL a, LL b) //计算C(a, b)
{
return Jc[a] * niYuan(Jc[b], mod) % mod* niYuan(Jc[a - b], mod) % mod;
}
(3) 1<=m<=n<=1018,p<=105,p为质数,用lucas定理,时间复杂度O(logpn*(p+logp))
ll lucas(ll n,ll m,ll p)//m>n,C(n,m)=0;m=0,C(n,m)=1
{
if(m==0) return 1;
return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}
(4)p是合数,扩展Lucas
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
ll pow(ll a, ll b, ll m)
{
ll ans = 1;
a %= m;
while(b)
{
if(b & 1)ans = (ans % m) * (a % m) % m;
b /= 2;
a = (a % m) * (a % m) % m;
}
ans %= m;
return ans;
}
ll extgcd(ll a, ll b, ll& x, ll& y)
//求解ax+by=gcd(a, b)
//返回值为gcd(a, b)
{
ll d = a;
if(b)
{
d = extgcd(b, a % b, y, x);
y -= (a / b) * x;
}
else x = 1, y = 0;
return d;
}
ll mod_inverse(ll a, ll m)
//求解a关于模上m的逆元
//返回-1表示逆元不存在
{
ll x, y;
ll d = extgcd(a, m, x, y);
return d == 1 ? (m + x % m) % m : -1;
}
ll Mul(ll n, ll pi, ll pk)//计算n! mod pk的部分值 pk为pi的ki次方
//算出的答案不包括pi的幂的那一部分,即第一部分
{
if(!n)return 1;
ll ans = 1;
if(n / pk)//第三部分
{
for(ll i = 2; i <= pk; i++) //求出循环节乘积
if(i % pi)ans = ans * i % pk;
ans = pow(ans, n / pk, pk); //循环节次数为n / pk
}
for(ll i = 2; i <= n % pk; i++)//第三部分中多余的后半部分
if(i % pi)ans = ans * i % pk;
return ans * Mul(n / pi, pi, pk) % pk;//递归求解第二部分
}
ll C(ll n, ll m, ll p, ll pi, ll pk)//计算组合数C(n, m) mod pk的值 pk为pi的ki次方
{
if(m > n)return 0;
ll a = Mul(n, pi, pk), b = Mul(m, pi, pk), c = Mul(n - m, pi, pk);
ll k = 0, ans;//k为pi的幂值
for(ll i = n; i; i /= pi)k += i / pi;//算上递归部分没有计算的pi^(n/pi)
for(ll i = m; i; i /= pi)k -= i / pi;
for(ll i = n - m; i; i /= pi)k -= i / pi;
ans = a * mod_inverse(b, pk) % pk * mod_inverse(c, pk) % pk * pow(pi, k, pk) % pk;//ans就是n! mod pk的值
ans = ans * (p / pk) % p * mod_inverse(p / pk, pk) % p;//此时用剩余定理合并解
return ans;
}
ll Lucas(ll n, ll m, ll p)
{
ll x = p;
ll ans = 0;
for(ll i = 2; i <= p; i++)
{
if(x % i == 0)
{
ll pk = 1;
while(x % i == 0)pk *= i, x /= i;
ans = (ans + C(n, m, p, i, pk)) % p;
}
}
return ans;
}
int main()
{
ll n, m, p;
while(cin >> n >> m >> p)
{
cout<<Lucas(n, m, p)<<endl;
}
return 0;
}
矩阵快速幂
矩阵数学意义:https://www.cnblogs.com/alantu2018/p/8528299.html
代码(与快速幂类似)
#include<bits/stdc++.h>
using namespace std;
const int p=1000000000+7;
struct jz
{
long long a[110][110];
};
jz A;
int n;
long long k;
jz mul(jz a,jz b)
{
jz c;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
c.a[i][j]=0;
for(int k=1;k<=n;++k)
{
c.a[i][j]+=a.a[i][k]*b.a[k][j];
c.a[i][j]%=p;
}
}
return c;
}
jz quickpow(jz a,long long b)
{
jz ans,base=a;
for(int i=1;i<=n;++i) ans.a[i][i]=1;//³õʼ»¯Îªµ¥Î»¾ØÕó
while(b)
{
if(b&1) ans=mul(ans,base);
base=mul(base,base);
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) scanf("%lld",&A.a[i][j]);
jz ans=quickpow(A,k);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j) printf("%lld ",ans.a[i][j]);
printf("\n");
}
}
概率与期望
学好数学,什么都没问题。
字符串hash(表)
hash表
把要存的书对一个大质数求余,作为下标,存入数组,若有重复用邻接表存。
int add(string h)//添加
{
int sum=hash(h);
int u=first[sum];
++cnt;
data[cnt]=h;
next[cnt]=first[sum];
first[sum]=cnt;
return 1;
}
int find(string h)//查找
{
int sum=hash(h);
int u=first[sum];
while(u)
{
if(data[u]==h) return 1;//找到了
u=next[u];
}
return 0;
}
字符串hash
一个字符串,用数字表示。设sum[k]表示前k个字符构成的字符串的哈希值,b进制,则任意区间[l,r]的子串的哈希值为:sum[r]-sum[l-1]*br-l+1,有点类似前缀和。需要预处理sum[]和bm,复杂度O(n+m)。
int st=0;
scanf("%s",a+1);//查询的字符串(全大于等于'A')
scanf("%s",s+1);//长的
int m=strlen(a+1),n=strlen(s+1);
for(int i=1;i<=m;++i) st=(st*b+a[i]-'A')%p;//查询的hash值
bn[0]=1;
for(int i=1;i<=1000000;++i) bn[i]=bn[i-1]*b%p;//预处理b^n
for(int i=1;i<=n;++i) sum[i]=(sum[i-1]*b+s[i]-'a)%p;
for(int i-1;i<=n-m;++i)
if(sum[i+m-1]*bn[m]==st)
printf("区间(%d,%d)匹配\n",i,i+m-1);
状压DP
对于状态复杂,但可以合并的DP可使用2进制表示状态进行处理,如一个棋盘一行有7个格,用1表示有棋子,0表示没有,可以以行的情况如0000101为一个维度,表示第5、7格有棋子。
例题:
有n个原子,任意两个原子互相撞击会产生一定的能量,并且被撞击的那个会消失,然后要你求当n个原子发生了n-1次撞击后能产生的最大能量。
输入:包含多组实例.每个实例的第一行是N(2<=N<=10),然后接下来N行,每行有N个整数x(0<=x<=1000),第i行的第j个数,表示i原子撞击j原子后产生的能量,且j消失.当N为0时表输入结束.
输出:最大能量。
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int v[15][15];
int d[1<<10];
int main()
{
while(scanf("%d",&n)==1&&n)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&v[i][j]);
memset(d,-1,sizeof(d));
d[0]=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)if(i!=j)//用j撞i销毁i
d[(1<<i)]=max( d[(1<<i)] , v[j][i] );
int ans = 0;
for(int S=0;S<(1<<n);S++)//枚举所有子状态
{
for(int i=0;i<n;i++)if( !(S&(1<<i)) && d[S]!=-1)//i不在S中
{
for(int j=0;j<n;j++)if(j!=i && !(S&(1<<j)))//j不在S中,从j到i或从i到j
{
d[S|(1<<i)] = max(d[S|(1<<i)],d[S]+v[j][i]);//从j到i,废弃i
d[S|(1<<j)] = max(d[S|(1<<j)],d[S]+v[i][j]);//从i到j,废弃j
ans = max(ans,max(d[S|(1<<j)],d[S|(1<<i)]));
}
}
}
printf("%d\n",ans);
}
return 0;
}
树与图
基环树
基环树就是一棵树上多了一条边,形成了一个环。
最重要的是找环。之后用断边方法化为树,取符合题目要求的。
找环:三个方法(来自LWJ)
void dft(int rt, int fa)//深度优先遍历找环
{
vis[rt] = 1;
for(int i=h[rt];i;i=edg[i].next)
{
if(edg[i].y == fa) continue;
if(!vis[edg[i].y]) dft(edg[i].y, rt);
else{
not_pass = i;
ring1 = edg[i].y;ring2 = rt;
}
}
}
void topo()//toposort找环
{
head=0,tail=1;
for(reg int i=1;i<=n;++i)
{
if(indeg[i]==1)
{
que[tail++]=i;
}
}
while(head!=tail)
{
++head;
int x=que[head];
for(reg int i=0;i<q[x].size();++i)
{
int y=q[x][i];--indeg[y];
if(indeg[y]==1)
{
que[tail++]=y;
}
}
}
}
for(int i=1;i<=n;++i) //链表找环
{
if(belong[i]!=0) continue;
int j=i;
while(!belong[j])
{
belong[j]=i;j=l[j];
}
if(belong[j]==i)
{
int num=0;
while(belong[j]!=-1)
{
belong[j]=-1;++num;j=l[j];
}
}
}
差分约束
给定一组二元不等式,计算满足该不等式的值得最大值或最小值,由于x+a<=y与三角不等式相似,故可转化为图的最长或最短路。
具体见偷来的讲解
桥与割点(tarjan)
桥的找不到了
先来个割点的
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int minn(int a,int b)
{
if(a>b) return b;
else return a;
}
int qr()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){ x=x*10+c-'0';c=getchar();}
return f*x;
}
int cnt=1,cut[2000010],h[1000010],vis,vistimes=0,dfn[2000010],low[2000010];
struct bian
{
int next,to,t;
}edg[1000010];
void add(int x,int y)
{
edg[++cnt].to=y;
edg[cnt].next=h[x];
h[x]=cnt;
}
void tarjan(int x,int root)
{
++vistimes;dfn[x]=low[x]=vistimes;
int flag=0;
for(int i=h[x];i;i=edg[i].next)
{
int y=edg[i].to;
if(!dfn[y])
{
tarjan(y,root);
low[x]=minn(low[x],low[y]);
if(low[y]>=dfn[x])
{
++flag;
if(x!=root||flag>1) cut[x]=1;
}
}
else low[x]=minn(low[x],dfn[y]);
}
}
int main()
{
int n,m;
n=qr(),m=qr();
for(int i=1;i<=m;++i)
{
int x=qr(),y=qr();
add(x,y);add(y,x);
}
for(int i=1;i<=n;++i)
{
if(!dfn[i]) tarjan(i,i);
}
int ans=0;
for(int i=1;i<=n;++i)
{
if(cut[i]) ++ans;
}
printf("%d\n",ans);
for(int i=1;i<=n;++i)
{
if(cut[i])
printf("%d ",i);
}
return 0;
}
树上差分
就是树上的差分
每个点的差分数组代表从该点到树的根上每一点或边的改变量。
统计点或边时,其改变量为 以改点为根的树每一点的差分值的和。
例题:松鼠的新家