5.19 考试修改+总结
2016-05-19 21:09
441 查看
作为一个也是出过几道水题的人,强烈谴责第三题这种不给数据范围的题目
而且给出的一些数的数据范围还和数据不符,表示非常不兹磁
先放题解吧
首先第一题开场5min推出来,10min写完,之后5min就拍上了
没什么好说的,首先一个很重要的结论是各位数字的乘积的可能情况并不多
大概有3w多个吧,之后我们枚举各位数字的乘积就可以确定这样的数字的取值范围
之后就转换成了SCOI Blinker的仰慕者了,而且比这道题目少一种情况(k=0)
记忆化搜索写数位DP就可以了
状态是f[i][j]表示i位数字乘积为j的方案数(j被哈希掉了)
第二题一看还以为是BZOJ上的排队,没看题目的时候觉得不会真是要写树套树吧
看完题目发现是个线段树的丝薄题
如果一段区间长度为B-A+1,且最小值为A,最大值为B,那么就存在
否则不存在
我直接按照这个思路写的,时间复杂度O(nlogn)
后来发现考试的时候自己犯傻,其实只需要维护一个位置的线段树就可以了,每次查询位置的最大值最小值作差判断是否=len就可以了
下午为了秀技术,强行写了一发fhq_treap,也A了
上午写完上面两道题+拍上大概到了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就可以啦
本题其实还有第二种解法,加一些奇技淫巧就可以过了
但是本人并不会O(1)快速乘的写法
所以还是会T的代码,时间复杂度O(nlog^2n)
但是思维量很小,就是我们考虑每一次的转移
j能转移到k当且仅当j^(1<<i)=k,且转移系数为1
我们构造FWT就可以处理一次转移了
之后怎么办呢?FWT+快速幂?
其实并不需要,我们只需要FWT之后对FWT出来的值做快速幂就可以了
因为快速幂的时候需要快速乘,所以时间复杂度O(nlog^2n)了
之后还原FWT即可
表示自己的知识点部分还是有些欠缺的
上午成功的将系数弄了出来,有很大的进步
但是看出是FWT不会写也是令人伤心
所以还是要努力啊,做做FWT的题目之后写一发总结吧
(挖坑ing)
加油!八个月!三月份!
而且给出的一些数的数据范围还和数据不符,表示非常不兹磁
先放题解吧
首先第一题开场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)
加油!八个月!三月份!
相关文章推荐
- EACCES (permission denied)解决办法 android 文件读写
- 2016春季练习——DP
- C 高级编程 2 内存管理
- Markdown:认识&入门
- PHP 代码跟踪
- MySQL数据库入门--软件下载与安装、开启服务、停止服务
- 【C语言】C语言储存类型关键字详细解析
- HTTP响应头和请求头信息对照表
- -/bin/sh: 命令:not found的解决办法
- UVA10112 Myacm Triangles
- undefined reference to `png_create_write_struct'问题解决
- MYSQL 函数大全
- Android开发AsyncTask源码分析【模板方法模式】
- 每天一个linux命令:ps命令
- base64加密原理代码实现
- 分支语句
- cf 185A(矩阵快速幂)
- UVa 10387 Billiard
- 链表反转
- 第12周项目1-实现复数类中的运算符重载