6.13 考试总结
因为今天并没有修改,所以只能总结了
又莫名其妙rank1了,貌似是因为我第三题的暴力写的吼?
第一题
貌似是BZOJ原题,zcg他们都做过
但是我没有做过,想了想发现是丝薄的二元关系最小割
按照pty论文里的建图方法弄了一下,算了算K值之后发现要求这个图必须是二分图才可以
然后思考了一下,随机了半个小时的数据跑发现都是二分图(这是OI的证明方法
(我猜这个题目的某种特别的性质决定了这是二分图,没错,就是这样)
于是就放心的写了个二分图染色建图跑最小割QAQ A掉了QAQ
至于二分图的数学证明,是这样的:
1、(a,b)=1,所以a,b不都是偶数
2、考虑a,b都是奇数,不妨设a=2*x-1,b=2*y-1
则(2*x-1)^2+(2*y-1)^2=2*(2*x^2-2*x+2*y^2-2*y+1)
不难发现后面的式子2的指数为1,所以不可能是完全平方数
由于偶数之间不会建边,奇数之间不会建边,所以原图是二分图
不用二分图染色直接分奇偶就可以了
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> #include<queue> using namespace std; typedef long long LL; const int maxn=1010; const int oo=0x7fffffff; int n,ans,S,T; int a[maxn],b[maxn]; int h[maxn],cnt=1; int cur[maxn],col[maxn],vis[maxn]; bool check[maxn][maxn]; struct edge{ int to,next,w; }G[5000010]; queue<int>Q; int gcd(int a,int b){return b==0?a:gcd(b,a%b);} void add(int x,int y,int z=0){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } 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(); } bool judge(int a,int b){ int c=gcd(a,b); if(c!=1)return false; LL now=1LL*a*a+1LL*b*b; int k=(int)(sqrt(now)); if(1LL*k*k!=now)return false; return true; } void Get_check(){ for(int i=1;i<=n;++i){ for(int j=i+1;j<=n;++j){ if(judge(a[i],a[j])){ check[i][j]=true; } } }return; } void paint(int u){ for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(col[v])continue; col[v]=3-col[u]; paint(v); }return; } void Get_paint(){ for(int i=1;i<=n;++i){ for(int j=i+1;j<=n;++j){ if(check[i][j])add(i,j),add(j,i); } } for(int i=1;i<=n;++i){ if(!col[i])col[i]=1,paint(i); } } void build_Graph(){ memset(h,0,sizeof(h));cnt=1; S=0;T=n+1; for(int i=1;i<=n;++i){ for(int j=i+1;j<=n;++j){ if(check[i][j]){ int u=i,v=j; if(col[u]!=1)swap(u,v); add(u,v,oo);add(v,u,0); } } } for(int i=1;i<=n;++i){ if(col[i]==1)add(S,i,b[i]),add(i,S,0); else add(i,T,b[i]),add(T,i,0); }return; } bool BFS(){ Q.push(S); for(int i=S;i<=T;++i)vis[i]=-1; vis[S]=0; while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(vis[v]==-1&&G[i].w>0){ vis[v]=vis[u]+1; Q.push(v); } } }return vis[T]!=-1; } int DFS(int x,int f){ if(x==T||f==0)return f; int w,used=0; for(int i=cur[x];i;i=G[i].next){ if(vis[G[i].to]==vis[x]+1){ w=f-used; w=DFS(G[i].to,min(w,G[i].w)); G[i].w-=w;G[i^1].w+=w; if(G[i].w>0)cur[x]=i; used+=w;if(used==f)return used; } } if(!used)vis[x]=-1; return used; } void dinic(){ while(BFS()){ for(int i=S;i<=T;++i)cur[i]=h[i]; ans-=DFS(S,oo); }return; } int main(){ freopen("number.in","r",stdin); freopen("number.out","w",stdout); read(n); for(int i=1;i<=n;++i)read(a[i]); for(int i=1;i<=n;++i)read(b[i]),ans+=b[i]; Get_check();Get_paint(); build_Graph();dinic(); printf("%d\n",ans); return 0; }
第二题
第二题给了一堆莫名的集合交并的式子
由于这个题目的时间复杂度由读入的点数决定,不难想到虚树一类的东西
考虑如何用树来描述这样的一个关系
当A集合的大小均为1的时候不难发现给定的每个点就是他的父亲
集合的元素就是他到根的所有的点的代表元素
考虑A集合的大小>1,不难发现每个点的父亲是A集合所有点在树上的LCA(即所有点的交
建出树之后我们考虑如何求一些集合的交,考试的时候思考了很久要不要写虚树
开始写之后发现这是求链交的裸模型,然后由于每条链的一个端点是根,所以我们直接将读入的点按DFS序排序
这样答案就是所有点的深度之和减去所有相邻两个点的LCA的深度和(实际上这差不多也就是个虚树的过程
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; const int maxn=200010; int n,k,x,N,m; int anc[maxn][20]; int dep[maxn]; int h[maxn],cnt=0; struct edge{ int to,next; }G[maxn<<1]; int st[maxn],ed[maxn],tot=0; int S[maxn]; bool cmp(const int &a,const int &b){ return st[a]<st[b]; } void add(int x,int y){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt; } 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 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=anc[p][i]; } if(p==q)return p; for(int i=log;i>=0;--i){ if(anc[p][i]!=-1&&anc[q][i]!=anc[p][i]){ p=anc[p][i];q=anc[q][i]; } }return anc[p][0]; } void merge(int u,int f){ anc[u][0]=f;dep[u]=dep[f]+1; add(f,u); for(int i=1;(1<<i)<=N;++i)anc[u][i]=-1; for(int i=1;(1<<i)<=N;++i){ if(anc[u][i-1]!=-1){ int a=anc[u][i-1]; anc[u][i]=anc[a][i-1]; }else break; }return; } void Get_DFS(int u){ st[u]=++tot; for(int i=h[u];i;i=G[i].next)Get_DFS(G[i].to); ed[u]=tot; } int main(){ freopen("legend.in","r",stdin); freopen("legend.out","w",stdout); read(n);N=n+1; for(int i=0;(1<<i)<=N;++i)anc[0][i]=-1; for(int i=1;i<=n;++i){ read(k); if(!k){merge(i,0);continue;} read(x);int now=x; for(int j=2;j<=k;++j){read(x);now=LCA(now,x);} merge(i,now); }Get_DFS(0);read(m); while(m--){ read(k); for(int i=1;i<=k;++i)read(S[i]); sort(S+1,S+k+1,cmp); int ans=dep[S[1]]; for(int i=2;i<=k;++i){ ans+=dep[S[i]]; int now=LCA(S[i],S[i-1]); ans-=dep[now]; }printf("%d\n",ans); }return 0; }
第三题
代码貌似能7k+
感觉并不想写
就简单口胡一下题解吧
首先考虑只有第一个限制的情况,这是非常简单的
我们建出后缀树,之后做一遍DFS,启发式合并所有的后缀
当前节点是否合法当且仅当相邻的两个后缀的差值的最大值不能超过当前节点的长度
之后我们考虑第二个限制,我们考虑当前这个节点第一次出现的位置
不难发现我们要求的是这个第一次出现的位置在拼接一个后缀后是否是一个后缀
这个我想的做法是在反着建一遍后缀自动机,在启发式合并的时候顺便维护一下
问题就转化成了 简单的最近公共祖先 了QAQ 好开心
没错,如果一个题你要建两个SAM,还要写Spaly启发式合并,然后还要写树链剖分的话
我觉得应该没有什么必要去写了QAQ