区域赛前整理

数据结构

复杂度分析

在这里插入图片描述

输入输出

在这里插入图片描述

结构体优先队列

在这里插入图片描述

倍增

样我们需要枚举长度d,又因为1≤N≤500000,所以可以考虑到二分范围,但当T比较小的时候,需要的d较小而二分到小和大值都比较慢,所以为了解决这个问题可以使用倍增的方法,即d从1开始,如果合适 d ∗ * =2,否则d/=2.

字符串

字典树

#include <bits/stdc++.h>
using namespace std;
#define eps 1e-5
const int mx=100100;
char ch[mx];
int a[mx][30],n,m,cnt[mx],tot;
void insert()
{
	int s=0;//头结点设为0
	for(int i=0;i<strlen(ch);i++){
		if(a[s][ch[i]-'a']==0){
			a[s][ch[i]-'a']=++tot;   //记录下个字符的位置
		}
		s=a[s][ch[i]-'a'];   //记录当前指针
	}
	cnt[s]++;  //统计个数
}
int query(){
	int ans=0,p=0;
	for(int i=0;i<strlen(ch);i++){
		p=a[p][ch[i]-'a'];
		if(p==0)  return ans;
		ans+=cnt[p];
		//printf("%d\n",cnt[p]);
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++){
		scanf("%s",ch);
		insert();
	}
	for(int i=0;i<m;i++){
		scanf("%s",ch);
		printf("%d\n",query());
	}
   return 0;
}


哈夫曼树

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

using namespace std;
pair<ll,int>t;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
ll ans=0,x;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%lld",&x);
        q.push({x,0});
    }
    while((n-1)%(k-1))
        q.push({0,0}),n++;
    while(q.size()>1){
        ll sum=0;
        int mx=0;
        for(int i=0;i<k;i++){
            sum+=q.top().first;
            mx=max(mx,q.top().second);
            q.pop();
        }
        ans+=sum;
        //printf("%lld\n",ans);
        q.push({sum,mx+1});
    }
    printf("%lld\n%d\n",ans,q.top().second);
    return 0;
}

哈希

hash的时候注意取余,一般是比输入值略小的素数。

//数值
const ll mod=99991;
int n,flag=1;
ll x[100010][7];
vector<ll >q[99991];
ll f(int pos){
    ll sum=0,mul=1;
    for(int i=0;i<6;i++){
        sum=(sum+x[pos][i])%mod;
        mul=(mul*x[pos][i])%mod;
    }
    return (sum+mul)%mod;
}
//字符串
scanf("%s",ch+1);
    int a,b,x,y,n,len=strlen(ch+1);
    val[0]=1;   //因为是左移的位数所以初始值为1
    for(int i=1;i<=len;i++){
        sum[i]=sum[i-1]*131+ch[i]-'a'+1;   //ch[i]-'a'+1防止出现某一位为0的情况
        val[i]=val[i-1]*131;
    }
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d%d%d%d",&a,&b,&x,&y);
        if(sum[b]-sum[a-1]*val[b-a+1]==sum[y]-sum[x-1]*val[y-x+1])  //注意补位
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;

数学相关

矩阵快速幂

1.0
const int N=10;
int tmp[N][N];
void multi(int a[][N],int b[][N],int n)
{
	memset(tmp,0,sizeof tmp);
	for(int i=0;i<n;i++) 
		for(int j=0;j<n;j++)
			for(int k=0;k<n;k++)
				tmp[i][j]+=a[i][k]*b[k][j];
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			a[i][j]=tmp[i][j];
}
int res[N][N];
void Pow(int a[][N],int n)
{
	memset(res,0,sizeof res);//n是幂,N是矩阵大小
	for(int i=0;i<N;i++) 
		res[i][i]=1;
	while(n)
	{
		if(n&1)
		multi(res,a,N);//res=res*a;复制直接在multi里面实现了;
			multi(a,a,N);//a=a*a
		n>>=1;
	}
}
2.0
struct Mat
{
    LL m[101][101];
};//存储结构体
Mat a,e; //a是输入的矩阵,e是输出的矩阵
Mat Mul(Mat x,Mat y)
{
    Mat c;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            c.m[i][j] = 0;
        }
    }
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            for(int k=1;k<=n;++k){
                c.m[i][j] = c.m[i][j]%mod + x.m[i][k]*y.m[k][j]%mod;
            }
        }
    }
    return c;
}
Mat pow(Mat x,LL y)//矩阵快速幂
{
    Mat ans = e;
    while(y){
        if(y&1) ans = Mul(ans,x);
        x = Mul(x,x);
        y>>=1;
    }
    return ans;
}

期望:

我们有一个长为L的pokey(是杏子,幻视ing),现有一个程序,如果L>d,则在L上等可能的选择任意一点,吃掉左边,得到新的L,然后重复此程序,求程序执行次数的期望。
即长为L的木棍,当L>d时,每次等可能的去掉X(0<X<=L),求操作次数的期望。
设f(x)为长度为x的pokey的操作次数的期望。
则f(x)=0 (x<=d)
f(x)=1+ ∫ 0 d f ( t )   d t / x \int_{0}^{d} f(t) \,dt/x 0df(t)dt/x+ ∫ d x f ( t )   d t / x \int_{d}^{x} f(t) \,dt/x dxf(t)dt/x (x<=d) (因为长度小于等于d时就不再继续了,所以第一个积分值为0)
=1+(F(x)-F(d)) /x (因为取[d,x]是等可能的所有除以x)

f ( x ) ˙ \dot{f(x)} f(x)˙= ( x ∗ F ( x ) ˙ − ( F ( x ) − F ( d ) ) ) / ( x 2 ) (x*\dot{F(x)}-(F(x)-F(d)))/(x^2) (xF(x)˙(F(x)F(d)))/(x2)求导
= ( x ∗ f ( x ) − ∫ d x f ( t )   d t ) / ( x 2 ) (x*f(x)-\int_{d}^{x} f(t) \,dt)/(x^2) (xf(x)dxf(t)dt)/(x2)
= ( x ∗ f ( x ) − x ∗ f ( x ) + x ) / ( x 2 ) (x*f(x)-x*f(x)+x)/(x^2) (xf(x)xf(x)+x)/(x2)
=1/x
所以 f ( x ) = l n x + c f(x)=lnx+c f(x)=lnx+c
lim ⁡ x → + d f ( x ) \lim_{x\rightarrow+d} f(x) limx+df(x)=1(因为L=d的时执行)
所以c= 1 − l n d 1-lnd 1lnd
则得到最终答案 f ( x ) = l n x − l n d + 1 f(x)=lnx-lnd+1 f(x)=lnxlnd+1

#include <bits/stdc++.h>
using namespace std;

int main()
{
	int t,n,a,b;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		long long ans=0;
		for(int i=0;i<n;i++){
			scanf("%d%d",&a,&b);
			ans+=(long long)a*b;
		}
		printf("%lld\n",ans);
	}
    return 0;
}

倍数

给出正整数 n 和 m,统计满足以下条件的正整数对 (a, b) 的数量:

  1. 1 <= a <= n, 1 <= b <= m;
  2. a × b 是 2016 的倍数。
    因为( a ∗ b a*b ab)%mod=0 <=> (a%mod) ∗ * (b%mod)%mod.
    所以 ( n ∗ m n*m nm)%2016=0我们可以改写成 (a%2016) ∗ * (b%2016)%2016=0
    然后我们将式子写成( a ∗ 2016 + x a*2016+x a2016+x) ∗ * ( b ∗ 2016 + y b*2016+y b2016+y)%2016=0 =>
    ( a ∗ b ∗ 201 6 2 + x ∗ b ∗ 2016 + y ∗ a ∗ 2016 + x ∗ y a*b*2016^2+x*b*2016+y*a*2016+x*y ab20162+xb2016+ya2016+xy)%2016=0
    所有上式能否成立取决于x*y是否为2016的倍数
    而当x>2016时可以改写成( x / 2016 ∗ 2016 + x x/2016*2016+x x/20162016+x%2016)(此处/为向下取整)
    所以x、y的范围就变成了[1,2016]
    又因为当 x ∗ y x*y xy%2016=0时,a和b可以取任意不超过范围的整数,所以方案则有(n-i)/2016+1中(1代表a=0时,(n-i)/2016代表a非0时)(b同理)
#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF){
		long long ans=0;
		for(int i=1;i<=min(2016,n);i++){
			for(int j=1;j<=min(2016,m);j++){
				if(i*j%2016==0){
					long long a=(n-i)/2016+1;
					long long b=(m-j)/2016+1;
					ans+=a*b;
				}
			}
		}
		printf("%lld\n",ans);
	}
    return 0;
}

快速乘

long long pw(long long a,long long b){
	long long ans=1;
	while(b){
		if(b&1)
			ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans%mod;
}

差分约束

每次操作对[L,R]的所有数进行+1操作,求最后有多少个奇数。
注意左右端点和具体L、R。

图论

最短Hamilton路径

给定一张 n个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
数据保证
1≤n≤20
a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。

因为从0开始到n-1结束,且每个点只走一遍。
又因为不超过20,所以可以用二进制的01表示每个点是否走过
例如:010011 表示走过了1号、2号、5号节点
然后可以得出状态转移方程x[i][j]=min(x[i][j],x[i^(1<<j)][k]+a[k][j]);
x[i][j]表示状态i的时候停留在j点的最短路径(状态i转换成二进制后则为走过的路径)
例如:x[5][3]表示目前经过1号点和3号点,现在停留在3号点的最短路径
注意点:x数组的一维要开到1<<20;需要赋初值。
(i>>j)&1:判断的是j点是否已经在i路径里了
((i^(1<<j))>>k&1):判断的是不包含j点的路径是否含有k

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int inf= 0x3f3f3f3f;
int x[1<<20][20]; 
int main()
{
    int a[50][50],n;
    scanf("%d",&n);
    memset(x,inf,sizeof(x));
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)
            scanf("%d",&a[i][j]);
    }
    x[1][0]=0;
    for(int i=1;i<(1<<n);i++){
        for(int j=0;j<n;j++){
            if((i>>j)&1){
                for(int k=0;k<n;k++){
                    if((i^(1<<j))>>k&1){
                        x[i][j]=min(x[i][j],x[i^(1<<j)][k]+a[k][j]);
                    }
                }
            }
        }
    }
    printf("%lld\n",x[(1<<n)-1][n-1]);
    return 0;
}

DFS判环

int vis[300010],tim,pre[300010];
struct stu{
	int v;
};
vector<stu> e[300010];
vector<int> temp;
void add(int a,int b){
	stu t;
	t.v=b;
	e[a].push_back(t);
}
void dfs(int u){
	vis[u]=++tim;   //时间戳
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i].v;
		if(v==pre[u])	continue;
		if(!vis[v]){
			pre[v]=u;
			dfs(v);
		}
		else if(vis[v]>vis[u]){
			int t=v;
			int now=0;
			while(t!=u){
				now++;
				t=pre[t];
			}
			now++;
			temp.push_back(now);
		}
	}
}
int main()
{
	for(int i=1;i<=n;i++){
		if(!vis[i])
			dfs(i);
	}
	for(int i=0;i<temp.size();i++){
		int v=temp[i];
		ans=ans*(pw(2,v)-1)%998244353;
		m-=v;
	}
}

BFS搜路径

Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 v 开始、点 v 结束的路径)。
为了方便,点用 1,2,…,n 编号。设 c o u n t ( x , y ) c o u n t ( x , y ) \mathrm{count}(x, y)count(x,y) count(x,y)count(x,y) 表示点 x 到点 y 不同的路径数量(规定 c o u n t ( x , x ) = 0 \mathrm{count}(x, x) = 0 count(x,x)=0),Bobo 想知道链
∑ i = 1 n ∑ j = 1 n c o u n t ( i , j ) ⋅ a i ⋅ b j \sum_{i=1}^{n}\sum_ {j=1}^{n}count(i,j)⋅a i⋅b j i=1nj=1ncount(i,j)aibj,除以 ( 1 0 9 10^9 109+7)的余数。其中, a i , b j a_i, b_j ai,bj是给定的数列。
根据 a ∗ b + c ∗ b = ( a + c ) ∗ b a*b+c*b=(a+c)*b ab+cb=(a+c)b这一性质,如果存在类似x->y->z这种路, a [ x ] ∗ b [ y ] + a [ x ] b [ z ] + a [ y ] b [ z ] a[x]*b[y]+a[x]b[z]+a[y]b[z] a[x]b[y]+a[x]b[z]+a[y]b[z],可以写成 a [ x ] ∗ b [ y ] + ( a [ x ] + a [ y ] ) ∗ b [ z ] a[x]*b[y]+(a[x]+a[y])*b[z] a[x]b[y]+(a[x]+a[y])b[z]即x->z这条路可以在求y->z时求出。
这样我们把起点(即入度为0的点)入队开始搜索,找到他的所有出度,并使出度点的a[V]加上入度的点a[u],然后如果当前出度点的入度为0了,就把它也入队。

#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
ll a[100010],b[100010],in[100010];
vector<int> v[100010];
queue<int> q;
int main()
{
	int n,m,x,y;
	while(scanf("%d%d",&n,&m)!=EOF){
		for(int i=0;i<=n;i++)
			v[i].clear();
		memset(in,0,sizeof(in));
		long long ans=0;
		for(int i=1;i<=n;i++){
			scanf("%lld%lld",&a[i],&b[i]);
		}
		for(int i=1;i<=m;i++){
			scanf("%d%d",&x,&y);
			v[x].push_back(y);
			in[y]++;
		}
		for(int i=1;i<=n;i++){
			if(!in[i])
				q.push(i);
		}
		while(!q.empty()){
			int u=q.front();
			q.pop();
			for(int i=0;i<v[u].size();i++){
				int v1=v[u][i];
				ans=(ans+ksc(a[u],b[v1]))%mod;
				a[v1]=(a[v1]+a[u])%mod;
				in[v1]--;
				if(!in[v1]){
					q.push(v1);
				}
			}
		}
		printf("%lld\n",ans);
	}
    return 0;
}

SLF优化的SPFA

//SLF优化的SPFA
#include <bits/stdc++.h>
using namespace std;
struct stu
{
    int next;
    int cost;
};
vector<stu> tu[100100];
int dis[25010],vis[25010];
void add(int a,int b,int c){
    stu t;
    t.next=b;
    t.cost=c;
    tu[a].push_back(t);
}
void spfa(int s)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    deque<int> q;  //双端队列
    vis[s]=1;
    dis[s]=0;
    q.push_back(s);
    while(!q.empty()){
        int x=q.front();
        vis[x]=0;
        q.pop_front();
        for(int i=0;i<tu[x].size();i++){
            int v=tu[x][i].next;
            int c=tu[x][i].cost;
            if(dis[v]>dis[x]+c){
                dis[v]=dis[x]+c;
                if(!vis[v]){
                    vis[v]=1;
                    if(q.size()&&dis[v]<dis[q.front()])
                        q.push_front(v);
                    else
                        q.push_back(v);
                }
            }
        }
    }
}
int main()
{
    int t,r,p,s,a,b,c;
    scanf("%d%d%d%d",&t,&r,&p,&s);
    for(int i=0;i<r;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    for(int i=0;i<p;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    spfa(s);
    for(int i=1;i<=t;i++){
        if(dis[i]==0x3f3f3f3f)
            printf("NO PATH\n");
        else
            printf("%d\n",dis[i]);
    }

    return 0;
}


多层图

在郊区有 N 座通信基站,P 条双向电缆,第 i 条电缆连接基站Ai和Bi。
特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。
现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费Li。
电话公司正在举行优惠活动。
农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。
农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。
求至少用多少钱可以完成升级。

如果不看k条免费的部分,那本题就是求一个从1到N的最短路,然后得到这条路径上最大的边权值,即求的是最短路上的最大边。
然后加上k条免费的部分,这使得每一条边都有0和C(C为原来的边权)两种选择。这样我就可以建分层图,即建立一个新的维度使每条边都有两个分支在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
int n,m,k;
struct stu{
    int next;
    int c;
};
vector<stu> p[20020];
int dis[1010][1010],vis[1010];
void add(int a,int b,int c){
    stu t;
    t.c=c;
    t.next=b;
    p[a].push_back(t);
}
void spfa(int s){
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int> t;
    vis[s]=1;
    dis[s][0]=0;
    t.push(s);
    while(!t.empty()){
        int x=t.front();
        t.pop();
        vis[x]=0;
        for(int i=0;i<p[x].size();i++){
            int v=p[x][i].next;
            int c=p[x][i].c;
            int w=max(dis[x][0],c);
            if(dis[v][0]>w){
                dis[v][0]=w;
                if(!vis[v]){
                    t.push(v);
                    vis[v]=1;
                }
            }
            for(int j=1;j<=k;j++){
                w=min(dis[x][j-1],max(dis[x][j],c));
                if(dis[v][j]>w){
                    dis[v][j]=w;
                    if(!vis[v]){
                        t.push(v);
                        vis[v]=1;
                    }
                }
            }
        }
    }
}
int main()
{
    int a,b,c;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    spfa(1);
    int ans=1e9;
    for(int i=0;i<=k;i++)
        ans=min(ans,dis[n][i]);
    if(ans==1e9)
        ans=-1;
    printf("%d\n",ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值