5.19 考试修改+总结

作为一个也是出过几道水题的人,强烈谴责第三题这种不给数据范围的题目

而且给出的一些数的数据范围还和数据不符,表示非常不兹磁

 

先放题解吧

首先第一题开场5min推出来,10min写完,之后5min就拍上了

没什么好说的,首先一个很重要的结论是各位数字的乘积的可能情况并不多

大概有3w多个吧,之后我们枚举各位数字的乘积就可以确定这样的数字的取值范围

之后就转换成了SCOI Blinker的仰慕者了,而且比这道题目少一种情况(k=0)

记忆化搜索写数位DP就可以了

状态是f[i][j]表示i位数字乘积为j的方案数(j被哈希掉了)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

typedef long long LL;
const int MOD=133331;
const int maxn=200010;
LL A,B,ans;
LL f[22][40010];
int Num[22],len;
struct HASHMAP{
	int cnt;
	int h[MOD+10],next[maxn];
	LL st[maxn];
	void push(LL S){
		int key=S%MOD;
		for(int i=h[key];i;i=next[i]){
			if(st[i]==S)return;
		}
		++cnt;next[cnt]=h[key];h[key]=cnt;
		st[cnt]=S;return;
	}
	int ask(LL S){
		int key=S%MOD;
		for(int i=h[key];i;i=next[i]){
			if(st[i]==S)return i;
		}return 0;
	}
}H;

void check(LL n){
	memset(Num,0,sizeof(Num));len=0;
	if(!n){Num[++len]=0;return;}
	while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,LL mul,int flag,int first){
	if(!pos){
		if(!first&&mul==1)return 1;
		return 0;
	}
	int now=H.ask(mul);
	if(!flag&&!first&&f[pos][now]!=-1)return f[pos][now];
	LL tmp=0;
	if(first)tmp=tmp+DFS(pos-1,mul,0,1);
	int u=flag?Num[pos]:9;
	for(int i=1;i<=u;++i){
		if(mul%i==0){
			tmp=tmp+DFS(pos-1,mul/i,flag&&i==u,0);
		}
	}return (flag||first)?tmp:f[pos][now]=tmp;
}

int main(){
	scanf("%lld%lld",&A,&B);
	memset(f,-1,sizeof(f));
	for(int i=1;i<=9;++i)H.push(i);
	for(int i=2;i<=18;++i){
		int now=H.cnt;
		for(int j=1;j<=now;++j){
			for(int k=1;k<=9;++k)H.push(H.st[j]*k);
		}
	}
	for(int i=1;i<=H.cnt;++i){
		LL L=(A%H.st[i]==0?A/H.st[i]:A/H.st[i]+1),R=B/H.st[i];
		if(L>R)continue;
		if(R==0)continue;
		check(R);ans+=DFS(len,H.st[i],1,1);
		check(L-1);ans-=DFS(len,H.st[i],1,1);
	}printf("%lld\n",ans);
	return 0;
}

第二题一看还以为是BZOJ上的排队,没看题目的时候觉得不会真是要写树套树吧

看完题目发现是个线段树的丝薄题

如果一段区间长度为B-A+1,且最小值为A,最大值为B,那么就存在

否则不存在

我直接按照这个思路写的,时间复杂度O(nlogn)

后来发现考试的时候自己犯傻,其实只需要维护一个位置的线段树就可以了,每次查询位置的最大值最小值作差判断是否=len就可以了

下午为了秀技术,强行写了一发fhq_treap,也A了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=200010;
int n,m,type;
int x,y,A,B;
int a[maxn],pos[maxn];
int p[maxn<<2];
int mx[maxn<<2];
int mn[maxn<<2];

void read(int &num){
	num=0;char ch=getchar();
	while(ch<'!')ch=getchar();
	while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
int Min(int a,int b){return a<b?a:b;}
int Max(int a,int b){return a>b?a:b;}
void build_pos(int o,int L,int R){
	if(L==R){p[o]=pos[L];return;}
	int mid=(L+R)>>1;
	build_pos(o<<1,L,mid);
	build_pos(o<<1|1,mid+1,R);
	p[o]=Min(p[o<<1],p[o<<1|1]);
}
void build_a(int o,int L,int R){
	if(L==R){mn[o]=mx[o]=a[L];return;}
	int mid=(L+R)>>1;
	build_a(o<<1,L,mid);
	build_a(o<<1|1,mid+1,R);
	mn[o]=Min(mn[o<<1],mn[o<<1|1]);
	mx[o]=Max(mx[o<<1],mx[o<<1|1]);
}
void UPD_pos(int o,int L,int R,int po,int v){
	if(L==R){p[o]=v;return;}
	int mid=(L+R)>>1;
	if(po<=mid)UPD_pos(o<<1,L,mid,po,v);
	else UPD_pos(o<<1|1,mid+1,R,po,v);
	p[o]=Min(p[o<<1],p[o<<1|1]);
}
void UPD_a(int o,int L,int R,int p,int v){
	if(L==R){mn[o]=mx[o]=v;return;}
	int mid=(L+R)>>1;
	if(p<=mid)UPD_a(o<<1,L,mid,p,v);
	else UPD_a(o<<1|1,mid+1,R,p,v);
	mn[o]=Min(mn[o<<1],mn[o<<1|1]);
	mx[o]=Max(mx[o<<1],mx[o<<1|1]);
}
int ask_pos(int o,int L,int R,int x,int y){
	if(L>=x&&R<=y)return p[o];
	int mid=(L+R)>>1;
	if(y<=mid)return ask_pos(o<<1,L,mid,x,y);
	else if(x>mid)return ask_pos(o<<1|1,mid+1,R,x,y);
	else return Min(ask_pos(o<<1,L,mid,x,y),ask_pos(o<<1|1,mid+1,R,x,y));
}
pair<int,int> ask_a(int o,int L,int R,int x,int y){
	if(L>=x&&R<=y)return make_pair(mx[o],mn[o]);
	int mid=(L+R)>>1;
	if(y<=mid)return ask_a(o<<1,L,mid,x,y);
	else if(x>mid)return ask_a(o<<1|1,mid+1,R,x,y);
	else{
		pair<int,int>t1=ask_a(o<<1,L,mid,x,y);
		pair<int,int>t2=ask_a(o<<1|1,mid+1,R,x,y);
		return make_pair(Max(t1.first,t2.first),Min(t1.second,t2.second));
	}
}

int main(){
	freopen("line.in","r",stdin);
	freopen("line.out","w",stdout);
	read(n);read(m);
	for(int i=1;i<=n;++i)read(a[i]),pos[a[i]]=i;
	build_pos(1,1,n);build_a(1,1,n);
	for(int i=1;i<=m;++i){
		read(type);
		if(type==1){
			read(x);read(y);
			A=a[x];B=a[y];
			UPD_pos(1,1,n,A,y);
			UPD_pos(1,1,n,B,x);
			UPD_a(1,1,n,x,B);
			UPD_a(1,1,n,y,A);
			a[x]=B;a[y]=A;
			pos[B]=x;pos[A]=y;
		}else{
			read(A);read(B);
			int len=B-A+1;
			int now=ask_pos(1,1,n,A,B);
			pair<int,int>tmp=ask_a(1,1,n,now,now+len-1);
			if(tmp.first==B&&tmp.second==A)printf("YES\n");
			else printf("NO\n");
		}
	}return 0;
}

上午写完上面两道题+拍上大概到了8:30吧

之后就一直对着第三题的数据范围死磕,后来考完告诉我第三题的数据和数据范围不符?

这是在逗我?然后没有给出qi和k的范围,自己写的程序爆了long long,只拿了27分

结果挂成了rank2,正常暴力分55,如果他的数据是按照第三题题面的话我有70分

 

由于t很大,很自然的想到要矩阵乘法来优化

一种暴力的做法是直接构造系数矩阵乘法,配合上t<=1000的丝薄普及组暴力就有55分辣

(数据水的我都不想吐槽什么)

我们注意到题面中数据有只有5个点有初始信息的,我们又发现每个点的贡献是可分离的

如果我们能搞出每个点对其他点的贡献系数,我们就可以过掉这些点了

我们考虑只有一个点有初始信息

不难用数学归纳法证明,若两个点跟这个点的海明码距离均为k,那么这两个点可以看成一个等价类

那么我们就有了(m+1)个等价类,k向k+1转移的系数显然为m-k,k向k-1转移的系数显然为k

然后构造矩阵,矩阵乘法就可以搞出这些系数啦

这样我们就搞定了只有5个点有初始信息的情况(然而真实数据中并没有这种情况)

之后我们考虑j对i的贡献系数,设Numi表示i的二进制表示中有多少个1,f(k)表示矩阵乘法得到的海明码距离为k的时候的系数

显然j对i的贡献为g(j)*f(Num(i^j))

令k=i^j,易得k^j=i

则构造出FWT的基本形式h(i)=sigma(g(j)*f(Num(k))(满足j^k=i)

由于模数有可能没有2的逆元,所以我们将模数*2^m之后做FWT再还原即可

(不要问我为什么乘起来不会爆long long,题面没有写,但数据就是这么造的)

原理是:(k*a)%(p*a)=(k%p)*a

这样我们就不用考虑逆元问题,a一定是2的倍数,直接/2就可以啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

typedef long long LL;
int m;
int Num[1050010];
LL n,t,mod;
LL f[1050010],g[1050010];
struct Matrix{
	LL a[22][22];
	void clear(){memset(a,0,sizeof(a));}
}A,ans;

LL mul(LL a,LL b){
	LL s=0;
	while(b){
		if(b&1){
			s=s+a;
			if(s>=mod)s-=mod;
		}
		a=(a<<1);
		if(a>=mod)a-=mod;
		b>>=1;
	}return s;
}
Matrix operator *(const Matrix &A,const Matrix &B){
	Matrix C;C.clear();
	for(int i=0;i<=m;++i){
		for(int j=0;j<=m;++j){
			for(int k=0;k<=m;++k){
				C.a[i][j]=C.a[i][j]+mul(A.a[i][k],B.a[k][j]);
				if(C.a[i][j]>=mod)C.a[i][j]-=mod;
			}
		}
	}return C;
}
Matrix pow_mod(Matrix v,LL p){
	Matrix tmp;tmp.clear();
	for(int i=0;i<=m;++i)tmp.a[i][i]=1;
	while(p){
		if(p&1)tmp=tmp*v;
		v=v*v;p>>=1;
	}return tmp;
}
void FWT(LL *A,int n,int flag){
	for(int k=1;k<n;k<<=1){
		int mk=(k<<1);
		for(int j=0;j<n;j+=mk){
			for(int i=0;i<k;++i){
				LL x=A[i+j],y=A[i+j+k];
				A[i+j]=(x+y)>>flag;A[i+j+k]=(x-y+mod)>>flag;
				if(A[i+j]>=mod)A[i+j]-=mod;
				if(A[i+j+k]>=mod)A[i+j+k]-=mod;
			}
		}
	}return;
}

int main(){
	scanf("%lld%lld%lld",&n,&t,&mod);
	mod*=n;
	for(m=0;(1<<m)<=n;m++);m--;
	for(int i=0;i<=m;++i){
		if(i>0)A.a[i-1][i]+=i;
		if(i<m)A.a[i+1][i]+=(m-i);
	}
	A=pow_mod(A,t);
	ans.a[0][0]=1;
	ans=ans*A;
	for(int i=0;i<n;++i)Num[i]=Num[i>>1]+(i&1);
	for(int i=0;i<n;++i)scanf("%lld",&f[i]),f[i]%=mod;
	for(int i=0;i<n;++i)g[i]=ans.a[0][Num[i]];
	FWT(g,n,0);FWT(f,n,0);
	for(int i=0;i<n;++i)f[i]=mul(f[i],g[i]);
	FWT(f,n,1);mod/=n;
	for(int i=0;i<n;++i)printf("%lld\n",f[i]%mod);
	return 0;
}

本题其实还有第二种解法,加一些奇技淫巧就可以过了

但是本人并不会O(1)快速乘的写法

所以还是会T的代码,时间复杂度O(nlog^2n)

但是思维量很小,就是我们考虑每一次的转移

j能转移到k当且仅当j^(1<<i)=k,且转移系数为1

我们构造FWT就可以处理一次转移了

之后怎么办呢?FWT+快速幂?

其实并不需要,我们只需要FWT之后对FWT出来的值做快速幂就可以了

因为快速幂的时候需要快速乘,所以时间复杂度O(nlog^2n)了

之后还原FWT即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
using namespace std;
 
const int maxn=1050010;
typedef long long LL;
int m;
LL n,t,mod;
LL f[maxn],g[maxn];
 
inline LL mul(LL a,LL b) {
    LL res=0;
    for(;b;b>>=11,a=(a<<11)%mod)res=(res+a*(b&0x7ff))%mod;
    return res;
}
LL pow_mod(LL v,LL p){
    LL tmp=1;
    while(p){
        if(p&1)tmp=mul(tmp,v);
        v=mul(v,v);p>>=1;
    }return tmp;
}
void FWT(LL *A,int n,int flag){
    for(int k=1;k<n;k<<=1){
        int mk=(k<<1);
        for(int j=0;j<n;j+=mk){
            for(int i=0;i<k;++i){
                LL x=A[i+j],y=A[i+j+k];
                A[i+j]=(x+y)>>flag;A[i+j+k]=(x-y+mod)>>flag;
                if(A[i+j]>=mod)A[i+j]-=mod;
                if(A[i+j+k]>=mod)A[i+j+k]-=mod;
            }
        }
    }return;
}
 
int main(){
    scanf("%lld%lld%lld",&n,&t,&mod);
    for(m=0;(1<<m)<=n;m++);m--;mod*=n;
    for(int i=0;i<n;++i)scanf("%lld",&f[i]);
    for(int i=1;i<n;i<<=1)g[i]=1;
    FWT(f,n,0);FWT(g,n,0);
    for(int i=0;i<n;++i){
        g[i]=pow_mod(g[i],t);
        f[i]=mul(g[i],f[i]);
    }
    FWT(f,n,1);mod/=n;
    for(int i=0;i<n;++i)printf("%lld\n",f[i]%mod);
    return 0;
}

 

表示自己的知识点部分还是有些欠缺的

上午成功的将系数弄了出来,有很大的进步

但是看出是FWT不会写也是令人伤心

所以还是要努力啊,做做FWT的题目之后写一发总结吧

(挖坑ing)

加油!八个月!三月份!

转载于:https://www.cnblogs.com/joyouth/p/5510156.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值