博弈论 + 组合数
NIM博弈
对于先手:永远将奇数台阶保持不变 我永远看到奇数层不一致的 对手看到永远是 一致的
先手必胜需要每个值异或不等0 这样留给后手操作的就是每个值异或等于0 操作完不为0的情况
台阶-NIM
对于先手:永远将奇数台阶保持不变 我永远看到奇数层不一致的 对手看到永远是 一致的(为输的局面)
奇数台阶石子数量是0 先手必败 不是0 先手必胜
对手从偶数层台阶 拿多少石子到奇数层台阶 我就把哪些石子拿到下层保持奇数级台阶不变
max函数 找到可以达到的情况没有的最小自然数
sg函数 sg(终点)=0 为无法操作的情况(无出边) sg()!=0 那么下一条路粗存在一种情况让sg()=0即让对手到达这个终点的情况
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 110,M=1e4+10;//M表示石子真实个数的值sg代表的值 n为可以进行的操作
int n,m;
int f[M],s[N];
int sg(int x){
if(f[x]!=-1) {
return f[x];
}
unordered_set<int>S;//S存出度结点的sg值
for (int i = 0; i < m; i ++ ){//遍历每种可能的操作
int sum=s[i];
if(x>=sum) S.insert(sg(x-sum));//如果可以操作 递归下一个值
}
for (int i = 0; ; i ++ ){//找max操作
if(!S.count(i))
return f[x]=i;
}
//最后一个renturn
}
int main()
{
cin >> m;
for (int i = 0; i < m; i ++ ) cin >> s[i];//s存每种操作的集合
cin >> n;
memset(f, -1, sizeof f);
int res=0;
for (int i = 0; i < n; i ++ ){
int x;
cin >> x;
res^=sg(x);//处理每个初始的情况 对多个图
}
//0为胜
if(res) cout << "Yes";//有向图任何一个非0的都会有情况会到0,任何一个0的情况到不了0
else cout << "No";
return 0;
}
sg(xy)=sg(x)sg(y);
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N = 110;
int n;
int f[N];
unordered_set<int> S;
int sg(int x)
{
if(f[x] != -1) return f[x];
for(int i = 0 ; i < x ; i++)
for(int j = 0 ; j <= i ; j++)//规定j不大于i,避免重复
S.insert(sg(i) ^ sg(j));//存入下一条边 相当于一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,
//等于这些局面SG值的异或和
for(int i = 0 ; ; i++)
if(!S.count(i))
return f[x] = i;
}
int main()
{
memset(f , -1 , sizeof f);
cin >> n;
int res = 0;
while(n--)
{
int x;
cin >> x;
res ^= sg(x);
}
if(res) puts("Yes");
else puts("No");
return 0;
}
组合数 1≤b≤a≤2000
c[i][j]=c[i-1][j]+c[i-1][j-1]
分成两个情况 选择这个果 在i-1的果里面选择j-1个 不选这果 在i-1里面选择剩下j个果
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2005,mod=1e9+7;
int c[N][N];
void init(){
for (int i = 0; i < N; i ++ ){//这里写成等于N会报爆se
for (int j = 0; j <= i; j ++ ){//i表示C右下面 j表示C右上角
if(!j) c[i][j]=1;
else
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
}
int main()
{
int n;cin>>n;init();
while (n -- ){
int a,b;cin>>a>>b;
cout << c[a][b]<<endl;
}
return 0;
}
组合数二 1≤b≤a≤1e5
使用逆元 计算 (a!/((a-b)!b!)) %mod=a!(a-b!逆元)%mod*(b!逆元)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
long long jie[N],injie[N];
const int mod = 1e9+7;
int qmi(long long a,int k){
int res=1;
while (k ){
if(k&1) res=res*a%mod;
a=a*a%mod;
k>>=1;
}
return res;
}
int main()
{
jie[0]=1;
injie[0]=1;
for (int i = 1; i < N; i ++ ){
jie[i]=jie[i-1]*i%mod;
injie[i]=(long long)injie[i-1]*qmi( i,mod-2 ) %mod;
}
int n;
cin >>n;
while (n -- ){
int a,b;cin>>a>>b;
cout <<(LL)jie[a]*injie[b]%mod*injie[a-b]%mod <<endl;//记录要开个LL
}
return 0;
}
求组合数3 1≤b≤a≤1e18
lucas定理 lucas(a,b) = (LL)c(a%p,b%p)*lucas(a/p,b/p)%p;
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int p;
int qmi(LL a,int k){
int res=1;
while (k ){
if(k&1) res=(LL)res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
LL c(LL a,LL b){
if(b>a) return 0;
int res=1;
for (int i = 1,j=a; i <= b; i ++,j-- ){
res=(LL)res*j%p;
res=(LL)res*qmi(i,p-2)%p;
}
return res;
}
int lucas(LL a,LL b){//这里一定要LL
if(a<p&&b<p) return c( a,b );
return (LL)c(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main()
{
int n ;cin>>n;
LL a,b;
while (n -- ){
cin >> a>>b>>p;
cout << lucas(a,b)<<endl;
}
return 0;
}
求组合数4 不能模一个数 需要高精度