2019-8-3 考试总结
A. 斐波那契
很水,但是没看出性质,
它的部分分可以拿到$70+$,所以就水到了$80$分。
很好找的一个规律,
每个节点的编号减去上一个$fibonacci$数就是它的父节点的编号。
所以每次上翻就可以了。
说起来很简单,细节很重要。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<set> #define int long long #define Maxn 300050 #define Reg register #define abs(x) ((x)<0?(-1*(x)):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)<(y)?(x):(y)) using namespace std; int m,num[Maxn],sup[Maxn],LCA; int getf(int x,int y) { if(x<y) swap(x,y); while(x>y) { int l1=lower_bound(num+1,num+59+1,x)-num; x=x-num[l1-1]; if(x<y) swap(x,y); } return x; } signed main() { scanf("%lld",&m); num[1]=1,num[2]=2; for(Reg int i=3;i<=59;++i) num[i]=num[i-1]+num[i-2]; for(Reg int i=1,x,y;i<=m;++i) { scanf("%lld%lld",&x,&y); LCA=getf(x,y); printf("%lld\n",LCA); } return 0; }
B.数颜色
题解的一句话说得好:高级数据结构学傻了。
什么莫队,什么主席树,线段树。。
只要用一个$vector$就可以了。
用$vector$存颜色的位置。
每次二分查找。
修改的时候因为只是$x$和$x+1$交换,对$vector$里的元素顺序没有改变。
所以用不着考虑排序了。
复杂度$O(nlogn)$。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<string> #include<cstdio> #include<vector> #define int long long #define Maxn 300050 #define Reg register using namespace std; int n,m,ans,A[Maxn]; vector<vector<int> > pos(Maxn); signed main() { scanf("%lld%lld",&n,&m); for(Reg int i=1;i<=n;++i) { scanf("%lld",&A[i]); pos[A[i]].push_back(i); } for(Reg int i=1,l,r,opt,c;i<=m;++i) { scanf("%lld",&opt); if(opt==1) { scanf("%lld%lld%lld",&l,&r,&c); int p1=upper_bound(pos[c].begin(),pos[c].end(),r)-pos[c].begin()-1; int p2=lower_bound(pos[c].begin(),pos[c].end(),l)-pos[c].begin(); printf("%lld\n",p1-p2+1); } else { scanf("%lld",&c); if(c==n||A[c]==A[c+1]) continue; int p1=lower_bound(pos[A[c]].begin(),pos[A[c]].end(),c)-pos[A[c]].begin(); int p2=lower_bound(pos[A[c+1]].begin(),pos[A[c+1]].end(),c+1)-pos[A[c+1]].begin(); pos[A[c]][p1]=c+1,pos[A[c+1]][p2]=c; swap(A[c],A[c+1]); } } return 0; }
C. 分组
考试时打了一个$dp$,水到$32$分,
正解:
当$K=1$时,
要找能够拓展的最长长度。
因为要记录前趋,所以可以从后向前枚举。
每次判断能不能拓展到它,如果可以,就拓展。
否则要把这个点设为断点。
但是,显然这个是$O(n^2)$的。
因为题目性质,能产生矛盾的都是平方数,所以可以枚举平方因子$x$,
判断每个$x^2-a[i]$是不是在当前的队列中,如果存在,那么肯定不能拓展。
复杂度降为$O(n\sqrt n)$。
当$K=2$时,
首先,如果$n=2$,那么直接输出$1$和$2$个回车,$4$分拿到。
之后就。。
二分图。
将能产生矛盾的兔子建边,判断整张图是不是二分图,如果是就可以扩展,否则为断点。
然后卡一卡就$O(n^2)$过了。
但是真正的正解要用并查集。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<string> #include<cstdio> #include<vector> #include<cmath> #define int long long #define Maxn 150000 #define Reg register #define max(x,y) ((x)>(y)?(x):(y)) using namespace std; int n,K,tot,root,top,maxx,A[Maxn],vis[Maxn*2],stack[Maxn],fir[Maxn],col[Maxn],sta[Maxn]; struct Tu {int st,ed,next;} lian[Maxn*2]; vector<vector<int> >son(Maxn*2); void add(int x,int y) { if(!fir[x]) sta[++sta[0]]=x; lian[++top].st=x; lian[top].ed=y; lian[top].next=fir[x]; fir[x]=top; return; } bool dfs(int x,int fa,int co) { if(x>stack[tot]) return 1; col[x]=co; for(Reg int i=fir[x];i;i=lian[i].next) { if(lian[i].ed==fa||lian[i].ed==x) continue; if(col[lian[i].ed]&&lian[i].ed<=stack[tot]) { if(col[lian[i].ed]==col[x]) return 0; else continue; } else if(!dfs(lian[i].ed,x,3-co)) return 0; } return 1; } bool judge(int x) { for(Reg int i=1;i<=sta[0];++i) col[sta[i]]=0; for(Reg int i=1;i<=sta[0];++i) { if(col[sta[i]]||!fir[sta[i]]) continue; if(!dfs(sta[i],sta[i],1)) return 0; } return 1; } signed main() { // freopen("division21.in","r",stdin); scanf("%lld%lld",&n,&K); for(Reg int i=1;i<=n;++i) { scanf("%lld",&A[i]); maxx=max(maxx,A[i]); } if(K==1) { maxx=sqrt(maxx<<1)+1; stack[++tot]=n+1; for(Reg int i=n;i>=1;--i) { int ok=1; for(Reg int j=maxx;j*j>=A[i];--j) if(vis[j*j-A[i]]) {ok=0; break;} if(!ok) { for(Reg int j=stack[tot];j>=i;--j) vis[A[j]]=0; stack[++tot]=i; } vis[A[i]]=1; } printf("%lld\n",tot); for(Reg int i=tot;i>=1;--i) if(stack[i]<n) printf("%lld ",stack[i]); } else { if(n==2) printf("1\n\n"); else { stack[++tot]=n; son[A[n]].push_back(n); maxx=sqrt(maxx<<1)+1; for(Reg int i=n-1;i>=1;--i) { int ok=1; for(Reg int j=maxx;j*j>=A[i];--j) { if(son[j*j-A[i]].size()==0) continue; else for(Reg int k=0;k<son[j*j-A[i]].size();++k) add(i,son[j*j-A[i]][k]),add(son[j*j-A[i]][k],i); } if(!judge(i)) { for(Reg int j=1;j<=sta[0];++j) fir[sta[j]]=0; for(Reg int j=stack[tot];j>=i;--j) son[A[j]].clear(); sta[0]=0; top=0; stack[++tot]=i; } son[A[i]].push_back(i); } int ans=0; printf("%lld\n",tot); for(Reg int i=tot;i>=1;--i) if(stack[i]<n) printf("%lld ",stack[i]); } } return 0; }
总结:
一开始看$T1$,感觉没什么思路,没找规律,就跳过了。
然后开始看$T2$,一看到题目绝对是个数据结构题。
想打动态开点线段树,看到数据范围$3\times 10^5$,然后就咕咕咕。
这套题测试点写的很清楚。
于是,测试点分治很好用。
前30分暴力树状数组,后面根据特殊性质再用别的。
于是水到$55$分。
之后回来看$T1$,打了一个$dfs$,开始找规律。
最后其实还是水到的$80$分。
$T3$有一些送分点,庆幸都拿到了。。
要不又掉下去了。
所以最后$80+55+32=167$。
没什么水平。。。