暑期集训7

130 rank 39

T1:约瑟夫问题(见专题)

T2:暴力模拟

T3 让人痴迷(痴狂)的数论

T4:【甜圈】线段树(hash区间加乘或者直接维护区间信息)

T3:给你T组询问,每组询问(n,k)求n!从最低位开始非0的k位。(n<=1e100,k<=3)

就是要求n!/\(10^k\)(%1000),直接求出结果困难(??)考虑拆分成2个部分,n!/\(10^k\)(%125),n!/\(10^k\)(%8),然后用CRT进行合并。
但是其实不用CRT考虑n!中2的因数个数远>5,所以(n!)/\(10^k\)(%8)绝大部分是0【除了n<=6此时阶乘不满足<=8自然不能用后文的简单算法,需要直接暴算出来】,即存在x,是8的倍数,x%125=ans%125。
考虑求出ans。就是n!/\(10^k\)(%125)。对分母进行拆分,\(10^k=2^k*5^k\)其中\(n!/5^k\)(mod 125)可以用扩展卢卡斯求出。证明过程:https://oi-wiki.org/math/number-theory/lucas/
其实就是分成3个部分:
【1】递归子问题求解的$(n/5)! / 5^m \(【2】整块预处理的FAC【125】的次幂【3】散块预处理的答案fac. 细节就是\)fac[125]^(n/(5 ^ m))$在mod 125意义下指数需要mod上phi[125]=100,因为gcd(125,n/\(5^k\))=1.
最后就是把答案*上\(2^m\)(mod 125).同样【1】求\(2^m\),指数优化,\(2^m \equiv 2^{m mod phi(125)}\),一个求指数m的技巧,即求阶乘x!的因数p的个数=\(\sum_{i=1}^{\propto } x/p^{i}\)/

from GZK
char s[M];
int l,k;
int fac[M],FAC[M];
struct big{
	int a[M],len;
	void get(){
		len=(l-1)/4+1;
		for(int i=0;i<=100;i++) a[i]=0;
		for(int i=1;i<len;i++){
			a[i]=s[l-4*i]*1000+s[l-4*i+1]*100+s[l-4*i+2]*10+s[l-4*i+3];
		}
		for(int i=1;i<=(l-1)%4+1;i++){
			a[len]=a[len]*10+s[i-1];
		}
	}
	void operator /= (int k){
		for(int i=len;i;i--){
			a[i-1]+=a[i]%k*10000;
			a[i]/=k;
		}
		while(a[len]==0&&len>0) len--;
	}
	int operator % (int k){
		return a[1]%k;
	}
	bool operator > (int k){
		return len>1||a[1]>k;
	}
};
big n,m;
int qpow(int x,int k,int Mod){
	int res=1;
	while(k){
		if(k&1) res=res*x%Mod;
		x=x*x%Mod;
		k>>=1;
	}
	return res;
}
int ext(big n){
	if(n>125);
	else return fac[n.a[1]];
	int x=n%125;
	n/=5;
	int K=ext(n);
	n/=25;
	return K*qpow(FAC[125],n%100,125)%125*FAC[x]%125;
}
void output(int x,int k){//0 1 2 3
	// for(int i=k;i;i--){
	// 	if(x%Mod[i]/Mod[i-1]==0) printf("0");
	// 	else break;
	// }
	if(k==1)printf("%01d\n",x%Mod[k]);
	else if(k==2)printf("%02d\n",x%Mod[k]);
	else if(k==3)printf("%03d\n",x%Mod[k]);
}
int ans,t,tot;
void work(){
	scanf("%s%d",s,&k);
	l=strlen(s);
	for(int i=0;i<l;i++) s[i]-=48;
	n.get();
	ans=ext(n);
	if(l==1&&n.a[1]<=6)
	{
		int a=1;
		for(int i=1;i<=n.a[1];i++){
			a*=i;
		}
		while(a%10==0) a/=10;
		output(a,k);
		return;
	}
	m=n;tot=0;
	while(m.len) m/=5,(tot+=m%100)%=100;
	ans*=qpow(63,tot,125);
	ans%=125;
	for(int i=0;i<1000;i+=8){
		if(i%125==ans) 
		{output(i,k);break;}
	}
}
signed main(){
	fac[0]=FAC[0]=1;
	for(int i=1;i<=125;i++){
		int j=i;
		while(j%5==0) j/=5;
		fac[i]=fac[i-1]*j%125;
		FAC[i]=FAC[i-1]*(i%5==0?1:i)%125;
	}
	int t=read();
	while(t--){
		work();
	}
	return 0;
}

tips:对于暴力,其实只要预处理出faci在除去末尾的0之后的%1e7之后的结果,在查询的时候输出,就可以得到50tps的好成绩,但是注意%1e7,就是模数尽量大一些,不然会有精度误差。(如果你%100,最后fac[1e7]的结果会面目全非......)

from Soytony
int t,n,k;
int fac[maxn];
int main(){
    fac[1]=1;
    ll now=1;
    for(int i=2;i<=maxn-10;++i){
        now=now*i;
        while(now%10==0&&now) now/=10;
        now%=1000000000;
        fac[i]=now;
    }
    t=read();
    while(t--){
        int n=read(),k=read();
        if(k==1) printf("%01d\n",fac[n]%10);
        else if(k==2) printf("%02d\n",fac[n]%100);
        else printf("%03d\n",fac[n]%1000);
    }
    return 0;
}

T4:给你n个盒子,初始为空,支持t个操作,每次(l,r,xi),表示在[l,r]区间编号的盒子有序放上xi编号物品。求最终满足盒子里的物品恰好是k个而且编号从小到大1--k的盒子数量。

[1]赛时自己打了个线段树,WA得很潇洒(10tps)
[2]正解1:线段树的思路,但是非常巧妙而且简单。你想象每次给一段区间操作,就像按照顺序放一堆有编号的东西,我只关注每次相邻的编号是否连续,因此我只在树上每个节点维护
2个信息:完整区间覆盖的最早的物品编号st,最晚的ed。如果不是完整继续下放,就像是对于包含关系的节点形成了由上到下的由st和ed拼接起来的“一条龙”。
(1)可以很好避免对于覆盖问题的判断,因为如果维护lazy标记那最新的lazy会覆盖上一个,但是我下放的时候节点合法性需要用到最早的一个
(2)可以及时排除冗余,当且仅当拼接会产生不合法,及时=-1,update和query根本不进去,复杂度就有了保证(本来就有保证!)
正解2:hash维护序列是否合法,区间加乘。

hash
const int N=2e5+10;
ull jia[N<<2],che[N<<2];
ull fina;
#define lson (rt<<1)
#define rson (rt<<1|1)
const ull base=131;
int n,k,t;
inline void build(int rt,int l,int r)
{
  che[rt]=1;if(l==r)return ;
  int mid=(l+r)>>1;
  build(lson,l,mid);build(rson,mid+1,r);
}
inline void pushdown(int rt)
{
  if(che[rt]!=1)//乘的在先
  {
    che[lson]*=che[rt];
    che[rson]*=che[rt];
    jia[lson]*=che[rt];
    jia[rson]*=che[rt];
    che[rt]=1;
  }
  if(jia[rt])
  {
    jia[lson]+=jia[rt];
    jia[rson]+=jia[rt];
    jia[rt]=0;
  }
}
inline void update(int rt,int l,int r,int L,int R,int opt)
{
  if(L<=l&&r<=R)
  {
     jia[rt]*=base;che[rt]*=base;jia[rt]+=opt;
     return;
  }
  pushdown(rt);
  int mid=(l+r)>>1;
  if(L<=mid)update(lson,l,mid,L,R,opt);
  if(R>mid)update(rson,mid+1,r,L,R,opt);
}
inline int query(int rt,int l,int r)
{
  if(l==r)
  {
    if(che[rt]+jia[rt]==fina)return 1;
    return 0;
  }
  int mid=(l+r)>>1;
  pushdown(rt);
  return query(lson,l,mid)+query(rson,mid+1,r);
}
int main()
{
	//freopen("d.in","r",stdin);
	//freopen("c1.out","w",stdout);
  n=re(),k=re(),t=re();fina=1;
  _f(i,1,k)fina=fina*base+i;
  build(1,1,n);
  _f(i,1,t)
  {
    int lo=re(),ro=re(),xi=re();
    update(1,1,n,lo,ro,xi);
  }
  chu("%d",query(1,1,n));
	return 0;
}


const int N=2e5+10;
int n,k,t;
#define lson (rt<<1)
#define rson (rt<<1|1)
int st[N<<2],ed[N<<2];
inline void pushdown(int rt)
{
	if(!st[rt])return;
	if(!st[lson])st[lson]=st[rt],ed[lson]=ed[rt];
	else if(ed[lson]!=st[rt]-1)ed[lson]=st[lson]=-1;
	else ed[lson]=ed[rt];
	if(!st[rson])st[rson]=st[rt],ed[rson]=ed[rt];
	else if(ed[rson]!=st[rt]-1)ed[rson]=st[rson]=-1;
	else ed[rson]=ed[rt];
	st[rt]=ed[rt]=0;
}
inline void update(int rt,int l,int r,int L,int R,int opt)
{
	if(st[rt]==-1)return;
	if(L<=l&&r<=R)
	{
		if(!st[rt]){st[rt]=ed[rt]=opt;}
		else if(ed[rt]!=opt-1)st[rt]=ed[rt]=-1;
		else ed[rt]=opt;
		return;
	}
	int mid=(l+r)>>1;pushdown(rt);
	if(L<=mid)update(lson,l,mid,L,R,opt);
	if(R>mid)update(rson,mid+1,r,L,R,opt);
}
inline int query(int rt,int l,int r)
{
	if(st[rt]==-1)return 0;
	if(l==r)return (st[rt]==1&&ed[rt]==k);
	pushdown(rt);int mid=(l+r)>>1;
	return query(lson,l,mid)+query(rson,mid+1,r);
}
int main()
{
	//freopen("d.in","r",stdin);
	//freopen("c1.out","w",stdout);
	n=re(),k=re(),t=re();
	_f(i,1,t)
	{
		int lk=re(),rk=re(),xi=re();
		update(1,1,n,lk,rk,xi);
	}
	chu("%d",query(1,1,n));
	return 0;
}
posted on 2022-08-21 16:19  HZOI-曹蓉  阅读(34)  评论(0)    收藏  举报