2021“MINIEYE杯”中国大学生算法设计超级联赛(8)(1002,1004,1006,1009)

前言

依旧是白嫖账号,只打了一些题/kk


正题


1002 Buying Snacks

题目大意

(n)个物品,每个可以买一次也可以不买,如果买需要选择(1/2)块钱的,然后也可以相邻两个一起买并且减少一块的花销,求恰好用掉(m)块钱的方案。

(1leq nleq 10^9,1leq mleq 20000,1leq Tleq 5)

解题思路

(f_{i,j})表示(i)个物品花(j)块钱的方案那么有

[f_{i,j}=f_{i-1,j}+f_{i-1,j-1}+f_{i-1,j-2}+f_{i-2,j-1}+2 imes f_{i-2,j-2}+f_{i-2,j-3} ]

然后化成生成函数就是

[F_i(x)=(1+x+x^2)F_{i-1}(x)+(x+2x^2+x^3)F_{i-2}(x) ]

和之前一道倍增(FFT)很像,然后考虑分割位置填两个就是(F_{1}(x)=1+x+x^2),然后

[F_{a+b}(x)=F_{a}(x)F_b(x)+(x+2x^2+x^3)F_{a-1}(x)F_{b-1}(x) ]

上倍增(FFT)就好了。

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1<<16,P=998244353;
ll T,n,k,m,r[N],f[3][N],t[3][N],g[2][N];
void fm(ll &x){x+=x>>31&P;}
ll power(ll x,ll b){
	ll ans=1;
	while(b){
		if(b&1)ans=ans*x%P;
		x=x*x%P;b>>=1;
	}
	return ans;
}
void NTT(ll *f,ll op){
	for(ll i=0;i<n;i++)
		if(i<r[i])swap(f[i],f[r[i]]);
	for(ll p=2;p<=n;p<<=1){
		ll tmp=power(3,(P-1)/p),len=p>>1;
		if(op==-1)tmp=power(tmp,P-2);
		for(ll k=0;k<n;k+=p){
			ll buf=1;
			for(ll i=k,tt;i<(k|len);i++){
				tt=1ll*buf*f[i|len]%P;
				fm(f[i|len]=f[i]-tt);
				fm(f[i]=f[i]+tt-P);
				buf=1ll*buf*tmp%P;
			}
		}
	}
	if(op==-1){
		ll invn=power(n,P-2);
		for(ll i=0;i<n;i++)
			f[i]=1ll*f[i]*invn%P;
	}
	return;
}
void print(ll x)
{if(x>9)print(x/10);putchar(x%10+'0');return;}
signed main()
{
	scanf("%lld",&T);
	while(T--){
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
		memset(t,0,sizeof(t)); 
		scanf("%lld%lld",&m,&k);k++;n=1;
		while(n<(k*2))n<<=1;
		for(ll i=0;i<n;i++)
			r[i]=(r[i>>1]>>1)|((i&1)?(n>>1):0);
		f[0][0]=f[0][1]=f[0][2]=f[1][0]=g[0][0]=1;
		for(ll d=1;d<=m;d<<=1){
			if(m&d){
				for(ll j=0;j<3;j++){
					for(ll i=0;i<n;i++)
						t[j][i]=(i<k)?f[j][i]:0;
					NTT(t[j],1);
				}
				NTT(g[0],1);NTT(g[1],1);
				for(ll i=0;i<n;i++){
					ll b0=g[0][i],b1=g[1][i];
					g[0][i]=1ll*b0*t[0][i]%P;
					g[1][i]=1ll*b0*t[1][i]%P;
					t[0][i]=1ll*t[1][i]*b1%P;
					t[1][i]=1ll*t[2][i]*b1%P;
				}
				NTT(g[0],-1);NTT(g[1],-1);
				NTT(t[0],-1);NTT(t[1],-1);
				for(ll i=0;i<k;i++){
					(g[0][i+1]+=t[0][i])%=P;(g[0][i+2]+=2ll*t[0][i])%=P;(g[0][i+3]+=t[0][i])%=P;
					(g[1][i+1]+=t[1][i])%=P;(g[1][i+2]+=2ll*t[1][i])%=P;(g[1][i+3]+=t[1][i])%=P;
				}
				for(ll i=k;i<n;i++)g[0][i]=g[1][i]=0;
			}
			if(d*2>m)break;
			for(ll j=0;j<3;j++){
				for(ll i=0;i<n;i++)
					t[j][i]=(i<k)?f[j][i]:0;
				NTT(t[j],1);
			}
			for(ll i=0;i<n;i++){
				f[0][i]=1ll*t[0][i]*t[0][i]%P;
				f[1][i]=1ll*t[1][i]*t[0][i]%P;
				f[2][i]=1ll*t[1][i]*t[1][i]%P;
				t[0][i]=1ll*t[1][i]*t[1][i]%P;
				t[1][i]=1ll*t[1][i]*t[2][i]%P;
				t[2][i]=1ll*t[2][i]*t[2][i]%P;
			}
			for(ll j=0;j<3;j++)
				NTT(f[j],-1),NTT(t[j],-1);
			for(ll i=0;i<k-1;i++){
				(f[0][i+1]+=t[0][i])%=P;(f[0][i+2]+=2ll*t[0][i])%=P;(f[0][i+3]+=t[0][i])%=P;
				(f[1][i+1]+=t[1][i])%=P;(f[1][i+2]+=2ll*t[1][i])%=P,(f[1][i+3]+=t[1][i])%=P;
				(f[2][i+1]+=t[2][i])%=P;(f[2][i+2]+=2ll*t[2][i])%=P;(f[2][i+3]+=t[2][i])%=P;
			}
			for(ll i=k;i<n;i++)f[0][i]=f[1][i]=f[2][i]=0;
		}
		for(ll i=1;i<k;i++)
			print(g[0][i]),putchar(' ');
		putchar('
');
	}
	return 0;
}

1004 Counting Stars

题目大意

(n)个数字要求支持操作

  1. 将一个区间所有数字的最低位减去
  2. 将一个区间非零数字的最高位提高一位

每次操作玩输出操作区间的和

(1leq Tleq 5,1leq n,qleq 10^5,1leq a_ileq 10^9)

解题思路

每个数字操作一最多执行(log a_i)次所以这个操作暴力做就好了

然后第二个操作我们把每个数字的最高位分离出来就变成了区间乘二。

代码是( ext{lemondinosaur})爷写的


1006 GCD Game

题目大意

(n)个数字每次操作的人可以选择一个数字(x)然后找一个(1leq k<x)(x)变为(gcd(x,k))

不能操作者输,求是否先手必胜
(1leq Tleq 100,sum nleq 10^6,1leq a_ileq 10^7)

解题思路

其实就是去掉任意个质因子,所以用线性筛筛出每个数字的最小质因子,然后递推出每个数字的质因子数就好了。
时间复杂度(O(a_i+sum n))

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e7+10;
int T,n,cnt,pri[N/10],v[N],c[N];
void Prime(){
	v[1]=1;
	for(int i=2;i<N;i++){
		if(!v[i])pri[++cnt]=i,v[i]=i;
		for(int j=1;j<=cnt&&i*pri[j]<N;j++){
			v[i*pri[j]]=pri[j];
			if(i%pri[j]==0)break;
		}
	}
	return;
}
int main()
{
	Prime();
	for(int i=2;i<N;i++)
		c[i]=c[i/v[i]]+1;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);int ans=0;
		for(int i=1,x;i<=n;i++)
			scanf("%d",&x),ans=ans^c[x];
		if(ans)puts("Alice");
		else puts("Bob");
	}
	return 0;
}

1009 Singing Superstar

题目大意

给一个字符串(S)(n)次询问字符串(a_i)(S)中出现的最大不重次数。

(1leq Tleq 5,1leq nleq 10^5,1leq |a_i|leq 30,sum |S|,sum|a_i|leq 4 imes 10^5)

解题思路

根据贪心的思想是能够匹配尽量匹配
把所有(S)长度不超过(30)的子串建一棵(Trie),然后记下每个子串的最后出现位置然后统计(ans)就好了。

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ull unsigned long long
using namespace std;
const int N=1e5+10;
int n,T,l,cnt,ans[N*30],t[N*30][26],last[N*30];
char s[N],st[N];
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);l=strlen(s+1);
		cnt=1;last[1]=ans[1]=0;
		memset(t[1],0,sizeof(t[1]));
		for(int i=1;i<=l;i++){
			int x=1;
			for(int j=1;j<=30;j++){
				if(i-j+1<1)break;
				int c=s[i-j+1]-'a';
				if(!t[x][c]){
					cnt++;memset(t[cnt],0,sizeof(t[cnt]));
					t[x][c]=cnt;last[cnt]=ans[cnt]=0;
				}
				x=t[x][c];
				if(i-last[x]>=j)last[x]=i,ans[x]++;
			}
		}
		scanf("%d",&n);
		while(n--){
			scanf("%s",st+1);
			int tl=strlen(st+1),x=1;
			for(int i=tl;i>=1;i--)
				x=t[x][st[i]-'a'];
			printf("%d
",ans[x]);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/15134289.html