模拟12 题解
A. 斐波那契(fibonacci)
首先想到a,b<=1e6的暴力:建树,直接向上标记求lca。
建树的过程中发现一个性质。
斐波那契第n代兔子,是n-2代及以前的兔子的儿子。
因为编号连续且与父亲编号大小有关,
设该节点的编号为$x$,在第$k$代,
则$f(x)=x-fib(k-1)$。
二分查找父亲,向上标记求lca即可。
(特判不换行,爆零两行泪)
(见代码第25行)
1 #include<iostream> 2 #include<cstdio> 3 #define ll long long 4 using namespace std; 5 ll fib[70],a,b; 6 int m; 7 inline ll read(){ 8 register ll x=0; char ch=getchar(); 9 while(!isdigit(ch)) ch=getchar(); 10 while(isdigit(ch)){ 11 x=(x<<1)+(x<<3)+(ch^48); 12 ch=getchar(); 13 } 14 return x; 15 } 16 int main(){ 17 fib[1]=fib[0]=1; 18 for(int i=2;i<=65;++i) fib[i]=fib[i-1]+fib[i-2]; 19 m=read(); 20 while(m--){ 21 a=read(); b=read(); 22 if(b==a+1||a==b+1){ 23 printf("1"); 24 continue; 25 } 26 int x=lower_bound(fib,fib+63,a)-fib,y=lower_bound(fib,fib+63,b)-fib; 27 if(fib[x]==a&&fib[y]==b){ 28 if((x-y)&1) printf("%d\n",1); 29 else printf("%lld\n",min(a,b)); 30 continue; 31 } 32 while(a!=b){ 33 if(b<a) a-=fib[lower_bound(fib,fib+63,a)-fib-1]; 34 else b-=fib[lower_bound(fib,fib+63,b)-fib-1]; 35 } 36 printf("%lld\n",a); 37 } 38 return 0; 39 }
B. 数颜色
刚开始把题看成了数区间内不同颜色个数,于是一直在想怎么维护pre,怎么做到$O(nlogn)$。
然后突然发现题目不一样,好像更简单了。
然后就忘了考虑vector这些东西。
打了个树状数组套主席树,
然而出题人毒瘤,查询可能比最大的权值更大。
70分死成了20分。
正解是vector,交换的操作直接二分后swap即可。
主席树也是可行的,交换邻项之后,后一项前缀和不变,对前一项的前缀和修改即可。
复杂度都是$O(nlogn)$。
1 #include<iostream> 2 #include<vector> 3 #include<cstdio> 4 using namespace std; 5 const int N=300010; 6 int a[N],n,m; 7 vector<int> ve[N]; 8 inline int read() 9 { 10 register int x=0; char ch=getchar(); 11 while(ch<'0'||ch>'9') ch=getchar(); 12 while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 13 return x; 14 } 15 int main(){ 16 n=read(); m=read(); 17 for(register int i=1;i<=n;++i) a[i]=read(),ve[a[i]].push_back(i); 18 for(register int i=1,opt,l,r,x;i<=m;++i){ 19 opt=read(); 20 if(opt==1){ 21 l=read(); r=read(); x=read(); 22 printf("%d\n",lower_bound(ve[x].begin(),ve[x].end(),r+1)-lower_bound(ve[x].begin(),ve[x].end(),l)); 23 } 24 else{ 25 x=read(); 26 if(a[x]==a[x+1]) continue; 27 swap(*lower_bound(ve[a[x]].begin(),ve[a[x]].end(),x),*lower_bound(ve[a[x+1]].begin(),ve[a[x+1]].end(),x+1)); 28 swap(a[x],a[x+1]); 29 } 30 } 31 return 0; 32 }
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=300010; 5 struct node{ 6 node *lch,*rch; 7 int val; 8 }*root[N],pool[N*70],*ptr; 9 int n,m,a[N],mx; 10 void insert(node register *&p,const int l,const int r,const int pos,const int val){ 11 if(p) *ptr=*p; 12 p=ptr++; 13 if(l==r){ 14 p->val+=val; 15 return ; 16 } 17 const int mid=l+r>>1; 18 if(pos<=mid) insert(p->lch,l,mid,pos,val); 19 else insert(p->rch,mid+1,r,pos,val); 20 } 21 inline int query(node *p,const int pos,int l=1,int r=mx){ 22 while(l!=r){ 23 if(!p) return 0; 24 int mid=l+r>>1; 25 if(pos<=mid) r=mid,p=p->lch; 26 else l=mid+1,p=p->rch; 27 } 28 return p?p->val:0; 29 } 30 const int L=1<<20|1; 31 char buffer[L],*S,*T; 32 #define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++) 33 inline int read() 34 { 35 register int x=0; char ch=getchar(); 36 while(ch<'0'||ch>'9') ch=getchar(); 37 while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 38 return x; 39 } 40 int main(){ 41 ptr=pool; n=read(); m=read(); 42 for(register int i=1;i<=n;++i) a[i]=read(),mx=max(mx,a[i]); 43 for(register int i=1;i<=n;++i) root[i]=root[i-1],insert(root[i],1,mx,a[i],1); 44 for(register int i=1,opt,l,r,x;i<=m;++i){ 45 opt=read(); 46 if(opt==1){ 47 l=read(); r=read(); x=read(); 48 if(x>mx){ 49 puts("0"); 50 continue; 51 } 52 printf("%d\n",query(root[r],x)-query(root[l-1],x)); 53 } 54 else{ 55 x=read(); 56 insert(root[x],1,mx,a[x],-1); 57 insert(root[x],1,mx,a[x+1],1); 58 swap(a[x],a[x+1]); 59 } 60 } 61 return 0; 62 }
C. 分组
从左向右考虑,设$dp(i)$表示由i点结束,分成最少的块。
显然有$dp(i)=dp(j)+1$ $if( [j+1 , i]满足条件)$
至于输出方案,转移时维护pre即可。
转移的两个维度:dp值的大小,当前块是否满足条件。
显然后者是有单调性的。
也就是说,对于确定的右端点r,存在一个极坐的端点l,
l以左均不满足条件,以右均满足条件。
所以维护好 l 这个指针就好了。
然而这个dp是对的吗?
题目要求输出字典序最小方案。
最后一次的字典序最小,一定意味着全局最小吗?
其实已经在不知不觉中用了贪心。
从右往左不断选,选到不能选时停止,得到的一定是最优答案。
对于k=1,贪心或者dp都是可行的。
对于k=2的情况,最好使用贪心来解决。
对于k=1,
因为权值和不大于$512^2$,
将枚举i,j判断是否为平方数,
转化为维护一个桶表示每个数出现次数,
枚举平方数,减去i的权值,如果出现j就说明不合法。
因为只有一个分组,不能存在任何两个数的和为平方数。
对于k=2,
同理维护一个桶。
不妨将互相矛盾的点之间建边。
将大组分为两个小组,其实等价于图中不存在奇环。
也就是说,原图能够交叉染色,是一个二分图。
暴力染色当然不是可行的,一种可行的,维护二分图的方法是:拓展域并查集。
把每个数分为黑域和白域,当a,b加和为平方数,
如果$find(a_{white})=find(b_{white})$,即存在矛盾。
否则合并$a_{white}和b_{black},a_{black}和b_{white}$。
然而如果存在相同的数,特判将很恶心。
1 #include<iostream> 2 #include<cstdio> 3 #include<vector> 4 #include<cstring> 5 using namespace std; 6 const int N=132000; 7 int n,k,a[N],mx; 8 inline int read(){ 9 int x=0; char ch=getchar(); 10 while(!isdigit(ch)) ch=getchar(); 11 while(isdigit(ch)){ 12 x=(x<<1)+(x<<3)+(ch^48); 13 ch=getchar(); 14 } 15 return x; 16 } 17 namespace case1{ 18 int cnt[N*2],dp[N],pre[N],q[N],front,tail; 19 void dfs(int x){ 20 if(!x) return ; 21 dfs(pre[x]); 22 printf("%d ",x); 23 } 24 void ooo(){ 25 front=tail=1; q[1]=0; 26 for(int i=1;i<=n;++i){ 27 for(int j=2;j*j<=mx*2;++j){ 28 while(front<tail&&j*j>a[i]&&cnt[j*j-a[i]]){ 29 int l=q[front]+1,r=q[++front]; 30 for(int o=l;o<=r;++o) --cnt[a[o]]; 31 } 32 } 33 dp[i]=dp[q[front]]+1; pre[i]=q[front]; 34 while(front<=tail&&dp[i]<dp[q[tail]]) --tail; 35 q[++tail]=i; ++cnt[a[i]]; 36 } 37 printf("%d\n",dp[n]); 38 dfs(pre[n]); 39 puts(""); 40 } 41 } 42 namespace case2{ 43 int cnt[N*2],f[N*2],stack[N],top,ans; 44 bool ban[N]; 45 int find(int x){ 46 return f[x]==x?x:f[x]=find(f[x]); 47 } 48 void ooo(){ 49 for(int i=1;i<=mx*2;++i) f[i]=i; 50 for(int i=2;i*i<=mx*2;++i) if(i%2==0) ban[i*i/2]=1; 51 int l=n,r=n; 52 while(r){ 53 while(l){ 54 bool flag=1; 55 for(int i=2;i*i<=mx*2;++i) 56 { 57 if(i*i-a[l]!=a[l]) continue; 58 if(cnt[a[l]]==1) f[find(a[l])]=find(a[l]+mx); 59 if(cnt[a[l]]>=2) flag=0; 60 } 61 if(!flag) break; 62 for(int i=2;i*i<=mx*2;++i) 63 if(i*i-a[l]>0&&i*i-a[l]<=mx&&cnt[i*i-a[l]]&&i*i-a[l]!=a[l]){ 64 int x_w=a[l],y_w=i*i-a[l],x_b=a[l]+mx,y_b=i*i-a[l]+mx; 65 if(cnt[i*i-a[l]]>=2&&ban[i*i-a[l]]) flag=0; 66 if(find(x_w)==find(y_w)) flag=0; 67 else f[find(x_w)]=find(y_b),f[find(x_b)]=f[find(y_w)]; 68 } 69 if(!flag) break; 70 ++cnt[a[l--]]; 71 } 72 for(int i=l;i<=r;++i) cnt[a[i]]=0,f[a[i]]=a[i],f[a[i]+mx]=a[i]+mx; 73 r=l; ans++; if(r) stack[++top]=r; 74 } 75 printf("%d\n",ans); 76 while(top) printf("%d ",stack[top--]); 77 puts(""); 78 } 79 } 80 int main(){ 81 //freopen("x.in","r",stdin); 82 //freopen("me.out","w",stdout); 83 n=read(); k=read(); 84 for(int i=1;i<=n;++i) a[i]=read(),mx=max(a[i],mx); 85 if(k==1) case1::ooo(); 86 else case2::ooo(); 87 return 0; 88 }