5.11 考试 修改+总结
今天考试总体来说不是很理想
先放题解吧
第一题是学长们的NOIP模拟题的原题,但是学长当时没有给我题解
考试的时候我推出来多项式的积性了,反思自己没有写的原因:
1、对自己的推导能力不自信
2、觉得多项式除法需要FFT,不会写模拟
3、第三题有点思路,准备写第三题
但是在第三题写完后我没有去尝试写第一题是这次考试的第一个大失误
自己的模拟能力尚需提高
至于题解嘛,首先显然当n%m的时候,x^n-1一定能被x^m-1整除
进而可以证明这个结果是积性的,我们就可以利用dp+多项式除法来把每一项搞出来
最后sort一遍输出即可
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> using namespace std; const int maxn=1010; int n; struct FFT{ int a[maxn],n; FFT(){memset(a,0,sizeof(a));} void print(){ printf("("); if(a[n]==1)printf("x"); else if(a[n]==-1)printf("-x"); else printf("%dx",a[n]); if(n>1)printf("^%d",n); for(int i=n-1;i>=1;--i){ if(a[i]){ if(a[i]==1)printf("+x"); else if(a[i]==-1)printf("-x"); else if(a[i]<0)printf("%dx",a[i]); else printf("+%dx",a[i]); if(i>1)printf("^%d",i); } }if(a[0]<0)printf("%d)",a[0]); else printf("+%d)",a[0]); } }f[maxn],g[maxn]; bool cmp(const FFT &A,const FFT &B){ if(A.n!=B.n)return A.n<B.n; for(int i=A.n;i>=0;--i){ if(abs(A.a[i])!=abs(B.a[i]))return abs(A.a[i])<abs(B.a[i]); else if(A.a[i]!=B.a[i])return A.a[i]<B.a[i]; }return false; } FFT operator /(const FFT &A,const FFT &B){ FFT C=A,now; now.n=A.n-B.n; for(int i=A.n;i>=B.n;--i){ int k=now.a[i-B.n]=C.a[i]/B.a[B.n]; for(int j=0;j<=B.n;++j)C.a[i-B.n+j]-=k*B.a[j]; }return now; } int main(){ freopen("fac.in","r",stdin); freopen("fac.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;++i){ f[i].a[i]=1;f[i].a[0]=-1;f[i].n=i; for(int j=1;j<i;++j)if(i%j==0)f[i]=f[i]/f[j]; } int cnt=0; for(int i=1;i<=n;++i)if(n%i==0)g[++cnt]=f[i]; sort(g+1,g+cnt+1,cmp); for(int i=1;i<=cnt;++i)g[i].print(); return 0; }
第二题爆零了,主要是题目意思不太清晰
按照题解的理解当一个人开始工作时,他是不能等待或停止的
所以显然如果两个相邻的人开始工作的时候,他们的时间差是恒定的
又因为要满足条件,所以设b的前缀和为S
i和i-1的时间差为max(Sj+1*ai-1 - Sj*ai)
取max是因为要保证任意任务都不会被两个人同时做
那么显然这是一个点积的式子,我们先对(Sj+1,-Sj)做出上凸壳
之后问题就是询问点(ai-1,ai)和所有点的点积的最大值,在上凸壳上三分即可
提问:如果ai是负数怎么办?做一个上凸壳,做一个下凸壳,正数在上凸壳上三分,负数在下凸壳上三分
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=100010; int m,n,top; int b[maxn]; LL S[maxn],a[maxn]; struct Point{ LL x,y; Point(LL x=0,LL y=0):x(x),y(y){} }p[maxn],st[maxn],now; LL f[maxn]; typedef Point Vector; Vector operator -(const Point &A,const Point &B){return Vector(A.x-B.x,A.y-B.y);} LL Cross(const Vector &A,const Vector &B){return A.x*B.y-A.y*B.x;} LL Dot(const Point &A,const Point &B){return A.x*B.x+A.y*B.y;} bool cmp(const Point &A,const Point &B){ if(A.x==B.x)return A.y<B.y; return A.x<B.x; } LL Get_ans(){ LL L=1,R=top; while(R-L>=3){ int m1=(L+L+R)/3,m2=(L+R+R)/3; if(Dot(st[m1],now)>Dot(st[m2],now))R=m2; else L=m1; } LL ans=0; for(int i=L;i<=R;++i)ans=max(ans,Dot(st[i],now)); return ans; } int main(){ freopen("mission.in","r",stdin); freopen("mission.out","w",stdout); scanf("%d%d",&m,&n); for(int i=1;i<=m;++i)scanf("%d",&b[i]),S[i]=S[i-1]+b[i]; for(int i=1;i<=n;++i)scanf("%lld",&a[i]); for(int i=1;i<=m;++i)p[i].x=S[i],p[i].y=-S[i-1]; sort(p+1,p+m+1,cmp); for(int i=1;i<=m;++i){ while(top>1&&Cross(p[i]-st[top],st[top]-st[top-1])<=0)top--; st[++top]=p[i]; } f[1]=0; for(int i=2;i<=n;++i){ now.x=a[i-1];now.y=a[i]; f[i]=Get_ans(); } for(int i=2;i<=n;++i)f[i]+=f[i-1]; f[n]=f[n]+S[m]*a[n]; printf("%lld\n",f[n]); return 0; }
第三题,第三题的正解是SA+线段树合并
但是我的做法开O2过了,所以并不打算用题解的做法
不过线段树合并还是要学习一下的,先在这里挖个坑,晚上来填
我的做法是这样的,首先这棵树显然是个trie
然后把字符串都反转对答案无影响,任意两点的LCP等于在trie上的LCA
之后对trie建立后缀自动机,任意两点的最长公共后缀等于他们在parent树上对应节点的LCA
我们考虑暴力,每次枚举trie上的LCA,然后枚举子树中的点
问题转化为了在parent树上把某个节点染黑,询问某个节点和所有染黑节点的LCA中深度最大的那个
我们可以把parent树的DFS序搞出来,利用树状数组+倍增(即跳到第一个子树中有黑点的祖先)就可以搞定啦
但是这样暴力枚举LCA会被卡成O(n^2)的枚举时间复杂度
我们可以考虑对trie树做树分治来优化这一步,这样我们枚举的时间复杂度变为了O(nlogn)
注意树分治的时候可能会有一棵子树和其他子树的LCA不是当前中心,但是注意到最多只会有一棵这样的子树
且即使LCA不是当前中心,对于这个子树的点来说,和其他子树的所有点的LCA是一个定值,特判即可
树状数组+倍增是O(log^2n)
整体时间复杂度O(n*log^3n),但是三个log的常数都很小
即使在极限数据(其实就是本题的数据)开O2的情况下也能过
提问:为什么本题的数据是极限数据?因为这颗树是条链,同时后缀自动机的parent树也被卡成了链
所以两个log都满了QAQ
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #include<map> #include<vector> using namespace std; const int maxn=500010; int n,f,b,k,ans; int h[maxn],cnt=0; struct edge{ int to,next,w; }G[maxn]; int dep[maxn]; int lca[maxn][20]; void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } int p[maxn]; int pos[maxn],end[maxn],tot; int anc[maxn][20],D[maxn]; vector<int>V[maxn]; struct SAM{ map<int,int>next; int len,link; }st[maxn]; void init(){cnt=0;st[0].link=-1;} int add(int p,int c){ int cur=++cnt; st[cur].len=st[p].len+1; for(;p!=-1&&st[p].next[c]==0;p=st[p].link)st[p].next[c]=cur; if(p==-1)st[cur].link=0; else{ int q=st[p].next[c]; if(st[q].len==st[p].len+1)st[cur].link=q; else{ int clone=++cnt; st[clone]=st[q];st[clone].len=st[p].len+1; for(;p!=-1&&st[p].next[c]==q;p=st[p].link)st[p].next[c]=clone; st[cur].link=st[q].link=clone; } }return cur; } void build_SAM(int u,int f){ bool flag=true; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; flag=false; p[v]=add(p[u],G[i].w); build_SAM(v,u); } if(flag)k++,add(p[u],300+k); } void pre_LCA(){ for(int i=0;i<=cnt;++i){ anc[i][0]=st[i].link; for(int j=1;(1<<j)<=cnt;++j)anc[i][j]=-1; } for(int j=1;(1<<j)<=cnt;++j){ for(int i=1;i<=cnt;++i){ if(anc[i][j-1]!=-1){ int a=anc[i][j-1]; anc[i][j]=anc[a][j-1]; } } }return; } void Get_Graph(){for(int i=1;i<=cnt;++i)V[st[i].link].push_back(i);} void Get_DFS(int u){ pos[u]=++tot; for(int i=0;i<V[u].size();++i){ int v=V[u][i]; D[v]=D[u]+1; Get_DFS(v); }end[u]=tot; } void Get_dep(int u,int f){ lca[u][0]=f; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; dep[v]=dep[u]+1; Get_dep(v,u); }return; } void pre_lca(){ for(int i=1;i<=n;++i){ for(int j=1;(1<<j)<=n;++j)lca[i][j]=-1; } for(int j=1;(1<<j)<=n;++j){ for(int i=1;i<=n;++i){ if(lca[i][j-1]!=-1){ int a=lca[i][j-1]; lca[i][j]=lca[a][j-1]; } } }return; } int LCA(int p,int q){ if(dep[p]<dep[q])swap(p,q); int log; for(log=0;(1<<log)<=dep[p];++log);--log; for(int i=log;i>=0;--i){ if(dep[p]-(1<<i)>=dep[q])p=lca[p][i]; } if(p==q)return p; for(int i=log;i>=0;--i){ if(lca[p][i]!=-1&&lca[p][i]!=lca[q][i]){ p=lca[p][i];q=lca[q][i]; } }return lca[p][0]; } struct BIT{ int t,v; }t[maxn]; int S[maxn],top=0,tim=0; int lowbit(int x){return x&(-x);} void modify(int x,int v){ for(int i=x;i<=tot;i+=lowbit(i)){ if(t[i].t!=tim)t[i].t=tim,t[i].v=0; t[i].v+=v; }return; } int ask(int x){ int sum=0; for(int i=x;i>=1;i-=lowbit(i)){ if(t[i].t==tim)sum+=t[i].v; }return sum; } int ff[maxn],w[maxn],g,sum; bool vis[maxn]; void Get_node(int u,int f){ S[++top]=u; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(vis[v])continue; if(v==f)continue; Get_node(v,u); }return; } bool check(int u){return ask(end[u])-ask(pos[u]-1)>0;} int Get_ask(int u,int k){ if(check(u))return dep[k]; int log; for(log=0;(1<<log)<=D[u];++log);--log; for(int i=log;i>=0;--i){ if(anc[u][i]!=-1&&!check(anc[u][i]))u=anc[u][i]; } if(anc[u][0]==-1)return 0; return st[anc[u][0]].len; } int cmax(int &a,int b){if(b>a)a=b;} void Get_G(int u,int f){ w[u]=1;ff[u]=0; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(vis[v])continue; if(v==f)continue; Get_G(v,u); w[u]+=w[v];cmax(ff[u],w[v]); }cmax(ff[u],sum-w[u]); if(ff[u]<ff[g])g=u; } void Get_ans(int u){ tim++;modify(pos[p[u]],1); int k=0;vis[u]=true; for(int i=h[u];i;i=G[i].next){ int v=G[i].to;top=0; if(vis[v])continue; if(dep[v]<dep[u]){k=v;continue;} Get_node(v,-1); for(int i=1;i<=top;++i)ans=max(ans,dep[u]+Get_ask(p[S[i]],S[i])); for(int i=1;i<=top;++i)modify(pos[p[S[i]]],1); } if(k){ top=0;Get_node(k,-1); for(int i=1;i<=top;++i)ans=max(ans,dep[LCA(u,S[i])]+Get_ask(p[S[i]],S[i])); } for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(vis[v])continue; g=0;sum=w[v];Get_G(v,-1); Get_ans(g); }return; } void print_SAM(){ for(int i=1;i<=n;++i)printf("%d %d\n",i,pos[i]); printf("\n"); for(int i=0;i<=cnt;++i)printf("%d %d\n",i,st[i].link); printf("\n"); for(int i=0;i<=cnt;++i)printf("%d %d\n",i,st[i].len); printf("\n"); for(int i=0;i<=cnt;++i){ for(map<int,int>::iterator it=st[i].next.begin();it!=st[i].next.end();++it){ printf("%d %d %d\n",i,it->second,it->first); } }printf("\n"); } int main(){ freopen("recall.in","r",stdin); freopen("recall.out","w",stdout); int __size__=32<<20; char *__p__=(char*)malloc(__size__)+__size__; __asm__("movl %0, %%esp\n"::"r"(__p__)); scanf("%d",&n); for(int i=2;i<=n;++i){ scanf("%d%d",&f,&b); add(f,i,b);add(i,f,b); }init();build_SAM(1,-1); //print_SAM(); pre_LCA();Get_Graph();Get_DFS(0); Get_dep(1,-1);pre_lca(); ff[0]=0x7fffffff;g=0;sum=n; Get_G(1,-1);Get_ans(g); printf("%d\n",ans); return 0; }
UPD:由于考试时本人犯傻,用倍增+树状数组来解决这个问题,实际上可以用树链剖分在一个log的时间内解决
总时间复杂度nlog^2n,亲测可以再不开O2的情况下通过所有数据
至于树链剖分的做法见我在cojs上出的题目
考试总结:
1、第一题有思路但是不敢去尝试是本场最大的失误,而且很多人都有35分,本人只有30分
2、第二题看错题不应该,因为按照我的理解输出会跟答案差1,而我以为从1时刻开始就加了个1,
没有认真分析样例(即使题目说的不很清楚,而且没有给样例解释)
实际上看对题之后这就是裸的SDOI 2013 保护出题人QAQ
3、第三题想到了线段树合并,但是并不会,属于知识问题
但是考场的发挥非常好,想到了用树状数组代替线段树,同时用树分治+倍增替代了合并过程
代码很长,有200+行,考试的时候一口气35min码完,又花了15min补上树分治,没有出现什么错误
可见我的码力较之前有所提高,以后还要继续在cojs出毒瘤题锻炼码力