第三题:n个数字,有m个限制,每个限制给出某两个数字的Gcd或者Lcm为多少。最后问这样的n个数存在否。
思路:我们发现,对于素数p1,p2,n个数中每个数含有多少个p1,p2是没有联系的,因此每个素数可以分开考虑。
现在我们计算对于某一个素数p是否存在矛盾,用Min[i],Max[i]计算n个数字中每个数字最少最多含有多少个p。然后第x个数字含有的p要么取Min[x],要么取 Max[x],因此可用2sat解决。
const int N=1005; #define VI vector<int> #define YES "Solution exists" #define NO "Solution does not exist" vector<int> prime; int n,m; vector<int> a,b,c; string type; int low[N],dfn[N],visit[N],belong[N],id,color; stack<int> S; struct node { int v,next; }; node edges[N<<1]; int head[N],e; void add(int u,int v) { edges[e].v=v; edges[e].next=head[u]; head[u]=e++; } int Min[N],Max[N]; void upMin(int &x,int y) { if(x>y) x=y; } void upMax(int &x,int y) { if(x<y) x=y; } void DFS(int u) { dfn[u]=low[u]=++id; S.push(u); int i; for(i=head[u];i!=-1;i=edges[i].next) { int v=edges[i].v; if(!dfn[v]) { DFS(v); upMin(low[u],low[v]); } else if(!visit[v]) { upMin(low[u],dfn[v]); } } if(low[u]==dfn[u]) { color++; int v; do { v=S.top(); S.pop(); visit[v]=1; belong[v]=color; }while(v!=u); } } int ok(int x) { int i,j; clr(head,-1); e=0; clr(dfn,0); clr(visit,0); id=color=0; while(!S.empty()) S.pop(); for(i=0;i<n;i++) Min[i]=0,Max[i]=1000; int num[N]; for(i=0;i<m;i++) { int cnt=0; int tmp=c[i]; while(tmp%x==0) cnt++,tmp/=x; num[i]=cnt; if(type[i]=='G') upMax(Min[a[i]],cnt),upMax(Min[b[i]],cnt); else upMin(Max[a[i]],cnt),upMin(Max[b[i]],cnt); } for(i=0;i<n;i++) if(Min[i]>Max[i]) return 0; for(i=0;i<m;i++) { int u=a[i]; int v=b[i]; if(type[i]=='G') { int x=Min[u]>num[i]; int y=Min[v]>num[i]; if(x&&y) return 0; else if(x) { if(Min[v]!=Max[v]) add(v+n,v); } else if(y) { if(Min[u]!=Max[u]) add(u+n,u); } else { if(Min[v]!=Max[v]&&Min[u]!=Max[u]) add(v+n,u),add(u+n,v); } } else { int x=Max[u]<num[i]; int y=Max[v]<num[i]; if(x&&y) return 0; else if(x) { if(Min[v]!=Max[v]) add(v,v+n); } else if(y) { if(Min[u]!=Max[u]) add(u,u+n); } else { if(Min[v]!=Max[v]&&Min[u]!=Max[u]) add(v,u+n),add(u,v+n); } } } for(i=0;i<n+n;i++) if(!dfn[i]) DFS(i); for(i=0;i<n;i++) if(belong[i]==belong[i+n]) return 0; return 1; } string cal(int _n,string _type,VI _a,VI _b,VI _c) { n=_n; type=_type; a=_a; b=_b; c=_c; m=SZ(type); int i; for(i=0;i<m;i++) { int t=c[i]; int j; for(j=2;j*j<=t;j++) if(t%j==0) { prime.pb(j); while(t%j==0) t/=j; } if(t>1) prime.pb(t); } for(i=0;i<SZ(prime);i++) { if(!ok(prime[i])) return NO; } return YES; } class GCDLCM { public: string possible(int n,string type,vector<int> A,vector<int> B,vector<int> C) { return cal(n,type,A,B,C); } };
第二题:给出两棵树均含有n个节点(编号0到n-1),每个节点有权值(有正有负)。两个树编号相同的节点的权值相同。找到一个[0,n-1]的子集S,使得S中的节点在两个树中都正好是一个子树。在这种情况下使得S中节点的权值和最大。
思路:对于某个节点u,假如我们最后一定选择该节点,那么对于节点p,我们若选它,那么p到u路径上的点都必须选,这样就成了一个最大权闭合图问题。
const int N=20005; struct node { int v,next,cap; }; node edges[N*100]; int head[N],e; int pre[N],curedge[N],h[N],num[N]; int s,t; void add(int u,int v,int cap) { edges[e].v=v; edges[e].cap=cap; edges[e].next=head[u]; head[u]=e++; } void Add(int u,int v,int cap) { add(u,v,cap); add(v,u,0); } int visit[N]; int Maxflow(int s,int t,int n) { clr(h,0); clr(num,0); int i; for(i=0;i<n;i++) curedge[i]=head[i]; int u=s,Min,k,x,ans=0; while(h[u]<n) { if(u==t) { Min=INF+1; for(i=s;i!=t;i=edges[curedge[i]].v) { x=curedge[i]; if(edges[x].cap<Min) { Min=edges[x].cap; k=i; } } ans+=Min; u=k; for(i=s;i!=t;i=edges[curedge[i]].v) { x=curedge[i]; edges[x].cap-=Min; edges[x^1].cap+=Min; } } for(i=curedge[u];i!=-1;i=edges[i].next) { if(edges[i].cap>0&&h[u]==h[edges[i].v]+1) { break; } } if(i!=-1) { curedge[u]=i; pre[edges[i].v]=u; u=edges[i].v; } else { if(--num[h[u]]==0) break; curedge[u]=head[u]; x=n; for(i=head[u];i!=-1;i=edges[i].next) { k=edges[i].v; if(edges[i].cap>0&&h[k]<x) x=h[k]; } h[u]=x+1; num[x+1]++; if(u!=s) u=pre[u]; } } return ans; } #define VI vector<int> int fa[55]; vector<int> g1[55],g2[55]; int n; int ss[55]; int f1[55],f2[55]; void DFS(int u,int pre,int fa[],VI g[]) { fa[u]=pre; int i; for(i=0;i<SZ(g[u]);i++) { int v=g[u][i]; if(v!=pre) DFS(v,u,fa,g); } } int cal(int r) { DFS(r,-1,f1,g1); DFS(r,-1,f2,g2); clr(head,-1); e=0; int S=n,T=n+1; int sum=0; int i; for(i=0;i<n;i++) { if(ss[i]>0) sum+=ss[i],Add(S,i,ss[i]); else Add(i,T,-ss[i]); if(f1[i]!=-1) Add(i,f1[i],INF); if(f2[i]!=-1) Add(i,f2[i],INF); } sum-=Maxflow(S,T,n+3); return sum; } int cal(VI a,VI b,VI c,VI d,VI score) { n=SZ(a)+1; int i; for(i=0;i<n-1;i++) { int u=a[i],v=b[i]; g1[u].pb(v); g1[v].pb(u); u=c[i]; v=d[i]; g2[u].pb(v); g2[v].pb(u); } for(i=0;i<n;i++) ss[i]=score[i]; int ans=0; for(i=0;i<n;i++) { int tmp=cal(i); if(tmp>ans) ans=tmp; } return ans; } class DoubleTree { public: int maximalScore(vector <int> a,vector<int> b,vector<int> c, vector<int> d, vector<int> score) { return cal(a,b,c,d,score); } };
第一题:一个人初始时在(0,0),要跳到(x,0)。给出一个数组,比如(2,4,9),那么跳的长度依次是2,4,9,2,4,9,…… 问最少跳多少次可以到达终点。
思路:两个循环之内可以跳完,那么我们只要让这些步数之内的数字组成两个p,q,使得p,q,x组成三角形即可(p+x=q或者p+q=x都行)。否则,若x是所有数字之和的很多倍,则一开始是一直直着向前跳m次,剩下rex=x-sum*m(sum是数组的数字之和),最后用前t项和组成大于rex的即可(这里大于也无所谓,因为这个一定可以和前面的m*sum以及x组成三角形)。
int cal(int L,vector<int> V) { if(L<0) L=-L; if(L==0) return 0; int n=SZ(V); int i; for(i=0;i<n;i++) V.pb(V[i]); n<<=1; i64 sum=0,Max=0,Min; for(i=0;i<n;i++) { if(V[i]>Max) Max=V[i]; sum+=V[i]; if(sum-Max>=Max) Min=0; else Min=Max-(sum-Max); if(Min<=L&&L<=sum) return i+1; } int ans=L/sum*n; int re=L%sum; i=0; while(re>0) re-=V[i++],ans++; return ans; } class PeriodicJumping { public: int minimalTime(int x,vector <int> jumpLengths) { return cal(x,jumpLengths); } };