《算法竞赛进阶指南》------数论习题篇1

练习9:XOR BZOJ2115 (* 线性基。求图中异或和,可谓经典中的经典)

传送门
题意:给一副边权非负整数的无向连通图,节点是 1 − N 1-N 1N,问从1号节点出发,到达N号,使得路径上经过的边的权值XOR和最大。
思路:想到对图DFS,生成了一个树(怎么生成这一颗树不重要,但我们知道了1-N的路径),对于dfs中已经访问到的点还有说明能成环。能成环说明我可以走这个环也可以不走
然后环的XOR边权值可选可不选。就可以想到用线性基.
总结: 初学线性基,很好用也很巧妙的算法实现,其思想是高斯消元。然后在这题中对图的dfs中判定环的想法学到了。这题做完有些豁然开朗。
参考博客:[学习笔记]线性基
MashiroSky
ACcode:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
int n,m;
int head[N],ver[N],nex[N];
int vis[N],sum;
ll c[N],dis[N],w[N];
int cnt,tot;
void addedge(int u,int v,ll val){
	ver[++cnt]=v;
	w[cnt]=val;
	nex[cnt]=head[u];
	head[u]=cnt;
}
void dfs(int u){
	vis[u]=1;
	for(int i=head[u];i;i=nex[i]){
		int v=ver[i];
		ll val=w[i];
		if(!vis[v]){
           dis[v]=dis[u]^val;
           dfs(v);
		}else c[++sum]=dis[v]^dis[u]^val;
	}
}
// 线性基 板子
struct L_B{
	// d[i] 二进制最高位是第i位.  p[i]就是第2^i个小的数是p[i]
    long long d[61],p[61];  // 顶值1e18
    int cnt;  // p数组有效个数
    L_B()
    {
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        cnt=0;
    }
	//插入
    bool insert(long long val)
    {
        for (int i=60;i>=0;i--)
            if (val&(1ll<<i))
            {
                if (!d[i])
                {
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
	//得到最大值
    long long query_max()
    {
        long long ret=0;
        for (int i=60;i>=0;i--)
            if ((ret^d[i])>ret)
                ret^=d[i];
        return ret;
    }
	//得到最小值
    long long query_min()
    {
        for (int i=0;i<=60;i++)
            if (d[i])
                return d[i];
        return 0;
    }
	// 题目中问到第k小的数是多少时,使用p数组
    void rebuild()
    {
        for (int i=60;i>=0;i--)
            for (int j=i-1;j>=0;j--)
                if (d[i]&(1ll<<j))
                    d[i]^=d[j];
        for (int i=0;i<=60;i++)
            if (d[i])
                p[cnt++]=d[i];
    }
	// 找第几小的值
    long long kthquery(long long k)
    {
        int ret=0;
        if (k>=(1ll<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1ll<<i))
                ret^=p[i];
        return ret;
    }
};
void solve(){
	scanf("%d %d",&n,&m);
	int u,v;
	ll val;
	rep(i,1,m){
      scanf("%d %d %lld",&u,&v,&val);
	  addedge(u,v,val);
	  addedge(v,u,val);
	}
	dfs(1);
    ll ans=dis[n];
	L_B  x;
    rep(i,1,sum) x.insert(c[i]);
	for(int i=60;i>=0;i--) {
		ans=max(ans,ans^x.d[i]); }
	printf("%lld\n",ans);
	
	return ;
}
int main (){
//  freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

练习10:新Nim游戏 BZOJ3105 (* NIM进阶版 NIM博弈+线性基)

传送门
题意在这里插入图片描述
也就是在传统的NIM游戏中,多了一回合特殊开始。
思路:根据Nim的游戏规则,必胜则 ∑ x o r ( a [ i ] ) \sum xor(a[i]) xor(a[i]), 现在我先手,我选了若干堆后。你的选择若干堆后,是希望 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i])=0 xor(a[i])=0,这样你才能赢。现在对于我来说我的选择需要避免你能实现 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i])=0 xor(a[i])=0。这样我才能赢。
什么情况我是必赢的呢? 我取了一部分后, ∑ x o r ( a [ i ] ) ≠ 0 \sum xor(a[i])\neq 0 xor(a[i])=0,然后你无论怎么取一部分,都不能满足 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i]) = 0 xor(a[i])=0
现在的问题就变成了:我怎么取这一部分,使得原 X O R XOR XOR中任取后, ∑ X O R \sum XOR XOR线性基里不能有包含0的情况。所以我要删除若干个数,使得剩下的数的线性基不能组成0的情况。 这里的删除的数删哪呢,怎么删呢?猜一猜,推一推,贪心一下。可以得知从最小的数开始删除。
总结:NIM博弈论,线性基+贪心
ACcode:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
// 线性基 板子
struct L_B{
	// d[i] 二进制最高位是第i位.  p[i]就是第2^i个小的数是p[i]
    long long d[61],p[61];  // 顶值1e18
    int cnt;  // p数组有效个数
    L_B()
    {
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        cnt=0;
    }
	//插入
    bool insert(long long val)
    {
        for (int i=60;i>=0;i--)
            if (val&(1ll<<i))
            {
                if (!d[i])
                {
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
	//得到最大值
    long long query_max()
    {
        long long ret=0;
        for (int i=60;i>=0;i--)
            if ((ret^d[i])>ret)
                ret^=d[i];
        return ret;
    }
	//得到最小值
    long long query_min()
    {
        for (int i=0;i<=60;i++)
            if (d[i])
                return d[i];
        return 0;
    }
	// 题目中问到第k小的数是多少时,使用p数组
    void rebuild()
    {
        for (int i=60;i>=0;i--)
            for (int j=i-1;j>=0;j--)
                if (d[i]&(1ll<<j))
                    d[i]^=d[j];
        for (int i=0;i<=60;i++)
            if (d[i])
                p[cnt++]=d[i];
    }
	// 找第几小的值
    long long kthquery(long long k)
    {
        int ret=0;
        if (k>=(1ll<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1ll<<i))
                ret^=p[i];
        return ret;
    }
};
ll a[200];
bool flag[200];
void solve(){
	int n;
	n=read();
	rep(i,1,n){
		a[i]=read();
	}
	sort(a+1,a+1+n);
	ll sum=0;
	for(int i=1;i<=n;i++){
		flag[i]=1;
		L_B  x;
		for(int j=1;j<=n;j++){
			if(flag[j]==0)  x.insert(a[j]);
		}
		bool ans=x.insert(a[i]);
		if(ans==0) sum+=a[i];
		else flag[i]=0;
	}
	cout<<sum<<endl;
}
int main (){
//  freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

练习11:排列计数 BZOJ4517 (* 错位排序)

传送门
题意:
求有多少种长度为 n n n 的序列 A A A.
满足以下条件: 1 1 1 ~ n n n n n n 个数在序列中各出现了一次若第 i i i个数 A [ i ] A[i] A[i] 的值为 i i i,则称 i i i 是稳定的。序列恰好有 m m m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。
思路:
前置知识错位排序: f ( n ) = ( f ( n − 1 ) + f ( n − 2 ) ) ∗ ( n − 1 ) f ( 1 ) = 0 , f ( 0 ) = 1 ; f(n)=(f(n-1)+f(n-2))*(n-1) f(1)=0,f(0)=1; f(n)=(f(n1)+f(n2))(n1)f(1)=0,f(0)=1;
情况1:插入第i个元素时,前 i − 1 i-1 i1个已经错位排好,则选择其中任意一个与第i个互换一定满足要求,选择方法共 i − 1 i-1 i1种,前 i − 1 i-1 i1位错排 f [ i − 1 ] f[i-1] f[i1]种,记 f [ i − 1 ] ∗ ( i − 1 ) f[i-1] * (i-1) f[i1](i1)
情况2:插入第i个元素时,前 i − 1 i-1 i1个中恰有一个元素 a [ j ] a[j] a[j]使得 a [ j ] = j a[j]=j a[j]=j,其他i-2个错位排好,则将 i i i j j j交换, j j j i − 2 i-2 i2位中的插入共 i − 1 i-1 i1种,前i-2位错排f[i-2]种,记 f [ i − 2 ] ∗ ( i − 1 ) f[i-2]*(i-1) f[i2](i1)
ACcode

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll qpow(ll a,ll x){
	ll ans=1;
	while(x){
		if(x&1){
			ans=(ans*a)%P;
		}
		a=(a*a)%P;
		x>>=1;
	}
	return ans;
}
ll a[N];
//ll inv[N]; 
ll f[N];
void pre(){
      a[0]=1;
	  for(int i=1;i<=1e6;i++){
		  a[i]=(a[i-1]*i)%P;
	  }
//	  for(int i=1;i<=2e7;i++){
//		  inv[i]=qpow(a[i],P-2);
//	  }
	  f[0]=1;
	  f[1]=0;
	  rep(i,2,1e6){
		  f[i]=(f[i-1]+f[i-2])*(i-1)%P;
	  }
	//   cout<<a[3]<<endl;
	  return ;
}
ll C(ll n,ll m){
	if(n<m) return 0;
	// printf("%d %d %d\n",a[n],inv[m],inv[n-m]);
	return (a[n]*qpow(a[m],P-2)%P *qpow(a[n-m],P-2))%P;
}
void solve(){
//	printf("%d %d\n",qpow(2,10),C(6,2));
    ll n,m;
	n=read();
	m=read();
	ll sum=C(n,m)*f[n-m]%P;
	printf("%lld\n",sum);
	return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
   pre();	
   int T=read();
   while(T--)
  solve();
  getchar();
  getchar();
  return 0;
}

练习12: Sky Code (* 容斥原理$莫比乌斯反演 经典)

题意: 给 N N N个数,求解 g c d ( a , b , c , d ) = = 1 gcd(a,b,c,d)==1 gcd(a,b,c,d)==1 的个数有多少。
思路: 容斥原理,经典中的经典。正面求解困难,求相反问题,四元数不互质的个数是多,然后 总 数 − 它 总数-它
考虑不互质的情况。当我们得出了 d = 2 , d = 3 d=2,d=3 d=2d=3的个数,那么在 d = 6 d=6 d=6时会重复,所以要减去。
总结: 莫比乌斯反演与容斥原理
ACcode

#include<iostream>
#include<cstring>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll d[N];
int v[N],prime[N],miu[N];
void get_miu(int n){
	int m=0;
	miu[1]=1;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;
			prime[++m]=i;
			miu[i]=-1;
		}
		for(int j=1;j<=m;j++){
			if(prime[j] > v[i] or prime[j]*i>n) break;
			v[i*prime[j]]=prime[j];
			if(miu[i]==0) miu[i*prime[j]]=0;
			if(v[i]==prime[j]) miu[i*prime[j]]=0;
			else miu[i*prime[j]]=-miu[i];
		}
	}
//	 rep(i,1,100){
//	 	printf("i:%d miu:%d\n",i,miu[i]);
//	 }
	return ;
}
void pre(int k){
	for(int i=1;i*i<=k;i++){
		if(k%i!=0) continue;
		if(i!=k/i){
			d[i]++;
			d[k/i]++;
		}else d[i]++;
	}
	return ;
}
void solve(){
	int n;
	while(scanf("%d",&n)==1){
		int maxn=0;
		int r;
	rep(i,1,n){
		pre(r=read());
		maxn=max(maxn,r);
		
	}
	ll ans=0;
	for(int i=1;i<=maxn;i++){
		ll tot=d[i]*(d[i]-1)*(d[i]-2)*(d[i]-3)/24;
		ans+=miu[i]*tot;
	}
	printf("%lld\n",ans);
	for(int i=1;i<=maxn;i++){
		d[i]=0;
	}
	}
	return ;
}
int main (){
//  freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  get_miu(10000);
  solve();
//  getchar();
//  getchar();
  return 0;
}

练习16 魔法珠 CH3B16 (SG博弈)

传送门
题意:
在这里插入图片描述
思路:经典SG博弈论。
ACcode

// submitted  by HNUST26
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
int a[1010];
int dfs(int  ans){
   if(a[ans]!=-1)  return a[ans];
   int sum=0;
   int b[1030];
   memset(b,0,sizeof(b));
   for(int i=1;i*i<=ans;i++){
   	   if(ans%i==0){
   	    if(i!=ans/i){
   	       	sum^=dfs(i);
   	       	if(i!=1) sum^=dfs(ans/i);
		}else  sum^=dfs(i);
	}
   }
   for(int i=1;i*i<=ans;i++){
   	    if(ans%i==0)
   	    {
   	    	b[sum^dfs(i)]=1;
   	    	if(i!=1) b[sum^dfs(ans/i)]=1;
		}
   }
   for(int i=0;;i++){
   	if(b[i]==0){
   		a[ans]=i;
//   		cout<<ans<<" "<<i<<endl;
   		return i;
	   }
   }
}
void solve(){
    int n;
    memset(a,-1,sizeof(a));
    a[1]=0;
    while(scanf("%d",&n)!=EOF){
        int ans=0;
        rep(i,1,n){
            ans^=dfs(read());
        }
        if(ans==0) cout<<"rainbow"<<endl;
        else cout<<"freda"<<endl;
    }
    return ;
}
int main (){
//  freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

练习17:Georgia and Bob (* NIM博弈三定理)

题意:排成直线的格子上放有 n n n个棋子。棋子i在左数第 p i p_i pi的格子上, G e o r g i a Georgia Georgia B o b Bob Bob轮流选择一个棋子向左移动,每次可以移动一格及任意多格,但是不允许反超其他的棋子,也不允许将两个棋子凡在同一个格子内。无法移动操作的一方失败。Georgia 先移动。
思路

错误思路

我最先想到的思路是,排个序, a [ i ] a[i] a[i]的棋子最终在 i i i的位置,具体实现如下:

for(int i=1;i<=n;i++){
            sum^=(a[i]-left-1);
            left++;
        }

当时WA了,我恨不能理解为什么。于是我返过头又看了看NIM博弈的原理是什么呢?

NIM博弈三定理

在这里插入图片描述
大概就是我要胜出的话,我需要抛出a1^ a2 ^ a3…^an=0给对方。那就是原理2了。所以呢,要满足NIM博弈还有一个要求的满足的条件是
每一推都要独立,不能受到影响。 而我在写的代码思路中,是一堆一堆石子取完,再取下一堆石子。而NIM博弈中我可以去一部分第一堆的石子,而后下一步可以从第二堆里去一部分石子。所以我的博弈想法出现了每一堆石子出现的先后的取,有影响。所以思路错误。

正确思路

在这里插入图片描述
在这里插入图片描述
而这里"只要将所加部分减回去就回到了原来的状态"。我的理解是我的博弈状态还是希望对方面临的异或和是0,我就能胜利。所以我并不想打破这种我能"胜利的局面",所以在原基础上我再减就好了。(有点像"敌不动,我不动",hmm我举的比喻,还算清楚吗?),所以在这里可以把他们看成一对一对的来处理,每一堆的石子是并没有影响的,就是独立的喽。
总结: 在做博弈论的题目时,先理清题意的要求,在使用结论时,始终依NIM博弈的三定理为依据且在策略中不能违背。
ACcode:

#include<iostream>
#include<algorithm>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll n,m;
int a[1010];
void solve(){
    int T=read();
    while(T--){
        n=read();
        rep(i,1,n) a[i]=read();
        ll sum=0;
        int i=1;
        sort(a+1,a+1+n);
        if(n&1) sum^=(a[1]-1),i++;
        
        for(;i+1<=n;i+=2){
            sum^=(a[i+1]-a[i]-1);
        }
        if(sum==0){
            printf("Bob will win\n");
        }else printf("Georgia will win\n");
    }
}
int main (){
//  freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

未完待续,持续更新中…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值