The 2016 ACMICPC Asia Beijing Regional Contest
A. Harmonic Matrix Counter (3/19)
B. Binary Tree (1/14)
C. Asa's Chess Problem (21/65)
[ Problem ]
给出一个棋盘格,里面的格子两两分为一组,组内的格子可以交换位置,
同一组格子一定在同一行或者同一列,每个格子为白色或者黑色,
现在要求每行中黑色的格子数量在rL到rR之间,每列中黑色的格子数量在cL到cR之间,
现在要求求出最少的交换次数使得要求被满足,不能满足则输出-1
[ Solution ]
我们对于每个行建立点,每列建立点,原点向行和列连本来就有的黑格子数量,
对于每行和每列向汇点连上下界为数据给出的[L,R]的流,费用均为0,
对于每对分组,同行则从黑色格子列向白色格子列连上下界[0,1],费用为1的流,
对于同列如法炮制,如果格子颜色相同则不需要引流。
求上下界网络流即可。
[ Code ]
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int N=2010,M=N*10,P=55; namespace ULB_Min_Cost_Max_Flow{ const int INF=0x3f3f3f3f; int V,SS,TT,S,T,cnt,ans,D[N],d[N],from[N],g[N],flow; bool in[N]; struct edge{int from,to,nxt,c,v;}e[M]; void add(int u,int v,int w,int c){ e[++cnt].from=u;e[cnt].to=v; e[cnt].nxt=g[u];g[u]=cnt; e[cnt].c=c;e[cnt].v=w; } void add_edge(int u,int v,int wl,int wr,int c){ D[u]-=wl; D[v]+=wl; add(u,v,wr-wl,c);add(v,u,0,-c); } void Rebuild(){ SS=V+1; TT=V+2; for(int i=1;i<=V;i++){ if(D[i]<0)add(i,TT,-D[i],0),add(TT,i,0,0); if(D[i]>0)add(SS,i,D[i],0),add(i,SS,0,0); } } bool spfa(){ memset(d,INF,sizeof(d)); d[SS]=0; memset(from,0,sizeof(from)); queue<int> q; q.push(SS); while(!q.empty()){ int now=q.front(); q.pop(); for(int i=g[now];i;i=e[i].nxt){ if(e[i].v&&d[e[i].to]>d[now]+e[i].c){ d[e[i].to]=d[now]+e[i].c; from[e[i].to]=i; q.push(e[i].to); } } }return(d[TT]!=INF); } void mcf(){ int x=INF; for(int i=from[TT];i;i=from[e[i].from])x=min(x,e[i].v);flow+=x; for(int i=from[TT];i;i=from[e[i].from]){e[i].v-=x;e[i^1].v+=x;ans+=e[i].c*x;} } void Initialize(){ memset(g,0,sizeof(g)); memset(e,0,sizeof(e)); memset(D,0,sizeof(D)); ans=flow=0; cnt=1; V=0; S=++V; T=++V; add_edge(T,S,0,INF,0); } void Run(){ while(spfa())mcf(); for(int i=g[SS];i;i=e[i].nxt)if(e[i].v)ans=-1; } } int n,mp[P][P],sc[P],sr[P],r[P],c[P],rL[P],rR[P],cL[P],cR[P]; int main(){ using namespace ULB_Min_Cost_Max_Flow; while(~scanf("%d",&n)){ Initialize(); memset(sc,0,sizeof(sc)); memset(sr,0,sizeof(sr)); memset(c,0,sizeof(c)); memset(r,0,sizeof(r)); for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){ scanf("%d",&mp[i][j]); if(mp[i][j])sr[i]++,sc[j]++; } for(int i=1;i<=n;i++)r[i]=++V,c[i]=++V; for(int i=1;i<=n;i++){ scanf("%d%d",&rL[i],&rR[i]); add_edge(S,r[i],sr[i],sr[i],0); add_edge(r[i],T,rL[i],rR[i],0); } for(int i=1;i<=n;i++){ scanf("%d%d",&cL[i],&cR[i]); add_edge(S,c[i],sc[i],sc[i],0); add_edge(c[i],T,cL[i],cR[i],0); } for(int i=1;i<=n*n/2;i++){ int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); if(mp[x1][y1]==mp[x2][y2])continue; if(!mp[x1][y1])swap(x1,x2),swap(y1,y2); if(x1==x2)add_edge(c[y1],c[y2],0,1,1); if(y1==y2)add_edge(r[x1],r[x2],0,1,1); }Rebuild(); Run(); printf("%d\n",ans); }return 0; }
D. What a Beautiful Lake (198/381)
[ Problem ]
给出环形的地形,求最长连续严格单调序列
[ Solution ]
倍长数组,正反做两次贪心即可。
[ Code ]
#include <cstdio> #include <algorithm> using namespace std; int n,a[300]; int main(){ while(~scanf("%d",&n),n){ for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++)a[n+i]=a[i]; int ans=0,l=0; for(int i=2;i<=n*2;i++){ if(a[i]>a[i-1])l++; else l=0; ans=max(ans,l); }l=0; for(int i=2;i<=n*2;i++){ if(a[i]<a[i-1])l++; else l=0; ans=max(ans,l); }printf("%d\n",ans); }return 0; }
E. What a Ridiculous Election (123/644)
[ Problem ]
一开始给出的数字串为12345,
操作有:
1.可以允许任意次数交换相邻的数字
2.允许有3次数字+1,如果大于等于10则对10取模
3.允许有2次数字*2,如果大于等于10则对10取模
问到每个给出的数字需要的最小操作数,如果无法到达则输出-1
[ Solution ]
我们用1000000*(*2次数)+100000*(+1次数)+数字本身作为状态做状态BFS,
在O(状态数)的时间内预处理所有的答案,之后直接输出即可。
[ Code ]
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> using namespace std; const int INF=0x3f3f3f3f; int ans[3000000],vis[3000000]; queue<int> Q; int main(){ memset(ans,INF,sizeof(ans)); ans[12345]=0; vis[12345]=1; Q.push(12345); while(Q.size()){ int x=Q.front(); Q.pop(); int d[7],t=x; for(int i=0;i<7;i++){ d[i]=t%10; t/=10; } for(int i=0;i<4;i++){ swap(d[i],d[i+1]); int y=0; for(int j=6;j>=0;j--)y=y*10+d[j]; if(!vis[y]){ vis[y]=1; ans[y]=ans[x]+1; //printf("%d %d\n",y,ans[y]); Q.push(y); }swap(d[i],d[i+1]); } if(d[5]<3){ for(int i=0;i<5;i++){ d[i]=(d[i]+1)%10; d[5]++; int y=0; for(int j=6;j>=0;j--)y=y*10+d[j]; if(!vis[y]){ vis[y]=1; ans[y]=ans[x]+1; Q.push(y); } d[5]--; d[i]=(d[i]+9)%10; } } if(d[6]<2){ for(int i=0;i<5;i++){ int t=d[i]; d[i]=(d[i]*2)%10; d[6]++; int y=0; for(int j=6;j>=0;j--)y=y*10+d[j]; if(!vis[y]){ vis[y]=1; ans[y]=ans[x]+1; Q.push(y); } d[6]--; d[i]=t; } } }int n; while(~scanf("%d",&n)){ int ANS=INF; ANS=min(ANS,ans[n]); ANS=min(ANS,ans[n+100000]); ANS=min(ANS,ans[n+200000]); ANS=min(ANS,ans[n+300000]); ANS=min(ANS,ans[n+1000000]); ANS=min(ANS,ans[n+1100000]); ANS=min(ANS,ans[n+1200000]); ANS=min(ANS,ans[n+1300000]); ANS=min(ANS,ans[n+2000000]); ANS=min(ANS,ans[n+2100000]); ANS=min(ANS,ans[n+2200000]); ANS=min(ANS,ans[n+2300000]); if(ANS==INF)puts("-1"); else printf("%d\n",ANS); }return 0; }
F. What a Simple Research (197/470)
[ Problem ]
求出每个字母在矩阵中的出现次数,按出现次数大小输出
[ Solution ]
签到题,注意不出现的字母不需要输出
[ Code ]
#include <cstdio> #include <algorithm> #include <map> using namespace std; struct data{char C;int t;}p[10]; char s[100010],ch[5]={'A','G','C','D','E'}; bool cmp(data a,data b){ if(a.t!=b.t)return a.t>b.t; return a.C<b.C; } map<char,int> M; int n,m; int main(){ while(~scanf("%d%d",&n,&m),n+m){ for(int i=0;i<5;i++)p[i].C=ch[i],p[i].t=0,M[p[i].C]=i; for(int i=0;i<n;i++){ scanf("%s",s); for(int j=0;j<m;j++)p[M[s[j]]].t++; }sort(p,p+5,cmp); int t=4; while(!p[t].t)t--; for(int i=0;i<t;i++)printf("%c %d ",p[i].C,p[i].t); printf("%c %d\n",p[t].C,p[t].t); }return 0; }
G. A Triangle Puzzle (1/40)
H. A New Ground Heating Device (13/103)
[ Problem ]
给出一个数(luan)学(qi)物(ba)理(zao)公式
求可行的最高高度,使得满足至少有S的面积能被K个或以上机器制热半径覆盖。
[ Solution ]
我们发现如果知道高度我们可以通过公式计算出每个圆的半径,
只要求出圆并,就能得到不同个数的圆能共同覆盖的总面积,
这个面积关于高度单调,因此我们二分答案,用k次圆并检验即可。
[ Code ]
#include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std; const int N=210; namespace KD_Circle_Merge{ #define sqr(x) ((x)*(x)) const double eps=1e-8; const double pi=acos(-1.0); double area[N]; int dcmp(double x){if(x<-eps)return -1; else return x>eps;} struct cp{ double x,y,r,angle; int d; cp(){} cp(double xx,double yy,double ang=0,int t=0){x=xx; y=yy; angle=ang; d=t;} void get(){scanf("%lf%lf%lf",&x,&y,&r);d=1;} }cir[N],tp[N*2]; double dis(cp a,cp b){return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));} double cross(cp p0,cp p1,cp p2){return(p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);} int CirCrossCir(cp p1,double r1,cp p2,double r2,cp &cp1,cp &cp2){ double mx=p2.x-p1.x,sx=p2.x+p1.x,mx2=mx*mx; double my=p2.y-p1.y,sy=p2.y+p1.y,my2=my*my; double sq=mx2+my2,d=-(sq-sqr(r1-r2))*(sq-sqr(r1+r2)); if(d+eps<0)return 0; if(d<eps)d=0; else d=sqrt(d); double x=mx*((r1+r2)*(r1-r2)+mx*sx)+sx*my2; double y=my*((r1+r2)*(r1-r2)+my*sy)+sy*mx2; double dx=mx*d,dy=my*d; sq*=2; cp1.x=(x-dy)/sq; cp1.y=(y+dx)/sq; cp2.x=(x+dy)/sq; cp2.y=(y-dx)/sq; if(d>eps)return 2; else return 1; } bool circmp(const cp& u,const cp& v){return dcmp(u.r-v.r)<0;} bool cmp(const cp& u,const cp& v) { if(dcmp(u.angle-v.angle))return u.angle<v.angle; return u.d>v.d; } double calc(cp cir,cp cp1,cp cp2){ double ans=(cp2.angle-cp1.angle)*sqr(cir.r)-cross(cir,cp1,cp2)+cross(cp(0,0),cp1,cp2); return ans/2; } void CirUnion(cp cir[],int n){ cp cp1,cp2; sort(cir,cir+n,circmp); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(dcmp(dis(cir[i],cir[j])+cir[i].r-cir[j].r)<=0) cir[i].d++; for(int i=0;i<n;i++){ int tn=0,cnt=0; for(int j=0;j<n;j++){ if(i==j)continue; if(CirCrossCir(cir[i],cir[i].r,cir[j],cir[j].r,cp2,cp1)<2)continue; cp1.angle=atan2(cp1.y-cir[i].y,cp1.x-cir[i].x); cp2.angle=atan2(cp2.y-cir[i].y,cp2.x-cir[i].x); cp1.d=1; tp[tn++]=cp1; cp2.d=-1; tp[tn++]=cp2; if(dcmp(cp1.angle-cp2.angle)>0)cnt++; } tp[tn++]=cp(cir[i].x-cir[i].r,cir[i].y,pi,-cnt); tp[tn++]=cp(cir[i].x-cir[i].r,cir[i].y,-pi,cnt); sort(tp,tp+tn,cmp); int p,s=cir[i].d+tp[0].d; for(int j=1;j<tn;j++){ p=s; s+=tp[j].d; area[p]+=calc(cir[i],tp[j-1],tp[j]); } } } } using namespace KD_Circle_Merge; double z[N]; int T,n,W,K,S,x[N],y[N]; double getR(double X,double Y,double h,double Z){ double l=sqrt(X*X+Y*Y+h*h); return W*1.0/l/Z; } bool check(double h){ memset(cir,0,sizeof(cir)); for(int i=0;i<n;i++){ cir[i].x=x[i]; cir[i].y=y[i]; cir[i].r=getR(x[i],y[i],h,z[i]); cir[i].d=1; }memset(area,0,sizeof(area)); CirUnion(cir,n); if(dcmp(area[K]-S)>=0)return 1; return 0; } int main(){ scanf("%d",&T); while(T--){ scanf("%d%d%d%d",&n,&W,&K,&S); for(int i=0;i<n;i++)scanf("%d%d%lf",&x[i],&y[i],&z[i]); if(check(500))puts("Oops!"); else if(!check(0))puts("No solution!"); else{ double l=0,r=500,mid,ans=-1; for(int i=1;i<100;i++){ mid=(l+r)*0.5; if(check(mid))l=mid,ans=mid; else r=mid; }printf("%.4lf\n",ans); } }return 0; }
I. A Boring Problem (36/289)
[ Problem ]
求出$Ans[n]=(a[1]+a[2]+…+a[n])^k+(a[2]+…+a[n])^k+(a[3]+…+a[n])^k$,
并顺序输出Ans[1~n]。
[ Solution ]
记$s[n]=∑_{i=1}^{n}a[i]$,
则$Ans[n]=∑_{i=0}^{n}(s[n]-s[i])^k$
我们将式子展开,得到$Ans[n]=∑_{i=0}^{k}C(k,i)s[n]^{(k-i)}*(∑_{j=0}^{n-1}(-s[j])^i)$
我们预处理$S[i][j]=s[i]^j$ 以及 $R[i][j]=∑_{t=0}^{i}S[t][j]$
那么就可以$O(nk)$计算答案了。
[ Code ]
#include <cstdio> #include <algorithm> using namespace std; const int N=100010,M=110; typedef long long LL; const LL P=1000000007LL; LL C[M][M],a[N],ans[N],s[N],S[N][M],T[N][M],R[N][M]; char ch[N]; int Cas,n,k; void up(LL &x,LL y){x+=y;if(x>=P)x-=P;if(x<0)x+=P;} int main(){ scanf("%d",&Cas); while(Cas--){ scanf("%d%d",&n,&k); for(int i=0;i<=k;i++)C[i][0]=C[i][i]=1LL; for(int i=1;i<=k;i++)for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; scanf("%s",ch+1); for(int i=1;i<=n;i++)a[i]=ch[i]-'0'; for(int i=1;i<=n;i++)s[i]=(s[i-1]+a[i])%P; // S[i][j]=s[i]^j for(int i=0;i<=n;i++)S[i][0]=1; for(int i=1;i<=n;i++)for(int j=1;j<=k;j++)S[i][j]=S[i][j-1]*s[i]%P; // R[i][j]=∑S[t][j](t<=i) for(int i=0;i<=k;i++)R[0][i]=S[0][i]; for(int i=1;i<=n;i++)for(int j=0;j<=k;j++)R[i][j]=(R[i-1][j]+S[i][j])%P; for(int i=1;i<=n;i++){ ans[i]=0; for(int p=0;p<=k;p++){ if(p%2==0)up(ans[i],C[k][p]*S[i][k-p]%P*R[i-1][p]%P); else up(ans[i],-C[k][p]*S[i][k-p]%P*R[i-1][p]%P); } } for(int i=1;i<=n;i++)printf("%lld%c",ans[i],i==n?'\n':' '); }return 0; }
J. Parrot (0/3)
K. JiLi Number (138/171)
[ Problem ]
吉利数指对于数字K,[1,K]中,数字中1的个数刚好等于K,
要求统计前$N(N<=10^100)$个数字中有多少吉利数
[ Solution ]
我们发现数字范围非常的大,因此猜想答案应该是收敛的,
打表发现最大的吉利数为1111111110,此后1的增长速度快过K的增长,
因此我们打表记录所有吉利数,直接查表即可。
[ Code ]
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; typedef long long LL; LL Table[84]={1,199981,199982,199983,199984,199985,199986,199987,199988, 199989,199990,200000,200001,1599981,1599982,1599983,1599984, 1599985,1599986,1599987,1599988,1599989,1599990,2600000,2600001, 13199998,35000000,35000001,35199981,35199982,35199983,35199984, 35199985,35199986,35199987,35199988,35199989,35199990,35200000, 35200001,117463825,500000000,500000001,500199981,500199982, 500199983,500199984,500199985,500199986,500199987,500199988, 500199989,500199990,500200000,500200001,501599981,501599982, 501599983,501599984,501599985,501599986,501599987,501599988, 501599989,501599990,502600000,502600001,513199998,535000000, 535000001,535199981,535199982,535199983,535199984,535199985, 535199986,535199987,535199988,535199989,535199990,535200000, 535200001,1111111110,100000000000000}; char s[1000]; int main(){ while(~scanf("%s",s)){ int len=strlen(s); if(len>11)puts("83 1111111110"); else{ LL t=0; for(int i=0;i<len;i++)t=t*10+s[i]-'0'; int cnt=0; while(Table[cnt]<=t)cnt++; printf("%d %lld\n",cnt,Table[cnt-1]); } }return 0; }
愿你出走半生,归来仍是少年