暑期学习总结

学习内容

数学

排列组合

高中数学的排列组合+代码实现
排列省略
组合数求法
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;
}

树上差分

就是树上的差分
每个点的差分数组代表从该点到树的根上每一点或边的改变量。
统计点或边时,其改变量为 以改点为根的树每一点的差分值的和。
例题:松鼠的新家

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值