ACM模板(自用)

手动开-o2优化

#pragma GCC optimize (2)//淦怎么会有憨批不给开-o2

一行读入不定数量string

#include<sstream>
string line,x;
while (getline(cin, line))
{
	stringstream ss(line);
	cnt = 0;
	bool ok = 0;
	while (ss >> x)
	{
		ok = 1;
		s[++cnt] = x;
	}
	if (ok)
		break;
}

常用头文件

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<stack>
#include<string>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
/*——————————————————————————————————————————————————————————*/
#include<bits/stdc++.h>//万能头文件 
比赛时注意是否支持万能头!

读入优化

#include<bits/stdc++.h>
using namespace std;
#define LL long long
inline LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}

并查集

//https://www.luogu.org/problem/P3367
//题目大意:给定n个数 查询m次 1合并 2查询 判断是否在同一集合内
#include<bits/stdc++.h>
using namespace std;
#define LL long long
inline LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,z,x,y,fa[10005];
int find(int xx)//寻找xx的根(路径压缩版) 
{
	int r=xx; 
	while(fa[r]!=r)//找到xx的根,用r记录 
	{
		r=fa[r];
	}
	int now=xx,pr;//now代表当前节点 pr代表当前节点的上一级 
	while(now!=r)//将节点x以及x到根节点中间所有的点进行路径压缩 
	{
		pr=fa[now];//记录当前节点的上一级 
		fa[now]=r;//路径压缩 
		now=pr;//将当前节点上跳为上一级,直到跳至根节点 
	}
	return r;
}
void un(int xx,int yy)//合并两个节点 
{
	int tx=find(xx);
	int ty=find(yy);
	fa[tx]=ty;
}
bool ifin(int xx,int yy)//判断两个点是否在一个集合中 
{
	return find(xx)==find(yy);
}
int main()
{
	n=read();
	m=read();
	for(register int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	for(register int i=1;i<=m;i++)
	{
		z=read();
		x=read();
		y=read();
		if(z==1)
		{
			un(x,y);
		}
		else
		{
			if(ifin(x,y))
			{
				cout<<"Y"<<endl;
			}
			else
			{
				cout<<"N"<<endl; 
			}
		}
	}
	return 0;
}

欧拉筛

注意素数定义:质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数(即0,1均非素数)

//https://www.luogu.org/problem/P3383
//题目大意 给定1-n个数 m次查询 输出该数是否是素数
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 10000000
LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
//质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
int n,m,cnt,t,prime[10000005];//prime记录素数 
bool v[10000005];//记录是否是合数 
int main()
{
	n=read();
	m=read();
	v[0]=1;//0不是素数 
	v[1]=1;//1不是素数 
	for(register int i=2;i<=n;i++)
	{
		if(!v[i])//如果i是素数 用prime记录,prime里面的素数严格递增 
		{
			prime[++cnt]=i;
		}
		for(register int j=1;j<=cnt;j++)//遍历所有素数 
		{
			if(prime[j]*i>n)
			{
				break ;
			}
			v[prime[j]*i]=1;
			if(i%prime[j]==0)//如果该数(i)大于等于这个数的最小质因子,就跳出
			//详见 https://www.cnblogs.com/jason2003/p/9761296.html 解释 
			{
				break ;
			}
		}
	}
	for(register int i=1;i<=m;i++)
	{
		t=read();
		if(v[t])
		{
			cout<<"No"<<endl;//被筛掉了——>不是素数
		}
		else
		{
			cout<<"Yes"<<endl;//是素数
		}
	}
	return 0;
} 
/*
算法思路:

  对于每一个数(无论质数合数)x,筛掉所有小于x最小质因子的质数乘以x的数。比如对于77,它分解质因数是7*11,
  那么筛掉所有小于7的质数*77,筛掉2*77、3*77、5*77。

  好吧,是不是听起来太简单了。。。。没事,重点在证明。

算法证明:

  首先我们要明确证明思路。如果要证明它是对的,只要保证两点:没用重复筛、没有漏筛

  1、没有重复筛。

    我们假设一个合数分解成p1*p2*p3,并且保证p1<p2<p3。我们知道,筛掉这个合数的机会有:p1和p2*p3,
    p2和p1*p3,p3和p1*p2。但我们知道,我们选择的那个质数必须小于那个合数的最小质因子。比如p2和p1*p3,
    因为p2>p1,所以这样是筛不到的。唯一会筛的是第一种:p1和 p2*p3。

  2、没有漏筛。

  还是假设把这个合数分解质因数a*b*c,保证a<b<c然后我们设s=b*c,s肯定小于刚才那个合数,
  说明肯定对于它已经筛过,然后a肯定小于s,因为s=b*c,并且a是最小的因子。说明a*s也就是这个合数一定筛过。
*/

快速幂

//https://www.luogu.org/problem/P1226
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 10000000
inline LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL b,p,k,s;
LL quickpow(LL x,LL y,LL m)//二分思想  
{
	x=x%m;//防止第一个x*x就爆掉longlong 
	LL ans=1;//记录答案 
	while(y)// 注意如果y=0的话不会进入循环 
	{
		if(y&1)//判断y是否是单数 
		{
			ans=ans*x;
			ans=ans%m;
		}
		x=x*x;
		x=x%m;
		y=y>>1;
	}
	ans=ans%m;//防止出现特殊数据:1^0%1=0的情况 
	return ans;
}
int main()
{
	b=read();
	p=read();
	k=read();
	s=quickpow(b,p,k);
	printf("%lld^%lld mod %lld=%lld\n",b,p,k,s);
	return 0;
}

矩阵快速幂

只有左矩阵的列数等于右矩阵的行数才可乘
(其实矩阵快速幂的难点在构造矩阵=.=)
矩阵乘法:行乘列(ans[i][j]=a[i][k]*b[k][j]的和)

//例题:求斐波那契数列
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 1000000007
#define MAXN 3
LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
//只有左矩阵的列数等于右矩阵的行数才可乘
LL n;
struct Matrix
{
    LL mat[MAXN][MAXN];

};
inline void init(Matrix &x)//注意此处的&
{
    x.mat[1][1]=1;
    x.mat[1][2]=1;
    x.mat[2][1]=1;
    x.mat[2][2]=0;
}
Matrix t;
Matrix cheng(Matrix a,Matrix b,LL nt,LL mod)//nt代表几阶矩阵
{
    Matrix an;
    for(register int i=1;i<=nt;i++)//初始化
    {
        for(register int j=1;j<=nt;j++)
        {
            an.mat[i][j]=0;
        }
    }
    for(register int i=1;i<=nt;i++)
    {
        for(register int j=1;j<=nt;j++)
        {
            for(register int k=1;k<=nt;k++)
            {
                an.mat[i][j]+=a.mat[i][k]*b.mat[k][j];
                an.mat[i][j]%=mod;
            }
        }
    }
    return an;
}
Matrix quickpow(Matrix a,LL b,LL nt,LL mod)//nt 代表几阶矩阵 b代表幂数
{
    Matrix ans,res;
    for(register int i=1;i<=nt;i++)
    {
        for(register int j=1;j<=nt;j++)
        {
            if(i==j)
            {
                ans.mat[i][j]=1;
            }
            else
            {
                ans.mat[i][j]=0;
            }
            res.mat[i][j]=0;
        }
    }
    res=a;
    while(b)
    {
        if(b&1)
        {
            ans=cheng(ans,res,2,MOD);
        }
        res=cheng(res,res,2,MOD);
        b>>=1;
    }
    return ans;
}
int main()
{
    n=read();
    init(t);
    cout<<quickpow(t,n-1,2,MOD).mat[1][1]<<endl;
    //system("pause");
    return 0;
}


/*

|x1 x2|  ×   | y1 |  =  |x1*y1+x2*y2|
|x3 x4|      | y2 |     |x3*y1+x4*y2|

*/

最小生成树

prim算法适合稠密图(边多点少),kruskal算法适合稀疏图(边少点多)。

Kruskal O(nlogn)

//https://www.luogu.org/problem/P3366 
#include<bits/stdc++.h>
using namespace std;
#define LL int
int read()
{
	int x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,cnt,tt;
struct node{
	int x,y,z;
};
node edge[200005];
void add(int a,int b,int w)
{
	edge[++cnt].x=a;
	edge[cnt].y=b;
	edge[cnt].z=w;
}
bool cmp(node a,node b)
{
	return a.z<b.z;
}
int fa[5005];
int found(int x)
{
	int pre;
	int r=x;
	while(fa[r]!=r)
	{
		r=fa[r];
	}
	while(x!=r)
	{
		pre=fa[x];
		fa[x]=r;
		x=pre;
	}
	return x;
}
bool ifin(int a,int b)
{
	return found(a)==found(b);
}
void un(int a,int b)
{
	int aa=found(a);
	int bb=found(b);
	fa[aa]=bb;
}
int ans,aa,bb,cc;
int main()
{
	n=read();
	m=read();
	for(register int i=1;i<=n;i++)//并查集初始化 
	{
		fa[i]=i;
	}
	for(register int i=1;i<=m;i++)
	{
		aa=read();
		bb=read();
		cc=read(); 
		add(aa,bb,cc);
	} 
	sort(edge+1,edge+cnt+1,cmp);//按照边从小到大排序 
	for(register int i=1;i<=cnt;i++)
	{
		if(tt==n-1)//tt记录已经在树中的边的个数 如果等于n-1说明建树完毕 
			break ;
		if(!ifin(edge[i].x,edge[i].y))//如果该边连接的两点不在同一集合中 则合并 
		{
			ans+=edge[i].z;
			un(edge[i].x,edge[i].y);
			tt++;
		}
	}
	if(tt<n-1)//判断是否成功建树 
	{
		cout<<"orz"<<endl;
	}
	else
	{
		cout<<ans<<endl;
	}
	return 0;
}

Prim O((n+m)logm)

//https://www.luogu.org/problem/P3366 
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 200007
LL read()
{
    LL x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return w*x;
}
int xi,yi,zi,cnt,n,m,dis[5001],mp[5001][5001];
bool v[5001];
struct nn
{
    int x,d;
    bool operator <(const nn& b)const
    {
        return d>b.d;
    }
};
priority_queue < nn > q;//优先队列优化
inline int prim(int s)
{
    int ans=0;
    v[s]=1;
    nn now;
    now.d=0;
    now.x=s;
    nn nw;
    for(register int ct=1;ct<=n-1;ct++)//找n-1个点
    {
        for(register int i=1;i<=n;i++)
        {
            nw=now;//憨批操作=.=
            if(mp[now.x][i]&&!v[i])//如果点i没有加入到树中 则将该边入队
            {
                now.d=mp[now.x][i];
                now.x=i;
                q.push(now);
            }
            now=nw;
        }
        while(!q.empty()&&v[q.top().x])//如果该点已经入树了 就跳过
        {
            q.pop();
        }
        if(q.empty())
        {
            break ;
        }
        now=q.top();
        q.pop();
        ans+=now.d;//记录树的大小
        v[now.x]=1;//标记该点已经在树中
    }
    return ans;
}
int main()
{
    n=read();
    m=read();
    for(register int i=1;i<=5000;i++)//初始化
    {
        for(register int j=1;j<=5000;j++)
        {
            if(i==j)
                mp[i][j]=0;
            mp[i][j]=MAXN;
        }
    }
    for(register int i=1;i<=m;i++)
    {
        xi=read();
        yi=read();
        zi=read();
        mp[xi][yi]=min(mp[xi][yi],zi);//防止重边
        mp[yi][xi]=min(mp[yi][xi],zi);//防止重边
    }
    cout<<prim(1)<<endl;//输出答案
    //system("pause");
    return 0;
}

lcm、gcd与exgcd

注意该无法处理含有负数的情况

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL a,b,X,Y;
LL gcd(LL x,LL y)//辗转相除法
{
    LL z;
	while(y!=0)
	{
		z=x;
		x=y;
		y=z%y;
	}
	return x;
}
LL exgcd(LL a,LL b,LL &x,LL &y)//用来解a*x+b*y=gcd(a,b)的情况 
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	LL r=exgcd(b,a%b,x,y);
	LL t=x;
	x=y;
	y=t-a/b*y;
	return r;
}
LL lcm(LL a,LL b)//最小公倍数数=a*b/最大公因数
{
	LL ans=a/gcd(a,b);//先计算除法防止爆掉 long long
	return ans*b;
}
int main()
{
	while(cin>>a>>b)
	{
		cout<<"LCM:"<<lcm(a,b)<<endl;
		cout<<"GCD:"<<gcd(a,b)<<endl;
		LL tt=exgcd(a,b,X,Y);
		cout<<"exGCD:"<<a<<"*"<<X<<'+'<<b<<'*'<<Y<<'='<<tt<<endl;
	}
	return 0;
 } 

单源最短路之队优Dijkstra

//https://www.luogu.org/problem/P4779 
#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
		{
			w=-1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,s;
int ui,vi,wi;
int cnt,head[100005],d[100005];
struct node{
	int to,next,w;
};
node edge[200005];
void add(int x,int y,int w)
{
	edge[++cnt].to=y;
	edge[cnt].next=head[x];
	edge[cnt].w=w;
	head[x]=cnt;
}
struct nn{
	int x,dis;
	bool operator <(const nn& b)const//重载运算符'<' 
	{
		return dis>b.dis;//顺序按照dis从小到大排(此处与sort的cmp恰好相反) 
	}
};//记录入队的节点和距离 
priority_queue < nn > q;
inline nn init(int xx,int dd)
{
	nn t;
	t.x=xx;
	t.dis=dd;
	return t;
}
nn now;
void dij(int s)
{
	for(register int i=1;i<=n;i++)//初始化 
	{
		d[i]=INF;
	}
	d[s]=0;//d[x]代表点s到 x的距离 
	now.x=s;
	now.dis=0;
	q.push(now);
	while(!q.empty())
	{	
		now=q.top();
		q.pop();
		if(now.dis>d[now.x])//优化 如果该边权大于到点now.x的距离 说明该边比已有点更差就没有必要去遍历该点了  
			continue ;
		for(register int i=head[now.x];i!=0;i=edge[i].next)//遍历当前now点所连接的所有边 
		{
			if(d[edge[i].to]>d[now.x]+edge[i].w)//如果该边比已有点更优 则对该边所连的点进行松弛,并将该点入列 
			{
				d[edge[i].to]=d[now.x]+edge[i].w;
				q.push(init(edge[i].to,d[edge[i].to]));
			}
		}
	}
}
int main()
{
	n=read();
	m=read();
	s=read();
	for(register int i=1;i<=m;i++)
	{
		ui=read();
		vi=read();
		wi=read();
		add(ui,vi,wi);
	}
	dij(1);
	for(register int i=1;i<=n;i++)
	{
		cout<<d[i]<<' ';
	}
	return 0;
}

线段树

难点在多种操作(例如区间加与区间乘与区间覆盖等操作同时存在时down函数的处理顺序上以及对不同lazy下标优先级与操作顺序上是否进行清零等操作)
看起来好乱

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 500007
LL read()
{
    LL x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return w*x;
}
struct node
{
    LL l,r,lazy,w;
};
node tree[INF*4];
LL n,q,f,xx,yy,ans,d;
void update(LL k)//更新k点的值
{
    tree[k].w=tree[k<<1].w+tree[k<<1|1].w;
}
void build(int x,int y,int k)//建树
{
    tree[k].l=x;
    tree[k].r=y;
    if(x==y)
    {
        tree[k].w=read();
        return ;
    }
    LL mid=(x+y)>>1;
    build(x,mid,k<<1);
    build(mid+1,y,k<<1|1);
    update(k);
}
void down(LL k)//下推lazy标记(此时该节点已经更新完成,该操作为更新两个子节点的值)
{
    tree[k<<1].lazy+=tree[k].lazy;
    tree[k<<1|1].lazy+=tree[k].lazy;
    tree[k<<1].w+=tree[k].lazy*(tree[k<<1].r-tree[k<<1].l+1);
    tree[k<<1|1].w+=tree[k].lazy*(tree[k<<1|1].r-tree[k<<1|1].l+1);
    tree[k].lazy=0;
}
void add(LL x,LL y,LL num,LL k)//区间增加 xy为查询区间 该节点区间直接用tree[k].l tree[k].r表示
{
    if(tree[k].l>=x&&tree[k].r<=y)
    {
        tree[k].w+=num*(tree[k].r-tree[k].l+1);
        tree[k].lazy+=num;
        return ;
    }
    down(k);//如果要继续二分,则要更新它的子节点,即进行下推lazy的操作
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(x<=mid)
    {
        add(x,y,num,k<<1);
    }
    if(y>mid)
    {
        add(x,y,num,k<<1|1);
    }
    update(k);//记得最后更新当前节点
}
void oneadd(LL nx,LL num,LL k)//单点增加
{
    if(tree[k].l==tree[k].r&&tree[k].l==nx)
    {
        tree[k].w+=num;
        return ;
    }
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(nx<=mid)
        oneadd(nx,num,k<<1);
    else
        oneadd(nx,num,k<<1|1);
    update(k);
}
LL getsum(LL x,LL y,LL k)//求和
{
    LL res=0;
    if(tree[k].l>=x&&tree[k].r<=y)
    {
        res=tree[k].w;
        return res;
    }
    down(k);
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(x<=mid)
        res+=getsum(x,y,k<<1);
    if(y>mid)
        res+=getsum(x,y,k<<1|1);
    return res;
}
int main()
{
    n=read();
    build(1,n,1);
    q=read();
    while(q--)
    {
        f=read();
        xx=read();
        yy=read();
        if(f==1)
        {
            d=read();
            add(xx,yy,d,1);
        }
        else
        {
            ans=getsum(xx,yy,1);
            printf("%lld\n",ans);
        }
        
    }
    //system("pause");
    return 0;
}

二分图

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL read()
{
	LL x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL u,v,n,m,e,ans,mp[2000][2000],used[2000],b[2000];
bool found(int x)
{
	for(register int i=1;i<=m;i++)
	{
		if(mp[x][i]&&!used[i])//x到i有边并且i没有被尝试改变归属且失败
		{
			used[i]=1;
			if(!b[i]||found(b[i]))//如果i没有归属或者成功地改变了i原本归属的归属
			{
				b[i]=x;//令i的归属为x
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	n=read();//集合1的个数
	m=read();//集合2的个数
	e=read();//边的条数
	for(register int i=1;i<=e;i++)
	{
		u=read();
		v=read();
		if(u>n)//原题数据有锅 无视就好
			continue ;
		if(v>m)
			continue ;
		mp[u][v]=1;//从u到v有边
	}
	for(register int i=1;i<=n;i++)
	{
		memset(used,0,sizeof(used));
		if(found(i))
			ans++;//记录匹配数量
	}
	cout<<ans<<endl;
	//system("pause");
	return 0;
}
// 判断一个图是否为二分图:将一个点标记为1 将与该点相连的点标记为2
// 将与标为2的点相连的点标记为1 如果有冲突 则不是二分图

最长上升子序列(LIS)


//初始化dp为inf=0x3f3f3f
memset(dp,inf,sizeof(dp));
 
for (int i=0;i<len;i++)
{
        //获取位置
        int pos=lower_bound(dp,dp+len,s[i])-dp;
        //更新位置
        dp[pos]=s[i];
        ans=max(ans,pos);
}
 
//答案为ans+1
cout << ans+1 << endl;

最长公共子序列(LCS)


//len1和len2为两字符串长度
for (int i=1;i<=len1;i++)
	for (int j=1;j<=len2;j++)
		//相等时直接更新
		if (s1[i-1]==s2[j-1])
			dp[i][j]=dp[i-1][j-1]+1;
		//不相等时继承之前的最大值
		else
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
 
//答案为dp[len1][len2]
cout << dp[len1][len2] << endl;

区间dp

//初始化DP数组
for (int i=1;i<=n;i++)
	dp[i][i]=INF(0 or 其他)
 
//枚举区间长度
for (int len=1;len<n;len++)
	//枚举起点
	for (int i=1;i+len<=n;i++)
	{
		//区间终点
		int j=i+len;
		//枚举分割点
		for (int k=i;k<j;k++)
			dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
	}
 
//区间1-n的结果
cout << dp[1][n] << endl;

大数加法

/*
    |大数模拟加法|
    |用string模拟|
    |16/11/05ztx, thanks to caojiji|
*/

string add1(string s1, string s2)
{
    if (s1 == "" && s2 == "")   return "0";
    if (s1 == "")   return s2;
    if (s2 == "")   return s1;
    string maxx = s1, minn = s2;
    if (s1.length() < s2.length()){
        maxx = s2;
        minn = s1;
    }
    int a = maxx.length() - 1, b = minn.length() - 1;
    for (int i = b; i >= 0; --i){
        maxx[a--] += minn[i] - '0'; //  a一直在减 , 额外还要减个'0'
    }
    for (int i = maxx.length()-1; i > 0;--i){
        if (maxx[i] > '9'){
            maxx[i] -= 10;//注意这个是减10
            maxx[i - 1]++;
        }
    }
    if (maxx[0] > '9'){
        maxx[0] -= 10;
        maxx = '1' + maxx;
    }
    return maxx;
}

大数阶乘

/*
    |大数模拟阶乘|
    |用数组模拟|
    |16/12/02ztx|
*/

#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;

const int maxn = 100010;

int num[maxn], len;

/*
    在mult函数中,形参部分:len每次调用函数都会发生改变,n表示每次要乘以的数,最终返回的是结果的长度
    tip: 阶乘都是先求之前的(n-1)!来求n!
    初始化Init函数很重要,不要落下
*/

void Init() {
    len = 1;
    num[0] = 1;
}

int mult(int num[], int len, int n) {
    LL tmp = 0;
    for(LL i = 0; i < len; ++i) {
         tmp = tmp + num[i] * n;    //从最低位开始,等号左边的tmp表示当前位,右边的tmp表示进位(之前进的位)
         num[i] = tmp % 10; //  保存在对应的数组位置,即去掉进位后的一位数
         tmp = tmp / 10;    //  取整用于再次循环,与n和下一个位置的乘积相加
    }
    while(tmp) {    //  之后的进位处理
         num[len++] = tmp % 10;
         tmp = tmp / 10;
    }
    return len;
}

int main() {
    Init();
    int n;
    n = 1977; // 求的阶乘数
    for(int i = 2; i <= n; ++i) {
        len = mult(num, len, i);
    }
    for(int i = len - 1; i >= 0; --i)
        printf("%d",num[i]);    //  从最高位依次输出,数据比较多采用printf输出
    printf("\n");
    return 0;
}

1. 01背包

1.1 题目

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],求将哪些物品装入背包可使价值总和最大。
1.2 特点

每种物品仅有一件,可以选择放与不放。

1.3 基本的状态转移方程

f[i][j] = max(f[i − 1][j], f[i − 1][j − w[i]] + v[i])

1.4 基本模板

for(int i = 0; i < h; i++)
{
    for(int j = c; j >= v[i]; j--)
    {
        dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
    }
}

1.5沾题

1.P2925 [USACO08DEC]干草出售Hay For Sale

简单01背包有一点优化:背包装满后就没必要再继续循环了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 50010;
 
int c, h, v[maxn], dp[maxn];
int main()
{
    scanf("%d%d", &c, &h);
    for(int i = 0; i < h; i++) scanf("%d", &v[i]);
    for(int i = 0; i < h; i++)
    {
 
        for(int j = c; j >= v[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
            if(dp[j] == j) continue;
        }
        if(dp[c] == c) // 优化装满后退出
        {
            break;
        }
    }
 
    printf("%d\n", dp[c]);
    return 0;
}

1.6 常数优化

for(int i = 0; i < h; i++)
{
    int sum = 0;
    for(int k = i; k < h; k++) sum += v[i];
    int maxx = max(c - sum, v[i]);
    for(int j = c; j >= maxx; j--)
    {
        dp[j] = max(dp[j], dp[j - v[i]] + v[i]); 
    }
    if(dp[c] == c)
    {
        break;
    }
}

1.7初始化细节

(1)恰好装满背包,初始化dp[0] = 0,其余F[1 … V] 均设为-INF

(2)未要求将背包装满,只是价格尽量大 F[0 … V] 均设为0

2.完全背包

2.1 题目

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
2.2 基本思路

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][j]表示前i种物品恰放入一个容量为V的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:

f[i][j] = max(f[i − 1][j − k ∗ w[i]] + k ∗ v[i]) ∣ 0 <= k ∗ w[i] <= V

二维状态转移方程

f[i][j] = max(f[i − 1][j], f[i][j − w[i]] + v[i])

2.3 模板

for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
2.4 沾个题

Piggy-Bank

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 10010;

int t, W, w[maxn], v[maxn], dp[maxn], n;
int main()
{
scanf("%d", &t);
while(t–)
{
int w1, w2;
scanf("%d%d", &w1, &w2);
fill(dp, dp + maxn, INF);
W = w2 - w1;
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%d%d", &v[i], &w[i]);
}
dp[0] = 0;
for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
if(dp[W] == INF)
{
printf(“This is impossible.\n”);
}
else
{
printf(“The minimum amount of money in the piggy-bank is %d.\n”, dp[W]);
}
}
return 0;
}
3 多重背包

3.1题目

有N种物品和一个容量为V的背包。第i种物品最多有p[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

3.2 复杂度的多重背包问题

void Zero(int w, int p)
{
for(int j = W; j >= w; j–)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Complete(int w, int p)
{
for(int j = w; j <= W; j++)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Multiple(int c, int w, int p)
{
if(c * w >= W)
{
Complete(w, p);
return;
}
int k = 1;
while(k < c)
{
Zero(k * w, k * p);
c = c - k;
k = 2 * k;
}

Zero(c * w, c * p);

}
3.3可行性问题O(VN)的算法

当问题是每种有若干的物品能否他Inman给定容积的背包,只需考虑装满背包的可行性O(VN)复杂度。

基本思想:设F[i, j]表示用了前i种物品填满容量为j的背包后最多还剩几个第i种物品可用。若F[i, j] = -1表示这种状态不太可行,若可行则满足F[i , j] >=0 && F[i, j] <= Mi

memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= V; j++)
{
if(dp[i - 1][j] >= 0) dp[i][j] = M[i];
else dp[i][j]= -1;
}
for(int j = 0; j <= V - C[i]; j++)
{
if(dp[i][j] > 0)
dp[i][j + C[i]] = max(dp[i][j + C[i], dp[i][j] - 1);
}
}
4 混合多种背包

4.1问题
如果将前面三个背包混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?

4.2 01背包与完全背包的混合
考虑到在01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN) O(VN)O(VN)。

4.3再加上多重背包
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN) O(VN)O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP NOIPNOIP范围的算法的话,用多重背包中将每个这类物品分成O(log(p[i])) O(log(p[i]))O(log(p[i]))个01背包的物品的方法也已经很优了

4.4赋个多校题

AreYouBusy

题意:他由很多工作0 ,至少取一个,1 最多取一个, 2随意取,让你求在时间之内,求最大的开心值。

1.第一类,至少选一项,即必须要选,那么在开始时,对于这一组的dp的初值,应该全部赋为负无穷,这样才能保证不会出现都不选的情况。

dp[i][j]=max(dp[i][j],max(dp[i][j-w[x]]+p[x],dp[i-1][j-w[x]]+p[x]));

2.第二类,最多选一项,即要么不选,一旦选,只能是第一次选。

dp[i][j]=max(dp[i][j],dp[i-1][j-w[x]]+p[x]);

3.第三类,任意选,即不论选不选,选几个都可以。

dp[i][j]=max(dp[i][j],dp[i][j-w[x]]+p[x]);

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
int n, t, x, y, v[maxn], w[maxn], dp[maxn][maxn];

int main()
{
while(scanf("%d%d", &n, &t) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
scanf("%d%d", &x, &y);
for(int k = 1; k <= x; k++) scanf("%d%d", &v[k], &w[k]);
if(y == 0) // 至少取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = -INF;
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
{
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
}
else if(y == 1) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
else if(y == 2) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
}
}
}
int ans = -1;
ans = max(ans, dp[n][t]);
printf("%d\n", ans);
}
return 0;
}
5 二维费用背包

5.1题目

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为w[i]和g[i]。两种代价可付出的最大值(两种背包容量)分别为V和T。物品的价值为v[i]。
5.2模板

for(int i = 0; i < n; i++)
{
    for(int j = v; j >= a[i]; j--)
    {
        for(int k = m; k >= b[i]; k--)
        {
            dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
        }
    }
}

5.3沾题

1.P1507 NASA的食物计划

#include<bits/stdc++.h>
using namespace std;
const int maxn = 410;

int v, m, n, a[maxn], b[maxn], c[maxn], dp[maxn][maxn];
int main()
{
scanf("%d%d", &v, &m);
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d%d%d", &a[i], &b[i], &c[i]);
for(int i = 0; i < n; i++)
{
for(int j = v; j >= a[i]; j–)
{
for(int k = m; k >= b[i]; k–)
{
dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
}
}
}
printf("%d\n", dp[v][m]);
return 0;
}
2.FATE

题意:打游戏有忍耐值,需要在忍耐值之内刷够经验,最多杀n只怪。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;

int n, m, k, s, a[maxn], b[maxn], dp[maxn][maxn];
int main()
{
while(scanf("%d%d%d%d", &n, &m, &k, &s) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 0; i < k; i++) scanf("%d%d", &a[i], &b[i]);
for(int i = 0; i < k; i++)
{
for(int j = b[i]; j <= m; j++)
{
for(int x = s; x >= 1; x–)
{
dp[j][x] = max(dp[j][x], dp[j - b[i]][x - 1] + a[i]);
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= s; j++)
{
if(dp[i][s] >= n)
{
ans = m - i;
break;
}
}
if(ans != 0x3f3f3f3f) break;

    }
    if(ans == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", ans);
}
return 0;

}
6 分组背包

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]∣物品i属于组k)

模板:

for(int i = 1; i <= n; i++)
{
for(int j = m; j >= 1; j–)
{
for(int k = 1; k <= j; k++)
{
dp[j] = max(dp[j], dp[j - k] + a[i][k]);
}
}

kmp

/*
    |kmp算法|
    |字符串匹配|
    |17/1/21ztx|
*/

void getnext(char str[maxn], int nextt[maxn]) {
    int j = 0, k = -1;
    nextt[0] = -1;
    while (j < m) {
        if (k == -1 || str[j] == str[k]) {
            j++;
            k++;
            nextt[j] = k;
        }
        else
            k = nextt[k];
    }
}

void kmp(int a[maxn], int b[maxn]) {    
    int nextt[maxm];    
    int i = 0, j = 0;    
    getnext(b, nextt);    
    while (i < n) {    
        if (j == -1 || a[i] == b[j]) { // 母串不动,子串移动    
            j++;    
            i++;    
        }    
        else {    
            // i不需要回溯了    
            // i = i - j + 1;    
            j = nextt[j];    
        }    
        if (j == m) {    
            printf("%d\n", i - m + 1); // 母串的位置减去子串的长度+1    
            return;    
        }    
    }    
    printf("-1\n");
}    

树状数组

/*
    |16/11/06ztx|
*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>

using namespace std;

typedef long long ll;

const int maxn = 50005;

int a[maxn];
int n;

int lowbit(const int t) {
    return t & (-t);
}

void insert(int t, int d) {
    while (t <= n){
        a[t] += d;
        t = t + lowbit(t);
    }
}

ll getSum(int t) {
    ll sum = 0;
    while (t > 0){
        sum += a[t];
        t = t - lowbit(t);
    }
    return sum;
}

int main() {
    int t, k, d;
    scanf("%d", &t);
    k= 1;
    while (t--){
        memset(a, 0, sizeof(a));
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &d);
            insert(i, d);
        }
        string str;
        printf("Case %d:\n", k++);
        while (cin >> str) {
            if (str == "End")   break;
            int x, y;
            scanf("%d %d", &x, &y);
            if (str == "Query")
                printf("%lld\n", getSum(y) - getSum(x - 1));
            else if (str == "Add")
                insert(x, y);
            else if (str == "Sub")
                insert(x, -y);
        }
    }
    return 0;
}

BFS

#include<string>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
#define MOD 998244353
#define PI 3.1415926535898
#define INF 0x3f3f3f3f
#define MAXN 80000080
const double EPS = 1e-8;
LL read()
{
	LL x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch>'9')
	{
		if (ch == '-')
		{
			w = -1;
		}
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return w * x;
}
int n, m, x, y;
int ans[405][405];
int dx[] = { 0,1,2,2,1,-1,-2,-2,-1 };
int dy[] = { 0,2,1,-1,-2,-2,-1,1,2 };
struct node
{
	int x, y;
};
queue<node> q;
node init(int x, int y)
{
	node xx;
	xx.x = x;
	xx.y = y;
	return xx;
}
void bfs()
{
	q.push(init(x, y));
	while (!q.empty())
	{
		node tt = q.front();
		q.pop();
		int a = tt.x;
		int b = tt.y;
		for (register int i = 1; i <= 8; i++)
		{
			int xx = a + dx[i];
			int yy = b + dy[i];
			if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && ans[xx][yy] == -1)
			{
				ans[xx][yy] = ans[a][b] + 1;
				q.push(init(xx, yy));
			}
		}
	}
}
int main()
{
	n = read();
	m = read();
	x = read();
	y = read();
	memset(ans, -1, sizeof(ans));
	ans[x][y] = 0;
	bfs();
	for (register int i = 1; i <= n; i++)
	{
		for (register int j = 1; j <= m; j++)
		{
			printf("%-5d", ans[i][j]);//左对齐 宽五格
		}
		cout << endl;
	}
	return 0;
}

1.巴什博弈

所谓巴什博弈,是ACM题中最简单的组合游戏,大致上是这样的:
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取1个,最多取m个,最后取光者得胜。

显然,如果n = m + 1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的法则:

如果 n = (m + 1) * r + s ,(r为任意自然数,s≤m),即n%(m+1) != 0,则先手必胜。

巴什博弈还是很好理解的,以你是先手的角度考虑。你想把对手给弄垮,那么每一局,你都必须构建一个局势,这个局势就是每次都留给对手m+1的倍数个物品。因为,如果n=(m+1)r + s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

变形:条件不变,改为最后取光的人输。

结论:当(n-1)%(m+1)==0时后手必胜。

2.威佐夫博奕

问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

解决思路:A:设(ai,bi)(ai ≤bi ,i=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。任给一个局势(a,b),如下公式判断它是不是奇异局势: ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)。

设a<b:
double x=(sqrt(5)+1)/2;
if((b-a)*x==a)
	后手必胜;
else
	先手必胜;//先手可以通过一步操作使得当前局面变成(b-a)*x==a的情况
	

变形例题:

移动的皇后
Problem Description
·

一个n * n棋盘上有一个皇后。每个人可以把它往左或下或左下45度移动任意多步。把皇后移动至左下角的游戏者获胜。现在给出皇后初始的X坐标和Y坐标,如果轮到你先走,假设双方都采取最好的策略,问最后你是胜者还是败者。
·
Input
·
输入包含若干行,表示若干种皇后的初始情况,其中每一行包含两个非负整数a和b,表示皇后的初始坐标,a和b都不大于1,000,000,000。
·
Output
·
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
·
Sample Input
·
2 1 8 4 4 7
·
Sample Output
·
0 1 0

该题可以把向下走看做一堆石子,向左看做另一堆石子,此时操作就变成了拿走任意一堆石子中的若干个或同时拿走两堆中的若干个石子。

3.Fibonacci博弈

问题模型:

有一堆个数为n的石子,游戏双方轮流取石子,满足:

(1)先手不能在第一次把所有的石子取完;
(2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。 约定取走最后一个石子的人为赢家。

结论:当n为Fibonacci数时,后手必胜.即存在先手的必败态当且仅当石头个数为Fibonacci数。
否则先手必胜

4.Ferguson博弈

问题模型:有两个盒子,在游戏的开始,第一个盒子中有n枚石子,第二个盒子中有m个石子(n, m > 0)。参与游戏的两名玩家轮流执行这样的操作:清空一个盒子中的石子,然后从另一个盒子中拿若干石子到被清空的盒子中,使得最后两个盒子都不空。
当两个盒子中都只有一枚石子时,游戏结束。最后成功执行操作的玩家获胜。
结论:对于一个位置(x, y)来说,如果x, y中有一个偶数,那么先手必胜。如果x和y都是奇数,那么后手必胜

5.Chomp! 博弈(巧克力博弈)

问题模型:Chomp是一个双人游戏,有m x n块曲奇饼排成一个矩形格状,称作棋盘。

----两个玩家轮流自选一块还剩下的曲奇饼,而且还要把它右边和下边所有的曲奇饼都取走(如果存在)

----先吃到左上角(1,1)那块曲奇饼的玩家为失败

如图所示

在这里插入图片描述
------红方选择(3,3)—>在这里插入图片描述
------蓝方选择(1,4)---->在这里插入图片描述

----红方选择(1,2)—>在这里插入图片描述
-----蓝方选择(2,1)–>在这里插入图片描述

------------>红方玩家只能选左上角那一块,失败

该类题型永远存在先手必胜!!!!!!!!!!!!

类似问题

1、三维Chomp游戏

将曲奇排成 P x Q x R 的立方体,两个玩家轮流自选吃掉一块剩下的曲奇饼,若取走的曲奇饼为 (i,j,k) ,则也要取走所有满足 i ≤ a ≤ P,j ≤ b ≤ Q , k ≤ c ≤ R 的曲奇饼(a,b,c)(如果存在)。

可以类似地将Chomp游戏扩展到任意维,并可以类似地证明,先手都存在必胜策略。

2、有限偏序集上的Chomp游戏

Chomp游戏可以推广到在任意一个存在最小元 a 的有限偏序集(S,≤)上:两名游戏者轮流选择S中的元素 x ,移走 x 以及所有 S 中比 x 大的元素。失败者是被迫选择最小元 a 的玩家。

如果 (S,≤) 有最大元素 b ,那么在偏序集上的Chomp游戏存在一个获胜策略.

3、约数游戏

给定一个大于1的自然数 N ,两个游戏参与者轮流选择N的大于1的正约数,但不可选择之前被选择过的因子的倍数(例如 N = 72,有一方之前选择了4,则之后任一方都不可以再选择36)

4、删数游戏

给定整数集合 {1,2,…n} ,两个人轮流从中选择一个数字,并将它和它的约数从集合中删除,删除最后一个数的人获胜。

以上几个游戏,类似Chomp游戏,得到结论就是无论 n 是几,都是先手必胜。

5.尼姆博弈(Nimm’s Game)

问题模型:有3堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取1个,多者不限,最后取光者得胜。
结论:我们用(a,b,c)表示某种局势,如果a XOR b XOR c==0,则后手必胜,否则先手必胜
证明: (3)
对于 (4,9,13) 这个容易验证是奇异局势

在这里插入图片描述

其中有两个8,两个4,两个1,非零项成对出现,这就是尼姆和为 零的本质。别人要是拿掉13里的8或者1,那你就拿掉对应的9 中的那个8或者1;别人要是拿 掉13里的4,你就拿掉4里的4; 别人如果拿掉13里的3,就把10作分解,然后想办法满 足非零项成对即可。

推广:当石子堆数为n堆时,则推广为当对每堆的数目进行异或之后值为零是必败态。

SG

取石子问题

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

SG[0]=0,f[]={1,3,4},

x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

以此类推…

x 0 1 2 3 4 5 6 7 8…

SG[x] 0 1 0 1 2 3 2 0 1…

由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:

1、使用 数组f 将 可改变当前状态 的方式记录下来。

2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。

4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。
模板如下:

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

未完

对拍

对拍.bat

@echo off
:loop
rand.exe
std.exe
a.exe
fc std.out a.out
if not errorlevel 1 goto loop
pause
:end

rand

#include<cstdlib>                                         //加入这个包才能使用随机函数rand()
  #include<cstdio>
  #include<ctime>                                           //加入这个包就能以时间为种子初始化随机函数
  #include<iostream>
  using namespace std;
  long long a,b,c;
  int main()
  {
     freopen("data.in","w",stdout);                        //注意:该程序生成的数据到data.in中
     srand(time(NULL));
	                                           //重要:初始化随机函数,以时间为种子
	      a=1;                                     
	//long long a=rand()%5+1;
	printf("%lld\n",a);
	
	while(a--)
	{
		b=rand()%5+1;
		printf("%lld\n",b);
		for(register int i=1;i<=b;i++)
		{
			c=rand()%5+1;
			printf("%lld ",c);
			c=rand()%5+1;
			printf("%lld\n",c);
		 } 
	}
	cout<<endl;
	
 }

std.cpp


#include<bits/stdc++.h>
using namespace std;
int main(){
	freopen("data.in","r",stdin);
    freopen("std.out","w",stdout); 
    return 0; 
}

a.cpp


#include<bits/stdc++.h>
using namespace std;
int main(){
	freopen("data.in","r",stdin);
    freopen("a.out","w",stdout); 
    return 0; 
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值