Petrozavodsk Winter-2018. Jagiellonian U Contest
A. XOR
求出所有数的异或和$sum$,将所有数and上$sum$,然后求线性基,则选取$sum$的所有$1$对应的基最优。
时间复杂度$O(n\log x)$。
#include<cstdio> #include<cstdlib> #include<algorithm> #include<ctime> using namespace std; typedef long long ll; int Case,n,i,j;ll ans,sum,A,B,a[100],x,v[111111],pool[100]; const int N=20; ll cal(){ ll ans=sum; for(int S=0;S<1<<n;S++){ ll A=sum,B=0; for(i=0;i<n;i++)if(S>>i&1)A^=v[i],B^=v[i]; A-=B; if(A<0)A=-A; if(A<ans)ans=A; } return ans; } void print(ll x){ for(int i=11;~i;i--)printf("%lld",x>>i&1); puts(""); } ll myabs(ll x){return x>0?x:-x;} ll solve1(int x){ ll cur=a[x]; for(int i=x-1;~i;i--)if(sum>>i&1){ cur^=a[i]; } return myabs(cur-(sum^cur)); } ll solve2(int x){ ll cur=0; for(int i=x-1;~i;i--){ cur=max(cur,cur^a[i]); } return myabs(cur-(sum^cur)); } int main(){ //srand(time(NULL)); scanf("%d",&Case); while(Case--){ scanf("%d",&n); //n=rand()%10+1; sum=0; for(i=0;i<61;i++)a[i]=0; for(i=0;i<n;i++){ scanf("%lld",&x); //x=rand()%1000; v[i]=x; sum^=x; for(j=60;~j;j--)if(x>>j&1){ if(a[j])x^=a[j]; else {a[j]=x;break;} } } //ll vio=cal(); for(i=0;i<61;i++)for(j=i+1;j<61;j++)if(a[j]>>i&1)a[j]^=a[i]; for(i=60;~i;i--)if(sum>>i&1)break; int lim=i; if(lim>=0){ for(i=0;i<61;i++)pool[i]=a[i]&sum,a[i]=0; for(i=0;i<61;i++){ x=pool[i]; for(j=60;~j;j--)if(x>>j&1){ if(a[j])x^=a[j]; else {a[j]=x;break;} } } for(i=0;i<61;i++)for(j=i+1;j<61;j++)if(a[j]>>i&1)a[j]^=a[i]; ans=min(sum,min(solve1(lim),solve2(lim))); }else{ ans=0; } /*A=sum,B=0;*/ //print(sum); //for(i=60;~i;i--)if(a[i])print(a[i]); /*ans=sum; for(i=60;~i;i--)if((A^a[i])>=(B^a[i])&&(A^a[i])-(B^a[i])<ans){ A^=a[i]; B^=a[i]; ans=A-B; }*/ /*if(ans==vio)puts("OK");else{ printf("vio=%lld ans=%lld\n",vio,ans); printf("%d\n",n); for(i=0;i<n;i++)printf("%lld ",v[i]); puts(""); while(1); }*/ printf("%lld\n",ans); } }
B. Tribute
按题意模拟即可。
#include<bits/stdc++.h> using namespace std; int casenum, casei; typedef unsigned int UI; int n; vector<UI>vt, wt; multiset<UI>sot; multiset<UI>ans; bool solve() { for(int i = 1; i <= n; ++i) { int x = *sot.begin(); ans.insert(x); // printf("x = %d\n", x); // for(auto y : vt)printf("%d ", y); puts(""); wt.clear(); for(auto y : vt) { int z = x + y; // printf("%d\n", z); if(sot.find(z) == sot.end())return 0; sot.erase(sot.find(z)); wt.push_back(z); } for(auto y : wt)vt.push_back(y); } return 1; } int main() { scanf("%d", &casenum); for(casei = 1; casei <= casenum; casei ++) { scanf("%d", &n); int m = (1 << n) - 1; vt.clear(); vt.push_back(0); sot.clear(); ans.clear(); for(int i = 1; i <= m; ++i) { int x; scanf("%d", &x); sot.insert(x); } if(!solve())puts("NO"); else { int id = 0; for(auto it : ans)printf("%d%c", it, ++id == n ? '\n' : ' '); } } }
C. Boardroom Meeting
CDQ分治+扫描线树状数组,时间复杂度$O(n\log^2n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=200010; int Case,n,i,a[N],b[N],c[N],ans,f[N],qa[N],qb[N],bit[N],vis[N],T; inline void up(int&a,int b){a<b?(a=b):0;} inline void ins(int x,int p){for(;x<=n;x+=x&-x)if(vis[x]<T)vis[x]=T,bit[x]=p;else up(bit[x],p);} inline void ask(int x,int&p){for(;x;x-=x&-x)if(vis[x]==T)up(p,bit[x]);} inline bool cmp(int x,int y){return a[x]<a[y];} void solve(int l,int r){ if(l==r){ up(ans,++f[l]); return; } int mid=(l+r)>>1,i,j,ca=0,cb=0; solve(l,mid); for(i=l;i<=mid;i++)qa[++ca]=i; for(;i<=r;i++)qb[++cb]=i; sort(qa+1,qa+ca+1,cmp); sort(qb+1,qb+cb+1,cmp); T++; for(i=j=1;i<=cb;i++){ while(j<=ca&&a[qa[j]]<a[qb[i]]){ ins(b[qa[j]],f[qa[j]]); j++; } ask(b[qb[i]]-1,f[qb[i]]); } solve(mid+1,r); } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=n;i++)scanf("%d",&b[i]),c[i]=b[i]; sort(c+1,c+n+1); for(i=1;i<=n;i++)b[i]=lower_bound(c+1,c+n+1,b[i])-c; for(i=1;i<=n;i++)f[i]=0; ans=0; solve(1,n); printf("%d\n",ans); } } /* 1 6 1 2 6 3 4 6 4 1 3 5 7 7 */
D. Secret Santa
第二类斯特林数容斥,时间复杂度$O(a\log n)$。
#include<cstdio> const int N=2010,P=1000000007; int n,a,i,j,Case,fac[N],S[N][N]; int po(int a,int b){int t=1;for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P;return t;} int T(int m,int n){ int ans=0; for(int k=0;k<m;k++){ int t=fac[k]; if((k+m)%2)t=(P-t)%P; t=1LL*t*po(k+1,n)%P; t=1LL*t*S[m][k+2]%P; ans=(ans+t)%P; } return ans; } int main(){ for(i=0;i<N;i++)for(j=0;j<N;j++){ if(i>=1&&j==0){S[i][j]=0;continue;} if(i==j){S[i][j]=1;continue;} if(j>=1&&j<i)S[i][j]=(1LL*S[i-1][j]*j+S[i-1][j-1])%P; } for(fac[0]=i=1;i<N;i++)fac[i]=1LL*fac[i-1]*i%P; scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&a);a++;n-=a-2; printf("%d\n",T(a,n)); } }
E. Guessing Game
状压DP,设$f[S]$表示$S$情况下最优策略最坏需要的步数,其中$S$是个$k$位$3$进制数,分别表示每一位未询问、已询问且回答为$0$、已询问且回答为$1$的情况。
用高维前缀和预处理出所有$f[S]=0$的状态,剩下的状态枚举询问哪一位转移即可。
时间复杂度$O(3^kk)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=13,M=1600000; int p[20],Case,n,m,i,j,k,f[M],c[M];char s[20]; inline int get(int x,int y){return x/p[y]%3;} int main(){ for(p[0]=i=1;i<20;i++)p[i]=p[i-1]*3; scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&k); m=p[k]; for(i=0;i<m;i++)c[i]=0; for(i=1;i<=n;i++){ scanf("%s",s); int t=0; for(j=0;j<k;j++)t=t*3+s[j]-'0'; c[t]++; } for(i=0;i<k;i++)for(j=0;j<m;j++)if(get(j,i)==2){ c[j]+=c[j-p[i]]+c[j-p[i]*2]; } for(i=0;i<m;i++){ if(c[i]<=1){f[i]=0;continue;} f[i]=M; for(j=0;j<k;j++)if(get(i,j)==2){ f[i]=min(f[i],max(f[i-p[j]],f[i-p[j]*2])); } f[i]++; } printf("%d\n",f[m-1]); } }
F. Flat Earth
按题意模拟即可。
#include<stdio.h> #include<math.h> int casenum, casei, a[10]; const double PI = acos(-1.0); int main() { scanf("%d", &casenum); for(casei = 1; casei <= casenum; casei ++){ for(int i = 1; i <= 8; i ++){ scanf("%d", &a[i]); } printf("%.10f\n", PI * a[4] * a[4]); } }
G. We Need More Managers!
建立一张$2^n$个点的图,分别表示每种串。对于每个点,向恰好改变了一位的$n$个点连边。
则题意可转化为对给定$m$个点求出两两最短路作为权值的最小生成树。
设$p_x$和$d_x$分别表示离每个点最近的给定串以及对应的距离,可以BFS求出,则对于一条边$(u,v)$,在生成树中加入一条连接$p_u$和$p_v$,权值为$d_u+d_v+1$的边即可。
时间复杂度$O(n2^n\alpha(m))$。
#include<cstdio> #include<algorithm> using namespace std; const int N=1100000; int Case,n,m,all,i,j,a[N];char s[100]; int h,t,d[N],p[N],x,y,z,q[N]; int f[N],ans,ce; struct E{ int x,y,z; E(){} E(int _x,int _y,int _z){x=_x,y=_y,z=_z;} }e[N*20]; inline bool cmp(const E&a,const E&b){return a.z<b.z;} inline void ext(int x,int y,int z){ if(y<d[x]){ d[x]=y; p[x]=z; q[++t]=x; } } int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void add(int x,int y,int z){ if(x!=y)e[++ce]=E(x,y,z); } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); all=1<<n; for(i=0;i<all;i++)d[i]=N; for(i=1;i<=m;i++){ scanf("%s",s); a[i]=0; for(j=0;j<n;j++)a[i]=a[i]*2+(s[j]=='L'); d[a[i]]=0,p[a[i]]=i; } h=1,t=0; for(i=0;i<all;i++)if(!d[i])q[++t]=i; while(h<=t){ x=q[h++]; y=d[x]+1; z=p[x]; for(i=0;i<n;i++)ext(x^(1<<i),y,z); } ce=0; for(i=0;i<all;i++)for(j=0;j<n;j++)add(p[i],p[i^(1<<j)],d[i]+d[i^(1<<j)]+1); for(i=1;i<=m;i++)f[i]=i; ans=0; sort(e+1,e+ce+1,cmp); for(i=1;i<=ce;i++)if(F(e[i].x)!=F(e[i].y))f[f[e[i].x]]=f[e[i].y],ans+=e[i].z; printf("%d\n",ans); } }
H. Masterpiece
若不考虑每个点是被第几次走过的,则方案唯一,$O(n)$模拟求出方案后统计分岔点个数$t$,则答案为$2^t$。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define rt 1,1,n #define ls o<<1 #define rs o<<1|1 #define mid (l+r>>1) #define lson ls,l,mid #define rson rs,mid+1,r typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; int r[N], c[N]; //try go to (y, x) bool flag = 1; int r1, r2; int c1, c2; bool doit(int y, int x) { if(r1 == r2 && c1 == c2)return flag = 1; if(y > n || x > n)return 0; if(!r[y] || ! c[x])return 0; --r[y]; --c[x]; return flag = 1; } int solve() { int ans = 1; r1 = 1, c1 = 1; r2 = 1, c2 = 1; doit(1, 1); --r[1]; --c[1]; while(1) { flag = 0; if(r1 == r2 && c1 == c2) { if(r1 == n && c1 == n) { return ans; } int rnum = r[r1]; int cnum = c[c1]; if(rnum && cnum) { ans = ans * 2 % Z; for(int i = 1; i <= rnum; ++i) { if(!doit(r1, ++c1))return 0; } for(int i = 1; i <= cnum; ++i) { if(!doit(++r2, c2))return 0; } } else if(rnum) { if(!doit(r1, ++c1))return 0; ++c2; } else if(cnum) { if(!doit(++r1, c1))return 0; ++r2; } else return 0; } while(r1 < r2) { if(r[r1]) { if(!doit(r1, ++c1))return 0; } else if(!doit(++r1, c1))return 0; } while(r2 < r1) { if(r[r2]) { if(!doit(r2, ++c2))return 0; } else if(!doit(++r2, c2))return 0; } while(c1 < c2) { if(c[c1]) { if(!doit(++r1, c1))return 0; } else if(!doit(r1, ++c1))return 0; } while(c2 < c1) { if(c[c2]) { if(!doit(++r2, c2))return 0; } else if(!doit(r2, ++c2))return 0; } if(!flag)return 0; } } int main() { scanf("%d", &casenum); for(casei = 1; casei <= casenum; ++casei) { scanf("%d", &n); //int n = rand() % 10 + 1; for(int i = 1; i <= n; ++i) { scanf("%d", &r[i]); //r[i] = rand() % (n + 1); } for(int i = 1; i <= n; ++i) { scanf("%d", &c[i]); //c[i] = rand() % (n + 1); } int ans = solve(); for(int i = 1; i <= n; ++i) { if(r[i] || c[i])ans = 0; } printf("%d\n", ans); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 100 5 3 3 3 3 3 1 4 4 3 3 4 4 2 2 4 4 2 2 4 */
I. Don’t Split The Atom!
胜负只取决于$n$的奇偶性。
#include<stdio.h> #include<math.h> int casenum, casei, a[10]; const double PI = acos(-1.0); int main() { scanf("%d", &casenum); for(casei = 1; casei <= casenum; casei ++){ int n; scanf("%d", &n); puts(n & 1 ? "B" : "A"); } }
J. Bobby Tables
组合数可以看作长度为$k$的一个区间除以长度为$k$的一个前缀。
枚举每个$k$作为前缀,二分对应的区间,取对数加速判定,在附近配合取模判定即可。
时间复杂度$O(m\log m)$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; typedef long double ld; const int N=200010,P=1000000007,K=20; const ld eps=1e-2; int Case,n,m,i,a[N],fac[N],inv[N],mul; ld Log[N],s[N],sum; inline bool check(int n,int m){ ld A=s[n]-s[n-m]-s[m]; if(fabs(A-sum)>eps)return 0; int B=1LL*fac[n]*inv[n-m]%P*inv[m]%P; if(B!=mul)return 0; puts("YES"); printf("%d %d\n",n,m); return 1; } inline void solve(){ scanf("%d%d",&n,&m); sum=0; mul=1; for(i=1;i<=n;i++){ scanf("%d",&a[i]); mul=1LL*mul*a[i]%P; sum+=Log[a[i]]; } for(i=1;i<=m;i++){ //[x,x+i-1]/[1..i] //C(x+i-1,i) //x+i-1>=i //1<=x<=m+1-i //x+i-1<=m int l=1,r=m+1-i; while(l<=r){ int mid=(l+r)>>1; ld A=s[mid+i-1]-s[mid-1]-s[i]; if(fabs(A-sum)<eps){ for(int j=max(mid-K,1);j<=min(m+1-i,mid+K);j++)if(check(j+i-1,i))return; break; } if(A<sum)l=mid+1;else r=mid-1; } } puts("NO"); } int main(){ for(i=1;i<N;i++)Log[i]=log2(i); for(i=1;i<N;i++)s[i]=s[i-1]+Log[i]; for(inv[0]=inv[1]=1,i=2;i<N;i++)inv[i]=1LL*(P-inv[P%i])*(P/i)%P; for(fac[0]=i=1;i<N;i++)fac[i]=1LL*fac[i-1]*i%P; for(i=1;i<N;i++)inv[i]=1LL*inv[i-1]*inv[i]%P; scanf("%d",&Case); while(Case--){ solve(); } }
K. Triples
长链剖分或树分治经典题。
#include<cstdio> #include<vector> #include<algorithm> #include<cstring> #define pb push_back #define V vector #define N 200010 using namespace std; typedef long long ll; typedef pair<int,ll>P; int Case; int n,i,x,y,g[N],v[N<<1],nxt[N<<1],ed,f[N],del[N];ll ans;V<P>h[N]; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} inline bool cmp(V<int>*a,V<int>*b){return a->size()>b->size();} template<class C>inline C&get(V<C>&a,size_t x){return a[a.size()-x-1];} V<int>*dfs(int x,int y){ V<V<int>*>t; for(int i=g[x];i;i=nxt[i])if(v[i]!=y)t.pb(dfs(v[i],x)); if(!t.size())return new V<int>(1,1); sort(t.begin(),t.end(),cmp); V<int>*a=t[0]; a->pb(1); if(t.size()==1)return a; V<ll>b; b.resize(t[1]->size()+1,0); for(int i=1;i<t.size();i++){ V<int>*u=t[i]; for(int j=1;j<=u->size();j++){ int ch=get(*u,j-1),&rt=get(*a,j);ll&rd=get(b,j); ans+=1LL*ch*rd; rd+=1LL*ch*rt; rt+=ch; } } for(int i=1;i<b.size();i++){ h[x].pb(P(i,get(b,i))); ans-=1LL*get(b,i)*get(*a,i); } return a; } void cal(int x,int y){ f[x]=1; for(int i=g[x];i;i=nxt[i])if(v[i]!=y&&!del[v[i]])cal(v[i],x),f[x]+=f[v[i]]; } inline int findroot(int x){ cal(x,-1); int n=f[x],t=0,y=-1; do{ t=0; for(int i=g[x];i;i=nxt[i])if(v[i]!=y&&!del[v[i]]&&2*f[v[i]]>n){ y=x,x=v[i],t=1; break; } }while(t); return x; } inline void work(V<V<int> >&d,V<V<P> >&q,int k,bool rt,int x){ int st=k==1?0:d.size()-1,en=k==1?d.size():-1;V<int>a(1,rt); for(int i=st;i!=en;i+=k){ V<int>D=d[i]; for(V<P>::iterator p=q[i].begin();p!=q[i].end();p++){ int j=p->first; if(j<a.size())ans+=1LL*a[j]*p->second; } a.resize(max(a.size(),D.size()),0); for(int j=0;j<D.size();j++)a[j]+=D[j]; } if(rt)for(V<P>::iterator p=h[x].begin();p<h[x].end();p++){ int j=p->first; if(j<a.size())ans+=1LL*a[j]*p->second; } } void dfsd(int x,int y,int z,V<int>&d){ if(z==d.size())d.pb(1);else d[z]++; for(int i=g[x];i;i=nxt[i])if(v[i]!=y&&!del[v[i]])dfsd(v[i],x,z+1,d); } void dfsq(int x,int y,int z,V<P>&q){ for(V<P>::iterator i=h[x].begin();i<h[x].end();i++){ int t=i->first-z; if(t>=0)q.pb(P(t,i->second)); } for(int i=g[x];i;i=nxt[i])if(v[i]!=y&&!del[v[i]])dfsq(v[i],x,z+1,q); } void solve(int x){ int y=findroot(x); del[y]=1; for(int i=g[y];i;i=nxt[i])if(!del[v[i]])solve(v[i]); V<V<int> >d;V<V<P> >q; for(int i=g[y];i;i=nxt[i])if(!del[v[i]]){ V<int>A(1,0); dfsd(v[i],-1,1,A); d.pb(A); V<P>B; dfsq(v[i],-1,1,B); q.pb(B); } work(d,q,1,1,y),work(d,q,-1,0,y); del[y]=0; } int main(){ scanf("%d",&Case); while(Case--){ i=x=y=0; memset(g,0,sizeof g); memset(f,0,sizeof f); memset(v,0,sizeof v); memset(nxt,0,sizeof nxt); ed=0; memset(del,0,sizeof del); ans=0; for(i=0;i<N;i++)h[i].clear(); scanf("%d",&n); for(i=1;i<n;i++){ scanf("%d%d",&x,&y); x--,y--; add(x,y),add(y,x); } dfs(0,-1); solve(0); printf("%lld\n",ans); } }
L. Related Languages
枚举$O(nm)$对区间右端点,左端点的取值满足单调性,双指针即可。
时间复杂度$O(nm)$。
#include<cstdio> const int N=4010; int Case,n,m,i,j,k,ans,g[N*3]; char a[N],b[N],f[N][N]; int w[N*3],V; int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d%d%s%s",&n,&m,&V,a+1,b+1); ans=0; for(i=1;i<=n;i++)for(j=1;j<=m;j++)f[i][j]=a[i]!=b[j]; for(i=0;i<=N+N+5;i++)g[i]=w[i]=0; for(i=1;i<=n;i++)for(j=1;j<=m;j++){ k=i-j+N; g[k]++; w[k]+=f[i][j]; int G=g[k],W=w[k]; while(G>0&&W>V){ G--; W-=f[i-G][j-G]; } g[k]=G; w[k]=W; if(G>ans)ans=G; } printf("%d\n",ans); } }