模拟12题解
T1[A. 斐波那契(fibonacci)]
考场上发现了一个性质:一个节点的父节点=该节点编号-上一个fibonacci数
所以求父亲节点代码
int fa(int x) { for(int i=mx;i;i--) if(fi[i]<x) return x-fi[i]; }
然后我只想到了建树的70%代码,数组越界了又少了30分QAQ
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<algorithm>
5 #include<queue>
6 #define int long long
7 using namespace std;
8 inline int read()
9 {
10 int f=1,x=0;char ch=getchar();
11 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
12 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
13 return f*x;
14 }
15 const int mx=65;
16 const int maxn=1000000;
17 int fi[mx+10];
18 int d[maxn+5];
19 int f[maxn+5][25];
20 struct node{
21 int v,nxt;
22 }e[maxn];int h[maxn],nu;
23 void add(int x,int y)
24 {
25 e[++nu].v=y;
26 e[nu].nxt=h[x];
27 h[x]=nu;
28 }
29 int gfa(int x)
30 {
31 for(int i=mx;i;i--)
32 if(fi[i]<x)
33 return x-fi[i];
34 }
35 void bfs()
36 {
37 queue<int>q;
38 d[1]=1;
39 q.push(1);
40 while(q.size())
41 {
42 int x=q.front();q.pop();
43 for(int i=h[x];i;i=e[i].nxt)
44 {
45 int y=e[i].v;
46 if(d[y])continue;
47 d[y]=d[x]+1;
48 f[y][0]=x;
49 for(int j=1;j<=21;j++)
50 f[y][j]=f[f[y][j-1]][j-1];
51 q.push(y);
52 }
53 }
54 }
55 int lca(int x,int y)
56 {
57 if(d[x]>d[y])swap(x,y);
58 for(int i=21;i>=0;i--)
59 if(d[f[y][i]]>=d[x])
60 y=f[y][i];
61 if(y==x)return x;
62 for(int i=21;i>=0;i--)
63 if(f[x][i]!=f[y][i])
64 x=f[x][i],y=f[y][i];
65 return f[x][0];
66 }
67 signed main()
68 {
69 //freopen("fibonacci2.in","r",stdin);
70 int m=read(),mxx;
71 fi[0]=fi[1]=1;
72 for(int i=2;i<=mx;i++)
73 fi[i]=fi[i-1]+fi[i-2];
74 for(int i=2;i<=maxn;i++)
75 {
76 int fa=gfa(i);
77 add(fa,i);
78 }
79 bfs();
80 for(int i=1;i<=m;i++)
81 {
82 int x=read(),y=read();
83 printf("%lld\n",lca(x,y));
84 }
85 }
86 /*
87 g++ 1.cpp -o 1
88 ./1
89
90 */
可是考完才发现暴力既好写,又能A
对于输入的两个节点x,y将其中一个向上遍历,并把经过的路径都存入一个vector
然后另一个向上翻,如果在vector中能找到,那此时的y就是答案
时间复杂度$O(60mlog(n))$
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<algorithm>
5 #include<queue>
6 #include<vector>
7 #define int long long
8 using namespace std;
9 inline int read()
10 {
11 int f=1,x=0;char ch=getchar();
12 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
13 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
14 return f*x;
15 }
16 const int mx=65;
17 int fi[mx+10];
18 int fa(int x)
19 {
20 for(int i=mx;i;i--)
21 if(fi[i]<x)
22 return x-fi[i];
23 }
24 signed main()
25 {
26 //freopen("data","r",stdin);
27 int m=read();
28 fi[0]=fi[1]=1;
29 for(int i=2;i<=mx;i++)
30 fi[i]=fi[i-1]+fi[i-2];
31 vector<int>v;
32 for(int i=1;i<=m;i++)
33 {
34 int x=read(),y=read();
35 v.clear();
36 while(x>1)
37 {
38 v.push_back(x);
39 x=fa(x);
40 }
41 while(y>1)
42 {
43 vector<int>::iterator res=find(v.begin(),v.end(),y);
44 if(res!=v.end()) break;
45 y=fa(y);
46 }
47 printf("%lld\n",y);
48 }
49 }
50 /*
51 g++ 1.cpp -o 1
52 ./1
53
54 */
vector中查找元素:
vector<int>::iterator res=find(v.begin(),v.end(),y);//在v中查找y if(res!=v.end()) //找到了
T2[B. 数颜色]「排序」「线段树」「分块」「莫队」
考场上只想到了$O(nm)$的纯暴力,
然而以上的这些都能拿到不少的部分分,更有主席树,树套树的高超方法,我却什么都搞不出来
65%+算法:动态开点线段树
考试时想用线段树,当时的思维是这样的:死板地维护区间1-n,然后查询。。。打到一半,诶,查询什么?线段树里开3e5个颜色的个数,不可能的。。。
然后就傻了,想想其他的数据结构我也没法维护。。
殊不知,,我"忘"了一个叫权值线段树的东西,动态开点,每棵树维护每种color的在原区间的个数,时空复杂度$O(nlog(n))$
使用动动的代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 int a[300001]; 5 int rt[300001],trsum[10000000],son[10000000][2],tot; 6 void change(int &x,int l,int r,int to,int d) 7 { 8 if(!x)x=++tot; 9 trsum[x]+=d; 10 if(l==r)return; 11 int mid=(l+r)>>1; 12 if(to<=mid)change(son[x][0],l,mid,to,d); 13 else change(son[x][1],mid+1,r,to,d); 14 } 15 int ask(int x,int l,int r,int L,int R) 16 { 17 if(!x)return 0; 18 if(l==L&&r==R)return trsum[x]; 19 int mid=(l+r)>>1; 20 if(L>mid)return ask(son[x][1],mid+1,r,L,R); 21 else if(R<=mid)return ask(son[x][0],l,mid,L,R); 22 else return ask(son[x][0],l,mid,L,mid)+ask(son[x][1],mid+1,r,mid+1,R); 23 } 24 int main() 25 { 26 scanf("%d%d",&n,&m); 27 for(int i=1;i<=n;i++) 28 { 29 scanf("%d",&a[i]); 30 change(rt[a[i]],1,n,i,1); 31 } 32 while(m--) 33 { 34 int opt; 35 scanf("%d",&opt); 36 if(opt==1) 37 { 38 int l,r,c; 39 scanf("%d%d%d",&l,&r,&c); 40 printf("%d\n",ask(rt[c],1,n,l,r)); 41 } 42 else 43 { 44 int x; 45 scanf("%d",&x); 46 change(rt[a[x ]],1,n,x ,-1); 47 change(rt[a[x ]],1,n,x+1, 1); 48 change(rt[a[x+1]],1,n,x+1,-1); 49 change(rt[a[x+1]],1,n,x , 1); 50 a[x]^=a[x+1]^=a[x]^=a[x+1]; 51 } 52 } 53 return 0; 54 }
65%+算法:分块
我现在已经想不起来我当时怎么把分块给否了的,可能当时粗略一想空间不够,但是分完块就没几块了,复杂度$O(m\sqrt{n})$这么直接的算法我也想不到,想得严密一些不要轻易就放弃自己的想法
正解:
将(颜色,位置)按照二元组排序,对于查询操作,使用结构体lower_bound二分查找
对于修改操作,暴力修改结构体的pos,
注意若两个颜色不同,那么修改不会改变每个颜色块里的顺序
若相同,不用修改
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define mid ((l+r)>>1) 6 using namespace std; 7 const int maxn=3e5+5; 8 inline int read() 9 { 10 int f=1,x=0;char ch=getchar(); 11 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} 13 return f*x; 14 } 15 int n,sum[maxn],jl[maxn]; 16 struct node{ 17 int pos,da; 18 }a[maxn]; 19 bool cmp(node x,node y){return (x.da==y.da)?x.pos<y.pos:x.da<y.da;} 20 int main() 21 { 22 // freopen("data","r",stdin); 23 n=read();int Q=read(),mc=0; 24 for(int i=1;i<=n;i++) 25 { 26 a[i].da=read(),a[i].pos=i; 27 sum[a[i].da]++; 28 mc=max(mc,a[i].da); 29 jl[i]=a[i].da; 30 } 31 for(int i=1;i<=mc;i++) 32 sum[i]+=sum[i-1]; 33 sort(a+1,a+n+1,cmp); 34 while(Q--) 35 { 36 int opt=read(); 37 if(opt==1) 38 { 39 int L=read(),R=read(),col=read(); 40 node l,r; 41 l.da=col,l.pos=L; 42 r.da=col,r.pos=R; 43 int ll=lower_bound(a+sum[col-1]+1,a+sum[col]+1,l,cmp)-a; 44 int rr=upper_bound(a+sum[col-1]+1,a+sum[col]+1,r,cmp)-a; 45 printf("%d\n",rr-ll); 46 } 47 if(opt==2) 48 { 49 int i=read(); 50 int x=i,y=i+1; 51 if(jl[x]==jl[y])continue; 52 node xx,yy; 53 xx.pos=x,xx.da=jl[x];int c1=jl[x]; 54 yy.pos=y,yy.da=jl[y];int c2=jl[y]; 55 int xp=lower_bound(a+sum[c1-1]+1,a+sum[c1]+1,xx,cmp)-a; 56 int yp=lower_bound(a+sum[c2-1]+1,a+sum[c2]+1,yy,cmp)-a; 57 a[xp].pos=y,a[yp].pos=x; 58 jl[x]=yy.da,jl[y]=xx.da; 59 } 60 } 61 } 62 /* 63 g++ 2.cpp -o 2 64 ./2 65 66 */
结构体lower_bound:
bool cmp(node x,node y){return (x.da==y.da)?x.pos<y.pos:x.da<y.da;}
int ll=lower_bound(a+1,a+n+1,l,cmp)-a;//a中查找l返回下标ll
T3[C. 分组]「二分图」
性质:若要保证字典序最小,则从后往前枚举,让每一段区间尽量长,那么可以保证字典序最小
k==1时,从后往前枚举,看枚举到的点与已有的点有无矛盾,若有则该点为断点
k==2时,同样的枚举方式对于枚举到的点的判断方式:
每段区间就是一个二分图,
1:若三者互相矛盾 ,那么当枚举到第三个时,发现第三个与其中一个放不到两边,设置为断点
2:若有两个相同的颜色,第三个不同颜色,枚举到的第三个是不同颜色时,或是两个相同颜色中的一个,都要干掉
实现见代码注释
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 #include<cmath> 7 using namespace std; 8 inline int read() 9 { 10 int f=1,x=0;char ch=getchar(); 11 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} 13 return f*x; 14 } 15 const int maxn=140000; 16 int mx,n,k,a[maxn+100],issqr[2*maxn+100],cnt,ans[maxn+100],v[maxn+100],fa[2*maxn+100]; 17 int find(int x) 18 { 19 if(fa[x]!=x)fa[x]=find(fa[x]); 20 return fa[x]; 21 } 22 int jud(int x,int y)//x y have had conflict &&判断是否能在两边 23 { 24 int x1=find(x),x2=find(x+maxn);//x1 x的敌人代表元素 x2 朋友 25 int y1=find(y),y2=find(y+maxn); 26 if(x1==y1||x2==y2)return 0;//have same friend or emery 27 fa[x1]=y2,fa[x2]=y1;// divide into two different jihe 28 return 1; 29 } 30 void solve1() 31 { 32 for(int i=1;i<=n;i++)mx=max(mx,a[i]); 33 ans[cnt]=n; 34 for(int i=n;i>=1;i--) 35 { 36 for(int j=sqrt(a[i]);j*j<=mx+a[i];j++) 37 { 38 if(j*j-a[i]>0&&v[j*j-a[i]]) 39 { 40 for(int k=i+1;k<=ans[cnt];k++)v[a[k]]=0; 41 ans[++cnt]=i; 42 break; 43 } 44 } 45 v[a[i]]=1; 46 } 47 } 48 int vis[maxn],svis[maxn]; 49 void solve2() 50 { 51 for(int i=1;i<=2*maxn;i++)fa[i]=i; 52 for(int i=1;i*i<=2*maxn;i++) issqr[i*i]=1; 53 for(int i=n,j=n;i>=1;) 54 { 55 for(;j>=1;j--) 56 { 57 if(vis[a[j]])//如果这个点出现过一次 58 { 59 if(!issqr[2*a[j]])continue;//并且这个点与自己同颜色的矛盾 60 if(svis[a[j]])goto fail;//如果这个点已经出现了两次,两个可以分到二分图的两边,所以不可能就再有了,成为断点 61 for(int k=1;k*k<=mx+a[j];k++)//所以这是第二个点 62 if(k*k-a[j]>0&&vis[k*k-a[j]]&&k*k!=2*a[j])goto fail;//找到了另一个与该颜色矛盾,跳过成为断点 63 svis[a[j]]=1;//这种颜色出现了两次,并且在二分图两边(1) 64 } 65 else //没有出现过 66 { 67 for(int k=1;k*k<=mx+a[j];k++) 68 if(k*k-a[j]>0&&vis[k*k-a[j]])//找到了与它矛盾的点 69 if(svis[k*k-a[j]]||jud(a[j],k*k-a[j])==0)//如果这个点已经在二分图的两边(1情况) 或 这两个点不能放在两边 70 {fa[a[j]]=a[j];fa[a[j]+maxn]=a[j]+maxn;goto fail;}//清空这个断点的并查集信息 71 vis[a[j]]=1; 72 } 73 } 74 fail: 75 if(j==0)break; 76 ans[++cnt]=j; 77 for(;i>j;i--)fa[a[i]]=a[i],fa[a[i]+maxn]=a[i]+maxn,vis[a[i]]=svis[a[i]]=0; 78 } 79 } 80 int main() 81 { 82 n=read(),k=read(); 83 for(int i=1;i<=n;i++) 84 a[i]=read(),mx=max(mx,a[i]); 85 if(k==1)solve1(); 86 else solve2(); 87 printf("%d\n",cnt+1); 88 for(int i=cnt;i>=1;i--) printf("%d ",ans[i]); 89 puts(""); 90 } 91 /* 92 g++ 1.cpp -o 1 93 ./1 94 95 */