Pbri

正睿NOIP十连测

正睿NOIP十连测

Day1

T1

题意:

\(\sum_{i=1}^n\sum_{j=1}^p\varphi(i^j)\)\(n\le 10^7,p\le 10^9\)

题解:

考虑到 \(\varphi(i^j)=\varphi(i)\times i^{j-1}\) ,于是只需要考虑求等比数列和即可。线筛出 \(\varphi,i^p\) ,线性推逆元就好了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define MAXN 10000010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int n,p;
int prm[MAXN],prnum;
bool vis[MAXN];
ll phi[MAXN],inv[MAXN],mi[MAXN],ans;
ll poww(ll a,int b)
{
	ll ans=1,base=a;
	while(b)
	{
		if(b&1) ans=ans*base%MOD;
		base=base*base%MOD;
		b>>=1;
	}
	return ans;
}
void sieve()
{
	FUP(i,2,n)
	{
		if(!vis[i]) prm[++prnum]=i,phi[i]=i-1,mi[i]=poww(i,p);
		FUP(j,1,prnum)
		{
			if(i*prm[j]>n) break;
			vis[i*prm[j]]=true,mi[i*prm[j]]=mi[i]*mi[prm[j]]%MOD;
			if(i%prm[j]) phi[i*prm[j]]=phi[i]*phi[prm[j]]%MOD;
			else
			{
				phi[i*prm[j]]=phi[i]*prm[j]%MOD;
				break;
			}
		}
		
	}
	inv[1]=phi[1]=1;
	FUP(i,2,n) inv[i]=MOD-(MOD/i)*inv[MOD%i]%MOD;
	//FUP(i,1,n) printf("i=%d phi=%lld inv=%lld\n",i,phi[i],inv[i]);
}
int main(){
	n=read(),p=read(),ans=p;
	sieve();
	//printf("%lld\n",inv[9]);
	FUP(i,2,n)
	{
		ans=(ans+(mi[i]-1)*inv[i-1]%MOD*phi[i]%MOD)%MOD;
		//printf("i=%d %lld\n",i,(mi[i]-1)*inv[i-1]%MOD*phi[i]%MOD);
	}
	printf("%lld\n",ans);
	return 0;
}

T2

题意:

求由所有 \(n\) 排列构成的大根堆中有多少个堆满足:对于给定的 \(S\) 个数 \(1\le a_i\le n\),编号是 \(a_i\) 的结点都是叶子。左右儿子有区别, \(S\le n\le 10^6\)

题解:

从后往前构造,考虑一个结点有多少个可以放的位置。对于一个普通的结点它插入之后,多了一个可以放的位置。对于一个强制叶子结点,插入之后少了一个可以放的位置,于是把每个结点可以放的位置数乘起来就好了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define MAXN 1000010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int n,S;
ll ans=1,cnt=1;
bool vis[MAXN];
int main(){
	n=read(),S=read();
	FUP(i,1,S) vis[read()]=true;
	FDW(i,n,1)
	{
		ans=ans*cnt%MOD;
		if(!vis[i]) cnt++;
		else cnt--;
	}
	printf("%lld\n",ans);
	return 0;
}

T3

题意:

定义一个长度为 \(l\)\(SCC\) 序列为:插入 \(l\) 条不存在自环和重边的有向边,\(a_i(1\le i\le l)\) 表示插入第 \(1\)\(i\) 条边之后有 \(a_i\)\(SCC\) 。问给定 \(n\) ,对于 \(l=1,2,3...n\times(n-1)\) ,求出有多少种不同的长度为 \(l\)\(SCC\) 序列。 \(n\le 100\)

题解:

首先考虑对于所有的 \(SCC\) 序列,我们都可以通过维护一个大的 \(SCC\) 和一堆单点来形成这样的情况。也就是说在加入 \(i\) 条边之后,这个图中有一个由 \(1\)\(n-(a_i-1)\) 的结点形成的一个大 \(SCC\) ,以及剩下的 \(a_i-1\) 个单点,其中两个单点或者 \(SCC\) 內部或者 \(SCC\) 内部与单点之间有边,但是不改变目前的 \(SCC\) 局面。然后我们设 \(f[i][j][k]\) 表示加入 \(i\) 条边,目前这个大 \(SCC\) 囊括到了 \(j\) (或者说大小为 \(j\) ),这 \(1\)\(j\) 个点中有 \(k\) 个点,在合并进这个大 \(SCC\) 时,大 \(SCC\) 到自己的每个点之间都连着边,自己向大 \(SCC\) 连边从而合并进去的。然后我们转移的时候,考虑再加入一条边有没有可能不改变目前的局面?这样就体现了采取这种方式构件图的好处:可以在目前的情况中再加入尽量多的边,保证转移不会漏下。显然在 \(n\) 个点之间两两从前往后连边,在 \(j\) 个点构成的 \(SCC\) 中可以连反向边,于是只要 \(i\le \dfrac{n\times(n-1)}{2}+\dfrac{j\times(j-1)}{2}\)\(f[i][j][k]\) 就可以由 \(f[i-1][j][k]\) 做贡献。如果要改变原先的局面的话,那么我们枚举之前的大 \(SCC\) 到了 \(l(l<j)\) ,然后自己要向前面连一条反向边,所以是由 \(\sum_{l=1}^{j-1}f[i-1][l][k-1]\) 转移过来。然而你还要考虑 \(i\) 条边够不够形成这样的情况。从 \(1\) 连到 \(j\) 需要 \(j-1\) 条边,中间有 \(k\) 条反向边,所以 \(i\ge j-1+k\) 的时候才可以转移。然后这个东西可以前缀和优化就不用枚举了,用滚动数组空间就是 \(n^2\) ,时间复杂度 \(\Theta(n^4)\)

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define MAXN 110
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int T;
int n,MOD,f[2][MAXN][MAXN],sum[MAXN][MAXN];
void write(int x)
{
	if(x>9) write(x/10);
	putchar('0'+x%10);
}
int add(int x,int y)
{
	x+=y;
	if(x>=MOD) x-=MOD;
	return x;
}
void solve()
{
	n=read(),MOD=read();
	f[0][1][0]=1;
	int nw=0,pr=1;
	FUP(i,1,n*(n-1))
	{
		nw^=1,pr^=1;
		memset(f[nw],0,sizeof(f[nw]));
		memset(sum,0,sizeof(sum));
		FUP(j,1,n) FUP(k,0,n) sum[j][k]=add(sum[j-1][k],f[pr][j][k]);
		int ans=0;
		FUP(j,1,min(i,n))
		{
			FUP(k,0,min(n,i))
			{
				if(i<=n*(n-1)/2+j*(j-1)/2) f[nw][j][k]=f[pr][j][k];
				if(i>=j-1+k) f[nw][j][k]=add(f[nw][j][k],sum[j-1][k-1]);
				ans=add(ans,f[nw][j][k]);
			}
		}
		write(ans),putchar(' ');
	}
	puts("");
}
int main(){
	//freopen("my.out","w",stdout);
	T=read();
	while(T--) solve();
	return 0;
}

T4

题意:

\(n\) 个数, \(A\)\(B\) 轮流操作, \(A\) 先手,每次操作可以从序列两头选择一个数拿走并异或手里的值,初始两人手里的值都是 \(0\) ,如果两人都足够聪明,那么最后是 \(A\) 赢还是 \(B\) 赢还是平局。

题解:

首先平局当且仅当所有的点异或起来是 \(0\) 。接下来从高位向低位考虑,如果对于这 \(n\) 个数,这一位是 \(1\) 的数量是偶数,那么在这一位肯定平局,直到数量是奇数,在这一位就可以决出胜负。所以现在问题转化为了:给 \(n\) 个非 \(0\)\(1\) 的序列,其中有奇数个 \(1\) ,问谁有必胜的策略。首先如果 \(n\) 是偶数,那么要么奇数的位置上有奇数个 \(1\) ,要么偶数的位置上有奇数个 \(1\) ,这样 \(A\) 先手就可以决定接下来每一个人选的位置是奇数位置还是偶数位置,它只要保证选则奇数的那个即可。如果 \(n\) 是偶数,如果两头都是 \(0\) ,那么 \(A\) 取哪个都没区别,因为转化成了偶数的情况,后手必胜。否则 \(A\) 先取一个 \(1\) ,接下来 \(A\) 必须每次都跟 \(B\) 取一样的,否则 \(A\) 手里的就变成和 \(B\) 手里一样的了,然后后手必胜。所以贪心的把两边相同的都抵消掉,然后最后必须变成 \(a_1=a_2,a_3=a_4,a_5=a_6...\) 这样才行。还有就是如果剩下的 \(1\) 的数量不是 \(4\) 的倍数,那么 \(A\) 最后就是取了奇数个 \(1\) ,加上一开始那个 \(1\) 变成偶数也输了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define MAXN 100010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int T;
int n,a[MAXN],b[MAXN];
bool ck(int l,int r)
{
	while(l<r&&b[l]==b[r]) l++,r--;
	FUP(i,l,r)
	{
		if(b[i]!=b[i+1]) return false;
		i++;
	}
	return true;
}
void solve(){
	n=read();
	FUP(i,1,n) a[i]=read();
	int w=0;
	FUP(i,1,n) w^=a[i];
	if(!w)
	{
		puts("Draw");
		return;
	}
	FDW(i,30,0)
	{
		int cnt=0;
		FUP(j,1,n) b[j]=(a[j]>>i)&1;
		FUP(j,1,n) cnt+=b[j];
		//FUP(j,1,n) printf("%d ",b[j]);
		//puts("");
		if(cnt%2==0) continue;
		if(n%2==0)
		{
			puts("Alice");
			return;
		}
		if((cnt-1)%4!=0)
		{
			puts("Bob");
			return;
		}
		if((b[1]&&ck(2,n))||(b[n]&&ck(1,n-1))) puts("Alice");
		else puts("Bob");
		return;
	}
}
int main(){
	//freopen("surreal2.in","r",stdin);
	//freopen("my.out","w",stdout);
	T=read();
	while(T--) solve();
	return 0;
}

posted @ 2021-08-30 16:36  Pbri  阅读(440)  评论(0编辑  收藏  举报