Gym - 101480 CERC 15:部分题目题解(队内第N次训练)
-------------------题目难度较难,但挺有营养的。慢慢补。
A .ASCII Addition
pro:用一定的形式表示1到9,让你计算加法。
sol:模拟。
solved by fzl;
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; typedef long long ll; string s[11] = { "xxxxxx...xx...xx...xx...xx...xxxxxx", "....x....x....x....x....x....x....x", "xxxxx....x....xxxxxxx....x....xxxxx", "xxxxx....x....xxxxxx....x....xxxxxx", "x...xx...xx...xxxxxx....x....x....x", "xxxxxx....x....xxxxx....x....xxxxxx", "xxxxxx....x....xxxxxx...xx...xxxxxx", "xxxxx....x....x....x....x....x....x", "xxxxxx...xx...xxxxxxx...xx...xxxxxx", "xxxxxx...xx...xxxxxx....x....xxxxxx", ".......x....x..xxxxx..x....x......." }; char Map[10][1000]; int judge(int x) { string tmp; for(int i = 0; i < 7; i++) for(int j = x; j < x + 5; j++)tmp += Map[i][j]; for(int i = 0; i <= 10; i++)if(s[i] == tmp)return i; } int main() { for(int i = 0; i < 7; i++)cin >> Map[i]; int n = strlen(Map[0]); ll cnt = 0, a; for(int i = 0; i < n; i += 6) { int tmp = judge(i); if(tmp == 10){a = cnt;cnt = 0;continue;} cnt = cnt * 10 + tmp; } a = a + cnt; stringstream ss;string tmp; ss << a; ss >> tmp; for(int i = 0; i < 7; i++)for(int j = 0; j < tmp.size(); j++) { for(int k = 5 * i; k < 5 * i + 5; k++)cout<<s[tmp[j] - '0'][k]; if(j == tmp.size() - 1)cout<<endl; else cout<<"."; } return 0; }
B .Book Borders
pro:给定文本,假设每行最多放L个字母时,统计第一行的信息。
sol:模拟+二分即可。
solve by pb。
#include<bits/stdc++.h> #define ll long long using namespace std; string s[500010]; int a,b,cnt,ans,n,sum[500010]; template<class T> inline void read(T&a){ char c=getchar(); for(a=0;(c<'0'||c>'9')&&c!='-';c=getchar()); bool f=0;if(c=='-')f=1,c=getchar(); for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0'; if(f)a=-a; } int main(){ ios::sync_with_stdio(false); while(cin.peek()!='\n')cin>>s[++n],sum[n]=sum[n-1]+s[n].size(); cin>>a>>b; for(int i=a;i<=b;i++){ int las=0,mid,l,r,now,hang=0; ans=0; while(las<n){ l=las+1,r=n; while(l<=r){ mid=(l+r)>>1; if(sum[mid]-sum[las]+mid-las-1<=i)now=mid,l=mid+1; else r=mid-1; } ans+=s[las+1].size(); las=now;hang++; } printf("%d\n",ans+hang-1); } return 0; }
D .Digit Division
pro:给定一个数字串,让你切割,使得切割后的每一部分都是M的倍数。
sol:保存前面切割的方案数,dp即可。 没mod 1e9+7,wa了一发。
solve by fcy。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; const int Mod=1e9+7;; ll ans,mp; char c[maxn]; int main() { mp=1; int N,M,x=0; scanf("%d%d%s",&N,&M,c+1); rep(i,1,N){ x=x*10+c[i]-'0'; x%=M; if(x==0) ans=mp,(mp+=mp)%=Mod; else ans=0; } printf("%lld\n",ans%Mod); return 0; }
F.Frightful Formula
pro:给定N*N矩阵,给出第一行第一列的初始值 f[i][1]和f[1][i],其他位置的满足f[i][j]=a*f[i][j-1]+b*f[i-1][j]+c; 求f[N][N];
sol:如果没有c,此题直接组合数可以做,但是有c,可能要fft或者其他多项式的算法。 比较麻烦。
我们可以想办法把c去掉,不过数学渣fcy好像不会,只能参考别人的。 待定系数法很好的解决了这个问题。
参考:https://blog.csdn.net/liufengwei1/article/details/78271574
H .Hovering Hornet
pro:三维平面里,给定一个5*5*5的立方体,内部含有一个1*1*1的骰子,现在有一只蜜蜂,问它看到的点数和的期望。
sol:由于二者都是处于水平位置,所以看到上面的点的概率是一定的。 而周围4个点的概率取决于底面的面积,直接用半平面交即可。
updated by fcy。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=10010; const double eps=1e-8; struct point{ double x,y; point(){} point(double xx,double yy):x(xx),y(yy){} }; struct line{ point a;//起点 point p;//起点到终点的向量 line(){} line(point aa,point pp):a(aa),p(pp){} double angle; }; double dot(point a,point b){ return a.x*b.x+a.y*b.y;} double det(point a,point b){ return a.x*b.y-a.y*b.x;} point operator *(point A,double p){ return point(A.x*p,A.y*p);} point operator +(point A,point B){return point(A.x+B.x,A.y+B.y);} point operator -(point A,point B){return point(A.x-B.x,A.y-B.y);} double getangle(point a){ return atan2(a.y,a.x);} double getangle(line a){ return getangle(a.p);} point llintersect(line A,line B) { point C=A.a-B.a; double t=det(C,B.p)/det(B.p,A.p); return A.a+A.p*t; } point s[maxn]; line t[maxn],q[maxn]; int head,tail; bool cmp(line a,line b){ double A=getangle(a),B=getangle(b); point t=(b.a+b.p)-a.a; if(fabs(A-B)<eps) return det(a.p,t)>0.0; return A<B; } bool onright(line P,line a,line b) { point o=llintersect(a,b); point Q=o-P.a; return det(Q,P.p)>0; //如果同一直线上不能相互看到,则>=0 } point w[maxn]; int pcnt=0; double halfplaneintersect(int N,line p) { s[N+1]=s[1]; rep(i,1,N) t[i].a=s[i],t[i].p=s[i+1]-s[i]; t[++N]=p; sort(t+1,t+N+1,cmp); int tot=0; rep(i,1,N-1) { if(fabs(getangle(t[i])-getangle(t[i+1]))>eps) t[++tot]=t[i]; } t[++tot]=t[N]; head=tail=0; rep(i,1,tot){ while(tail>head+1&&onright(t[i],q[tail],q[tail-1])) tail--; while(tail>head+1&&onright(t[i],q[head+1],q[head+2])) head++; q[++tail]=t[i]; } while(tail>head+1&&onright(t[head+1],q[tail],q[tail-1])) tail--; //while(tail>head+1&&onright(t[tail],q[head+1],q[head+2])) head++;加上了会wa6,不知道为什么 pcnt=0; q[tail+1]=q[head+1]; rep(i,head+1,tail) w[++pcnt]=llintersect(q[i],q[i+1]); double res=0; rep(i,2,pcnt-1) res+=det(w[i]-w[1],w[i+1]-w[1]); return res/2; } void solve() { double ans=0,area=5.0*5*5-1*1*1; rep(i,1,4) scanf("%lf%lf",&s[i].x,&s[i].y); ans+=6.0*halfplaneintersect(4,line(point(-0.5,0.5),point(1,0)))*5/area; ans+=5.0*5*5*4/area; ans+=4.0*halfplaneintersect(4,line(point(-0.5,-0.5),point(0,1)))*5/area; ans+=3.0*halfplaneintersect(4,line(point(0.5,0.5),point(0,-1)))*5/area; ans+=1.0*halfplaneintersect(4,line(point(0.5,-0.5),point(-1,0)))*5/area; printf("%.10lf\n",ans); } int main() { solve(); return 0; }
I .Ice Igloos
pro:在500*500的二维平面上,给定N个圆。Q次询问,每次给出一个线段,问多少个圆和直线有交点。 N,Q<1e5
sol:由于平面并不大,所以我们可以暴力一点的做法。 即去找可能有交点是位置,然后看那个位置上的圆是否满足。
solved by fcy 我的做法是,枚举x坐标,然后求出这个x到直线最近的点,然后往两边找,复杂度~500*Q*常数。(比标程暴力一丢丢)
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; double G[510][510]; struct point{ double x,y; point(){} point(double xx,double yy):x(xx),y(yy){} }A,B; double det(point a,point b) {return a.x*b.y-a.y*b.x;} double dot(point a,point b) {return a.x*b.x+a.y*b.y;} double dist(point a,point b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } double ptoseg(point p) { point Ap=point(p.x-A.x,p.y-A.y); point AB=point(B.x-A.x,B.y-A.y); point pB=point(B.x-p.x,B.y-p.y); if(dot(AB,Ap)<=0) return dist(A,p); if(dot(AB,pB)<=0) return dist(B,p); return fabs(det(Ap,AB)/dist(A,B)); } int ans; void get1(int x,int y) { while(y>=1){ double ty=1.0*y; double dis=ptoseg(point(1.0*x,ty)); if(dis>1.0) break; if(G[x][y]>dis) ans++; y--; } } void get2(int x,int y) { while(y<=500){ double ty=1.0*y; double dis=ptoseg(point(1.0*x,ty)); if(dis>1.0) break; if(G[x][y]>dis) ans++; y++; } } int main() { int N,Q; scanf("%d",&N); rep(i,1,N){ int x,y; double r; scanf("%d%d%lf",&x,&y,&r); G[x][y]=r; } scanf("%d",&Q); while(Q--){ scanf("%lf%lf%lf%lf",&A.x,&A.y,&B.x,&B.y); if(A.x>B.x||(A.x==B.x&&A.y>B.y)) swap(A.x,B.x),swap(A.y,B.y); ans=0; if(A.x==B.x){ for(int i=(int)A.y;i<=(int)B.y;i++) ans+=(G[(int)A.x][i]>0); } else if(A.y==B.y){ for(int i=(int)A.x;i<=(int)B.x;i++) ans+=(G[i][(int)A.y]>0); } else { for(int i=max((int)A.x-1,1);i<=min((int)B.x+1,500);i+=1){ double y=1.0*A.y; if(i!=A.x) y=A.y+1.0*(B.y-A.y)/(B.x-A.x)*(i-A.x); int t=(int)y; get1(i,t); get2(i,t+1); } } printf("%d\n",ans); } return 0; }
J .Juice Junctions
pro:给定N点M边的无向图,保证没个点的度数不超过3。 现在求所有点对的最大流。N<3e3, M<4.5e3;
sol:开始以为是N*M的复杂度,枚举起点,然后dfs,发现由于方向不能确定,所有并不能实现。
updated by fcy。
由于度数<=3;所以我们枚举点对,然后去验证可能性(0,1,2,3)。
0:不连通; 1:连通; 2: 双连通; 3:删去任意一边任然是双连通。
0,1用并查集可以做; 2用tarjan做。 3的话,需要用点东西才行,std的做法是,枚举删去的边,然后保存所有点所在的scc,所以两个点的所有情况下scc都同,即满足,这里可以用hash做。
#include<bits/stdc++.h> #define ll unsigned long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=100010; const int seed1=131; const int seed2=1331; int Laxt[maxn],Next[maxn],To[maxn],cnt=1; int q[maxn],top,dfn[maxn],low[maxn],scc[maxn],scc_cnt,times; int fa[maxn],ans,del,hash1[maxn],hash2[maxn]; void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } int find(int x) { if(x==fa[x]) return x; return fa[x]=find(fa[x]); } void dfs(int u,int f) { q[++top]=u; dfn[u]=low[u]=++times; for(int i=Laxt[u];i;i=Next[i]){ if(i==del*2||i==del*2+1||To[i]==f) continue; if(!dfn[To[i]]) { dfs(To[i],u); low[u]=min(low[u],low[To[i]]); } else low[u]=min(low[u],dfn[To[i]]); } if(dfn[u]==low[u]){ scc_cnt++; while(1){ int x=q[top--]; scc[x]=scc_cnt; if(x==u) break; } } } int main() { int N,M,u,v; scanf("%d%d",&N,&M); rep(i,1,N) fa[i]=i; rep(i,1,M){ scanf("%d%d",&u,&v); fa[find(u)]=find(v); add(u,v); add(v,u); } rep(i,1,N) rep(j,i+1,N) if(find(i)==find(j)) ans++; rep(i,1,N) if(!dfn[i]) dfs(i,0); rep(i,1,N) rep(j,i+1,N) if(scc[i]==scc[j]) ans++; rep(i,1,M) { del=i; scc_cnt=0; times=0; rep(j,1,N) dfn[j]=scc[j]=0; rep(j,1,N) if(!dfn[j]) dfs(j,0); rep(j,1,N){ hash1[j]=hash1[j]*seed1+scc[j]; hash2[j]=hash2[j]*seed2+scc[j]; } } rep(i,1,N) rep(j,i+1,N) if(hash1[i]==hash1[j]&&hash2[i]==hash2[j]) ans++; printf("%d\n",ans); return 0; }
K .Kernel Knights
pro:给定二分图。每个点有个出度,现在让你找一种方案,选出其中一些点S,满足这些点之间没有边,而未选的点,至少有S中的点指向它,保证有解。
sol:首先,如果一个点没有入度,必选。 我们可以用topo排序得到这些点。 对于剩下的点,由于是二分图,我们选择其中一边即可。
solve by fzl&pb
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; typedef long long ll; int n,a[200010],du[200010]; queue<int>que; bool s[200010],c[200010]; void dfs(int x,int y){ if(y){ c[x]=1; if(!s[a[x]]&&!c[a[x]])dfs(a[x],0); } else{ s[x]=1; if(!s[a[x]]&&!c[a[x]])dfs(a[x],1); } } int main(){ //freopen("1.in", "r", stdin); //freopen("1.out", "w", stdout); scanf("%d",&n); for(int i=1;i<=2*n;i++){ scanf("%d",a+i); du[a[i]]++; } for(int i=1;i<=2*n;i++) if(!du[i])que.push(i); while(!que.empty()){ int x=que.front();que.pop(); //cout<<x<<" "<<c[x]<<endl; if(c[x])continue;du[a[x]]--; s[x]=1; if(!c[a[x]])c[a[x]]=1,du[a[a[x]]]--; if(!s[a[a[x]]] && !c[a[a[x]]] && !du[a[a[x]]])que.push(a[a[x]]); } for(int i=1;i<=2*n;i++) if(!s[i]&&!c[i])dfs(i,0); for(int i=1;i<=2*n;i++) if(s[i])printf("%d ",i); return 0; }