Asia-Tsukuba 2017
A. Secret of Chocolate Poles
DP,$f[i][j]$表示高度为$i$,顶层颜色为$j$的方案数。
时间复杂度$O(l)$。
#include<cstdio> typedef __int128 lll; const int N=200; lll f[N][2],ans;//dark white int l,k,i; void write(lll x){ if(x>=10)write(x/10); x%=10; printf("%d",(int)(x)); } int main(){ scanf("%d%d",&l,&k); f[1][0]++; f[k][0]++; for(i=1;i<=l;i++){ f[i+1][1]+=f[i][0]; f[i+1][0]+=f[i][1]; f[i+k][0]+=f[i][1]; } for(i=1;i<=l;i++)ans+=f[i][0]; write(ans); }
B. Parallel Lines
将$O(n^2)$对点对按斜率分组,设$f[S][i]$表示$S$集合的点已经配对,当前分组选了$i$条直线的最大得分。
时间复杂度$O(2^nn^3)$。
#include<cstdio> #include<cmath> #include<cstdlib> #include<algorithm> using namespace std; const int N=16,inf=100000000; int n,i,j,m,mx,ans,f[1<<N][10],g[1<<N][10],w[N]; struct P{ int x,y; }a[N]; struct E{ int x,y,s; }e[N*N*5]; inline bool cmp(const E&a,const E&b){ if(a.x!=b.x)return a.x<b.x; return a.y<b.y; } inline void up(int&a,int b){a<b?(a=b):0;} inline void clr(){ for(int i=0;i<1<<n;i++)for(int j=0;j<=mx;j++)g[i][j]=f[i][j]; } inline void nxt(){ for(int i=0;i<1<<n;i++)for(int j=0;j<=mx;j++)f[i][j]=g[i][j]; } inline void ins(int S){ for(int i=0;i<1<<n;i++)if(!(i&S))for(int j=0;j<=mx;j++)if(f[i][j]>=0){ up(g[i|S][j+1],f[i][j]); } } inline void mov(){ for(int i=0;i<1<<n;i++)for(int j=0;j<=mx;j++)g[i][j]=-inf; for(int i=0;i<1<<n;i++)for(int j=0;j<=mx;j++)if(f[i][j]>=0)up(g[i][0],f[i][j]+w[j]); nxt(); } int main(){ for(i=0;i<N;i++)w[i]=i*(i-1)/2; scanf("%d",&n); mx=n/2; for(i=0;i<n;i++)scanf("%d%d",&a[i].x,&a[i].y); for(i=0;i<n;i++)for(j=0;j<i;j++){ int dx=a[i].x-a[j].x; int dy=a[i].y-a[j].y; int A,B; if(dx==0)A=0,B=1; else if(dy==0)A=1,B=0; else{ if(dx<0)dx*=-1,dy*=-1; int d=__gcd(abs(dx),abs(dy)); A=dx/d,B=dy/d; } e[++m].x=A; e[m].y=B; e[m].s=(1<<i)|(1<<j); } for(i=0;i<1<<n;i++)for(j=0;j<=mx;j++)f[i][j]=-inf; f[0][0]=0; sort(e+1,e+m+1,cmp); for(i=1;i<=m;i=j){ for(j=i;j<=m&&e[i].x==e[j].x&&e[i].y==e[j].y;j++){ clr(); ins(e[j].s); nxt(); } mov(); } for(i=0;i<1<<n;i++)up(ans,f[i][0]); printf("%d",ans); }
C. Medical Checkup
若上一个人比这一个人慢,则可以将两人合并,否则可以独立计算。
#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 ls o<<1 #define rs o<<1|1 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, T; LL h[N], a[N], b[N]; int main() { while(~scanf("%d%d",&n, &T)) { for(int i = 1; i <= n; ++i)scanf("%lld", &h[i]); a[0] = b[0] = 0; for(int i = 1; i <= n; ++i) { if(b[i - 1] >= h[i]) { a[i] = a[i - 1] + h[i]; b[i] = b[i - 1]; } else { a[i] = a[i - 1] + h[i]; b[i] = h[i]; } } for(int i = 1; i <= n; ++i) { if(T < a[i]) { printf("%d\n", 1); } else { int t = T - a[i]; int g = t / b[i]; printf("%d\n", g + 2); } } } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
D. Making Perimeter of the Convex Hull Shortest
留坑。
E. Black or White
最优解中填充的区间一定是包含的或者相离的。
底层那次填充满足长度不超过$k$后,内部填充不需要考虑长度限制。
此时内部最优解即为对应颜色连续段的个数,设$f[i]$表示$[1,i]$合法的最小代价,若$a[i]==b[i]$,则$f[i]=f[i-1]$;否则$f[i]=\min(f[j]+cost(j+1,i))+1$,其中$i-j\leq k$。
将$cost$用前缀和表示,单调队列优化转移即可。
时间复杂度$O(n)$。
#include<cstdio> const int N=500010,inf=1000000000; int n,k,i,j,f[N],vr[N][2],vl[N][2],s[N];char a[N],b[N]; inline void up(int&a,int b){a>b?(a=b):0;} struct DS{ int h,t,a[N],b[N]; void init(){ h=t=1; } int ask(int x){ while(h<=t&&x-a[h]>k)h++; return b[h]; } void add(int x,int y){ while(h<=t&&b[t]>=y)t--; a[++t]=x; b[t]=y; } }q[2]; int main(){ scanf("%d%d%s%s",&n,&k,a+1,b+1); for(i=0;i<2;i++){ char o=i?'B':'W'; for(j=1;j<=n;j++){ s[j]=s[j-1]; if(b[j]==o&&b[j]!=b[j-1])s[j]++; vr[j][i]=s[j]; vl[j][i]=-s[j]+(b[j]==o&&b[j+1]==o); } } for(j=0;j<2;j++)q[j].init(); for(i=1;i<=n;i++){ f[i]=inf; if(a[i]==b[i])f[i]=f[i-1]; for(j=0;j<2;j++)up(f[i],q[j].ask(i)+1+vr[i][j]); for(j=0;j<2;j++)q[j].add(i,f[i]+vl[i][j]); //printf("f[%d]=%d\n",i,f[i]); } printf("%d",f[n]); }
F. Pizza Delivery
求出$1$和$2$到每个点的最短路,并得到$1$到$2$的最短路DAG,以及$1$和$2$在DAG上到每个点的路径条数。
对于一条边,若是DAG中的边,则若经过它的方案数为总方案数,最短路变长,否则不变;
若不是DAG中的边,则若对应最短路加上边权小于最短路,最短路变短,否则不变。
路径条数可以取模判断。
时间复杂度$O(m\log m)$。
#include<cstdio> #include<algorithm> #include<vector> #include<queue> using namespace std; typedef long long ll; typedef pair<ll,int>P; const ll inf=1LL<<60; const int N=100010,M=100010,MOD=1000000007; int n,m,i,e[M][3]; struct G{ int g[N],v[M],w[M],nxt[M],ed; ll d[N]; priority_queue<P,vector<P>,greater<P> >q; inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;} inline void ext(int x,ll y){if(d[x]>y)q.push(P(d[x]=y,x));} void work(int S){ int i; for(i=1;i<=n;i++)d[i]=inf; ext(S,0); while(!q.empty()){ P t=q.top();q.pop(); if(d[t.second]<t.first)continue; for(i=g[t.second];i;i=nxt[i])ext(v[i],t.first+w[i]); } } }A,B; struct GG{ int g[N],v[M],nxt[M],ed,f[N],vis[N]; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} int dfs(int x){ if(vis[x])return f[x]; vis[x]=1; for(int i=g[x];i;i=nxt[i])f[x]=(f[x]+dfs(v[i]))%MOD; return f[x]; } void work(int S){ f[S]=1; vis[S]=1; for(int i=1;i<=n;i++)dfs(i); } }C,D; ll best; int main(){ scanf("%d%d",&n,&m); for(i=1;i<=m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); e[i][0]=x; e[i][1]=y; e[i][2]=z; A.add(x,y,z); B.add(y,x,z); } A.work(1); B.work(2); best=A.d[2]; for(i=1;i<=m;i++){ int x,y,z; x=e[i][0]; y=e[i][1]; z=e[i][2]; if(A.d[x]+z+B.d[y]==best){ C.add(y,x); D.add(x,y); } } C.work(1); D.work(2); for(i=1;i<=m;i++){ int x,y,z; x=e[i][0]; y=e[i][1]; z=e[i][2]; if(A.d[x]+z+B.d[y]==best){ if(1LL*C.f[x]*D.f[y]%MOD==C.f[2])puts("SAD"); else puts("SOSO"); }else{ if(A.d[y]+z+B.d[x]<best)puts("HAPPY"); else puts("SOSO"); } } }
G. Rendezvous on a Tetrahedron
将立体图形展开成二维,直接求出终点坐标,然后计算位于哪一面即可。
#include<cstdio> #include<cmath> #include<algorithm> #include<cstdlib> using namespace std; #define double double double R,eps=1e-5,T; const double PI = acos(-1.0); int cmp(double x) { if(fabs(x) < eps) return 0; return x > 0 ? 1 : -1; } double Sin(double th) { double ap = th * PI / 180; return sin(ap); } struct point { double x, y; }a[3]; double det(point a, point b) { return a.x * b.y - a.y * b.x; } const double sq3 = sqrt(3.0), sq3_2 = sq3 / 2; double area(point a, point b, point c) { return fabs(det(a, b) + det(b, c) + det(c, a)); } bool inTri(double x, double y, int xx, int yy) { point d; d.x = x; d.y = y; double area1 = area(d, a[0], a[1]) + area(d, a[1], a[2]) + area(d, a[2], a[0]); double area2 = area(a[0], a[1], a[2]); if(cmp(area1 - area2) == 0) return 1; //if(area(d, a[0], a[1]) + area(d, a[1], a[2]) + area(d, a[2], a[0]) == area(a[0], a[1], a[2])) return 1; /* if(xx == 3 && yy == 3){ printf("area1 = %.10f\n", area1); printf("area2 = %.10f\n", area2); printf("x = %.10f y = %.10f ", x, y); printf("xx = %d yy = %d\n", xx, yy); for(int i = 0; i < 3; i ++){ printf("a[] = %.10f %.10f\n", a[i].x, a[i].y); } } */ return 0; } int solve(double &th2, double &lp2) { double th, lp; th = th2; lp = lp2; double ax, xy, X, Y; if(th <= 30){ ax = lp * Sin(60 + th) / Sin(90); xy = lp * Sin(30 - th) / Sin(90); X = -xy; Y = -ax; } else{ ax = lp * Sin(60 + th) / Sin(90); xy = lp * Sin(th - 30) / Sin(90); X = xy; Y = -ax; } // X 是横坐标,Y是纵坐标 for(int x = 1; x <= 40; x ++){ for(int y = (x - 1) * 2 + 1; y >= 1; y --){ if(x % 2 == 1 && y % 2 == 1){ a[0].x = (y + 1) / 2 - (x + 1) / 2; a[0].y = (x - 1) * - sq3_2; a[1].x = a[0].x - 0.5; a[1].y = a[0].y - sq3_2; a[2].x = a[0].x + 0.5; a[2].y = a[1].y; } else if(x % 2 == 1){ a[0].x = (y + 1) / 2 - (x + 1) / 2; a[0].y = (x - 1) * -sq3_2; a[1].x = a[0].x + 1; a[1].y = a[0].y; a[2].x = a[0].x + 0.5; a[2].y = a[0].y - sq3_2; } else if(y % 2 == 1){ a[0].x = (y + 1) / 2 - (x + 2) / 2 + 0.5; a[0].y = (x - 1) * -sq3_2; a[1].x = a[0].x - 0.5; a[1].y = a[0].y - sq3_2; a[2].x = a[0].x + 0.5; a[2].y = a[1].y; } else{ a[0].x = (y + 1) / 2 - (x + 2) / 2 + 0.5; a[0].y = (x - 1) * -sq3_2; a[1].x = a[0].x + 1; a[1].y = a[0].y; a[2].x = a[0].x + 0.5; a[2].y = a[0].y - sq3_2; } if(inTri(X, Y, x, y)){ /* printf("pos: %.10f %.10f ", X, Y); printf("rc: %d %d\n", x, y); for(int i = 0; i < 3; i ++){ printf("%.10f %.10f\n", a[i].x, a[i].y); } */ if(x % 2 == 1){ if(y % 4 == 1){ return 1; } else if(y % 4 == 2){ return 4; } else if(y % 4 == 3){ return 2; } return 3; } else{ if(y % 4 == 1){ return 3; } else if(y % 4 == 2){ return 2; } else if(y % 4 == 3){ return 4; } return 1; } } } } } char s1[3], s2[3]; double th1, lp1, th2, lp2; void fre() { freopen("c://test//output.out", "r", stdin); freopen("c://test//output1.out", "w", stdout); } int main() { //fre(); while(~scanf("%s%lf%lf", s1, &th1, &lp1)) { int ans1 = solve(th1, lp1); scanf("%s%lf%lf", s2, &th2, &lp2); int ans2 = solve(th2, lp2); if(s1[0] == 'B'){ // 不变 } else if(s1[0] == 'D'){ if(ans1 == 1) ans1 = 3; else if(ans1 == 2) ans1 = 2; else if(ans1 == 3) ans1 = 4; else if(ans1 == 4) ans1 = 1; } else if(s1[0] == 'C'){ if(ans1 == 1) ans1 = 4; else if(ans1 == 2) ans1 = 2; else if(ans1 == 3) ans1 = 1; else if(ans1 == 4) ans1 = 3; } if(s2[0] == 'B'){ // 不变 } else if(s2[0] == 'D'){ if(ans2 == 1) ans2 = 3; else if(ans2 == 2) ans2 = 2; else if(ans2 == 3) ans2 = 4; else if(ans2 == 4) ans2 = 1; } else if(s2[0] == 'C'){ if(ans2 == 1) ans2 = 4; else if(ans2 == 2) ans2 = 2; else if(ans2 == 3) ans2 = 1; else if(ans2 == 4) ans2 = 3; } /* if(s1[0] == s2[0]){ } else if(s1[0] == 'B' && s2[0] == 'C' || s1[0] == 'C' && s2[0] == 'D' || s1[0] == 'D' && s2[0] == 'B'){ if(ans2 == 1) ans2 = 4; else if(ans2 == 2) ans2 = 2; else if(ans2 == 3) ans2 = 1; else if(ans2 == 4) ans2 = 3; } else{ if(ans2 == 1) ans2 = 3; else if(ans2 == 2) ans2 = 2; else if(ans2 == 3) ans2 = 4; else if(ans2 == 4) ans2 = 1; } */ if(ans1 == ans2) puts("YES"); else puts("NO"); } } /* BC 32 11 CD 59 11 */
H. Homework
对于最优解,将两门课程合并然后贪心取截止日期最早的即可。
对于最劣解,建图:
$S\rightarrow$每份数学作业连边,容量$1$。
每份信息作业$\rightarrow T$连边,容量$1$。
每一天拆成两个点,内部连边,容量$1$。
每份作业与对应天连边,容量$1$。
则每一条$S$到$T$的增广路径都对应某一天无论选哪种课程答案都会加一,而某一天只有一种课程的自然不会有流量。
故此时用题中所给贪心策略求出的答案就是这个图的最大流。
#include<cstdio> #include<queue> #include<vector> #include<algorithm> using namespace std; const int N=1500; int n,m,lim,i,j,a[N],b[N];vector<int>g[N];priority_queue<int,vector<int>,greater<int> >q; int ans; namespace Flow{ const int inf=~0U>>2; struct E{int t,f;E*nxt,*pair;}*g[N],*d[N],pool[1000000],*cur=pool; int S,T,h[N],gap[N],maxflow; inline void add(int s,int t,int f){ E*p=cur++;p->t=t;p->f=f;p->nxt=g[s];g[s]=p; p=cur++;p->t=s;p->f=0;p->nxt=g[t];g[t]=p; g[s]->pair=g[t];g[t]->pair=g[s]; } int sap(int v,int flow){ if(v==T)return flow; int rec=0; for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){ int ret=sap(p->t,min(flow-rec,p->f)); p->f-=ret;p->pair->f+=ret;d[v]=p; if((rec+=ret)==flow)return flow; } if(!(--gap[h[v]]))h[S]=T; gap[++h[v]]++;d[v]=g[v]; return rec; } void init(int o){ S=o+1; T=S+1; } int work(){ for(gap[0]=T,i=1;i<=T;i++)d[i]=g[i]; while(h[S]<T)maxflow+=sap(S,inf); return maxflow; } } int main(){ scanf("%d%d",&n,&m); lim=400; //[1..m] [m+1..n] Flow::init(n+lim*2); for(i=1;i<=n;i++){ scanf("%d%d",&a[i],&b[i]); g[a[i]].push_back(b[i]); if(i<=m){ Flow::add(Flow::S,i,1); for(j=a[i];j<=b[i];j++)Flow::add(i,j+n,1); }else{ Flow::add(i,Flow::T,1); for(j=a[i];j<=b[i];j++)Flow::add(j+n+lim,i,1); } } for(i=1;i<=lim;i++)Flow::add(i+n,i+n+lim,1); for(i=1;i<=lim;i++){ for(j=0;j<g[i].size();j++)q.push(g[i][j]); while(!q.empty()){ int x=q.top(); if(x<i)q.pop();else break; } if(!q.empty())q.pop(),ans++; } printf("%d\n%d",ans,Flow::work()); }
I. Starting a Scenic Railroad Service
对于乘客自己选座的情况:答案为最大的和某个区间相交的区间总数加$1$。
对于系统自己选座的情况:答案为最大的单站所需乘客数。
前缀和计算即可。
时间复杂度$O(n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=200010; int n,i,a[N],b[N],s[N],l[N],r[N],ans1,ans2; int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ int x,y; scanf("%d%d",&x,&y); a[x]++; b[y]++; l[i]=x; r[i]=y; s[x]++; s[y]--; } for(i=1;i<N;i++)a[i]+=a[i-1],b[i]+=b[i-1]; for(i=1;i<=n;i++)ans1=max(ans1,a[r[i]-1]-b[l[i]]); for(i=1;i<N;i++)ans2=max(ans2,s[i]+=s[i-1]); printf("%d %d",ans1,ans2); }
J. String Puzzle
每个位置只会和前面一个位置直接通过信息等价,将其看成有根树的父亲,则将所有已知字符和询问都挪到根即可处理问题。
对于挪到根,只需要双指针枚举所有信息,然后减去一个等差数列,保证每次跳过一条信息。
时间复杂度$O(m^2)$。
#include<cstdio> #include<map> using namespace std; const int N=1100; int n,a,b,q,i,x,m,known[N];char ch[N][9]; int y[N],h[N]; map<int,char>T; struct E{int l,d;E(){}E(int _l,int _d){l=_l,d=_d;}}e[N]; inline int go(int x){ int i=m; while(1){ while(i&&e[i].l>x)i--; int l=e[i].l,d=e[i].d; if(!i||!d)break; x-=(x-l)/d*d; while(x>=l)x-=d; } return x; } int main(){ scanf("%d%d%d%d",&n,&a,&b,&q); for(i=1;i<=a;i++)scanf("%d%s",&known[i],ch[i]); for(i=1;i<=b;i++)scanf("%d%d",&y[i],&h[i]); y[b+1]=n+1; for(i=1;i<=b;i++)e[++m]=E(y[i],h[i]?y[i]-h[i]:0); for(i=1;i<=a;i++)T[go(known[i])]=ch[i][0]; while(q--){ scanf("%d",&x); x=go(x); if(T.find(x)==T.end())putchar('?');else putchar(T[x]); } }
K. Counting Cycles
求出DFS生成树,将非树边的端点作为关键点求出虚树,则新图中点数为$O(m-n)$,且答案不变。
那么每个简单环只可能是若干非树边以及对应树上路径异或后的结果,暴力枚举所有情况然后检验即可。
时间复杂度$O(n+m+2^{m-n}(m-n)^2)$。
#include<cstdio> const int N=110000,M=20; int n,m,i,j,x,y,g[N],v[N<<1],nxt[N<<1],ed,dfn,vis[N],f[N],d[N],pre[N],w[N]; int e[M][2],cnt,ans; int id[N],vip[N],cv; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ int d=0; vis[x]=++dfn; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; if(!vis[u]){ f[u]=x; dfs(u,x); if(!id[u])continue; d++; id[x]^=id[u]; }else if(vis[u]<vis[x]){ vip[u]=vip[x]=1; e[cnt][0]=x; e[cnt][1]=u; cnt++; } } if(d>1)vip[x]=1; if(vip[x]){ id[x]=x; vip[x]=++cv; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(f[u]!=x)continue; int t=id[u]; if(t)pre[vip[t]]=vip[x]; } } } int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void merge(int x,int y){ d[x]++,d[y]++; if(F(x)!=F(y))f[f[x]]=f[y]; } inline void addcir(int x,int y){ merge(x,y); while(x!=y)w[x]^=1,x=pre[x]; } inline bool check(int S){ int i,j; for(i=1;i<=cv;i++)f[i]=i,d[i]=w[i]=0; for(i=0;i<cnt;i++)if(S>>i&1)addcir(e[i][0],e[i][1]); for(i=1;i<=cv;i++)if(pre[i]&&w[i])merge(i,pre[i]); for(i=1;i<=cv;i++)if(d[i]!=0&&d[i]!=2)return 0; for(i=1;i<=cv;i++)if(d[i])break; for(j=i+1;j<=cv;j++)if(d[j]&&F(i)!=F(j))return 0; return 1; } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=m;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); for(i=1;i<=n;i++)if(!vis[i])dfs(i,0); for(i=0;i<cnt;i++)for(j=0;j<2;j++)e[i][j]=vip[e[i][j]]; for(i=1;i<1<<cnt;i++)if(check(i))ans++; printf("%d",ans); }