• 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
    ",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
    ");
    			else printf("NO
    ");
    		}
    	}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
    ",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
    ",f[i]%mod);
        return 0;
    }
    

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

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

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

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

    (挖坑ing)

    加油!八个月!三月份!

  • 相关阅读:
    IOS之Block的应用-textFeild的回调应用
    KVC与KVO的不同
    git
    perl读取excel
    Linux用户管理
    Linux软件包的管理
    linux系统学习(二)
    linux系统学习(一)
    js模版渲染
    Discuz核心函数的解析
  • 原文地址:https://www.cnblogs.com/joyouth/p/5510156.html
Copyright © 2020-2023  润新知