2017 ACM Jordanian Collegiate Programming Contest
A. Chrome Tabs
当$n=1$时答案为$0$,当$k=1$或$k=n$时答案为$1$,否则答案为$2$。
#include<cstdio> int T,n,k; int main(){ freopen("tabs.in","r",stdin); scanf("%d",&T); while(T--){ scanf("%d%d",&n,&k); if(n==1)puts("0"); else if(k==1||k==n)puts("1"); else puts("2"); } }
B. OverCode
按题意模拟即可。
#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() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #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 = 0, 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 a[505]; int main() { freopen("overcode.in","r",stdin); scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { scanf("%d", &n); for(int i = 1; i <= n; ++i)scanf("%d", &a[i]); sort(a + 1, a + n + 1); int p = 1; int ans = 0; while(p <= n - 5) { if(a[p + 5] - a[p] <= 1000) { ++ans; p += 6; } else p += 1; } printf("%d\n", ans); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
C. A message for you!
按题意模拟即可。
#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() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #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 = 0, 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; char s[20]; int a[20]; bool e[20]; int main() { freopen("scoreboard.in","r",stdin); scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { scanf("%d", &n); scanf("%s", s); MS(e, 0); for(int i = 0; i < n; i ++){ e[s[i] - 'A' + 1] = 1; } for(int i = 1; i <= 13; ++i)scanf("%d", &a[i]); int ans = 0, tmp = 0; for(int i = 1; i <= 13; i ++){ if(e[i] == 0 && a[i] > tmp){ tmp = a[i]; ans = i; } } printf("%c\n", ans + 'A' - 1); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
D. Test Cases
枚举左端点,往右枚举右端点,同时维护每个数字出现次数的奇偶性。
时间复杂度$O(n^2)$。
#include<cstdio> int T,n,i,j,odd,ans,v[1000010],a[5555]; int main(){ freopen("cases.in","r",stdin); scanf("%d",&T); while(T--){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]); ans=0; for(i=1;i<=n;i++){ for(j=i;j<=n;j++)v[a[j]]=0; odd=0; for(j=i;j<=n;j++){ v[a[j]]?odd--:odd++; v[a[j]]^=1; if(odd==1)ans++; } } printf("%d\n",ans); } }
E. Robot I - Instruction Reduction
按题意模拟即可。
#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() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #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 = 505, 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, m, q; int y, x; char dir; char s[N][N]; int f[N][N][4]; //real way const int dy[4] = {-1,0,1,0}; const int dx[4] = {0,1,0,-1}; int a[1010 * 1010]; int Y, X, D; pair<int,int> ord[1010]; int main() { freopen("reduce.in","r",stdin); scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { char dir; scanf("%d%d%d", &n, &m, &q); scanf("%d%d %c", &Y, &X, &dir); if(dir == 'U')D = 0; else if(dir == 'R')D = 1; else if(dir == 'D')D = 2; else D = 3; int sty = Y, stx = X, stdir = D; for(int i = 1; i <= n; ++i) { scanf("%s", s[i] + 1); } for(int i = 2; i < n; ++i) { for(int j = 2; j < m; ++j)if(s[i][j] == '.') { for(int d = 0; d < 4; ++d) { for(int k = 0; k < 4; ++k) { int dd = (d + k) % 4; if(s[i + dy[dd]][j + dx[dd]] == '.') { f[i][j][d] = dd; break; } } } } } //printf("f:%d\n", f[3][2][1]); int g = 0; for(int i = 1; i <= q; ++i) { char op; scanf(" %c", &op); if(op == 'R') { D = (D + 1) % 4; } else { int step; scanf("%d", &step); while(step--) { D = f[Y][X][D]; Y += dy[D]; X += dx[D]; a[++g] = D; } } } Y = sty, X = stx, D = stdir; //Y X D int o = 0; for(int i = 1; i <= g; ++i) { /* printf("step = %d, status = %d %d %d, aimdir = %d\n", i, Y, X, D, a[i]); printf("f[%d][%d][%d] = %d, a[i] = %d\n", Y, X, D, f[Y][X][D], a[i]); getchar(); */ for(int k = 0; k < 4; ++k) { if(f[Y][X][D] == a[i]) { D = a[i]; Y += dy[D]; X += dx[D]; if(o && ord[o].first == 1) { ++ord[o].second; } else ord[++o] = {1, 1}; break; } else { ord[++o] = {2, -99999999}; D = (D + 1) % 4; } } } printf("%d\n", o); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
F. Robot II - Colorful Grid
留坑。
G. WiFi Password
枚举区间$OR$本质不同的$O(n\log w)$段区间,更新答案即可。
#include<cstdio> #include<algorithm> using namespace std; const int N=100010; int T,n,lim,i,j,v[N],a[N],l[N],ans; inline int fun(int a,int b){return a|b;} int main(){ freopen("wifi.in","r",stdin); scanf("%d",&T); while(T--){ scanf("%d%d",&n,&lim); ans=0; for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=n;i++)for(v[i]=a[i],j=l[i]=i;j;j=l[j]-1){ v[j]=fun(v[j],a[i]); while(l[j]>1&&fun(a[i],v[l[j]-1])==fun(a[i],v[j]))l[j]=l[l[j]-1]; if(v[j]<=lim)ans=max(ans,i-l[j]+1); } printf("%d\n",ans); } }
H. Gas Stations
枚举去掉哪一个加油站,那么最优策略要么是在一开始插入,要么是在末尾插入,要么是将间隔最大的空段劈成两半,维护前$4$大即可快速询问。
时间复杂度$O(n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=100010; int bestside[N]; int T,n,m,ans,i,j,q[N]; int pre[N],nxt[N]; char a[N]; int w[N],cnt; int now[N],tmp; inline bool cmp(int x,int y){return x>y;} inline int cal(int l,int r){ int dis=r-l-1; if(l==0||r==n+1)return dis; return (dis+1)/2; } inline int calleft(int mark){ for(int i=2;i<=m;i++)if(mark!=q[i])return q[i]-1; } inline int calright(int mark){ for(int i=m-1;i;i--)if(mark!=q[i])return n-q[i]; } inline void gao(int l,int r){ if(l==0||r==n+1)return; w[++cnt]=r-l-1; } inline void del(int l,int r){ if(l==0||r==n+1)return; int dis=r-l-1; int i; for(i=1;i<=tmp;i++)if(now[i]==dis)break; if(i>tmp)return; for(;i<tmp;i++)now[i]=now[i+1]; tmp--; } inline void add(int l,int r){ if(l==0||r==n+1)return; int dis=r-l-1; int i; now[++tmp]=dis; sort(now+1,now+tmp+1,cmp); if(tmp>4)tmp--; } inline void work(int x){ int i=pre[x],j=nxt[x];//i x j tmp=cnt; for(int _=1;_<=cnt;_++)now[_]=w[_]; del(i,x); del(x,j); add(i,j); int left=calleft(x),right=calright(x); int maxgap=0,secgap=0; if(tmp>=1)maxgap=now[1]; if(tmp>=2)secgap=now[2]; //case 1 add one in left ans=min(ans,max(bestside[left],max(right,(maxgap+1)/2))); //case 2 add one in right ans=min(ans,max(left,max(bestside[right],(maxgap+1)/2))); //case 3 add one in maxgap ans=min(ans,max(max(left,right),max((maxgap/2+1)/2,(secgap+1)/2))); } int main(){ freopen("stations.in","r",stdin); for(i=j=1;i<N;i++){ while(j<i&&max(j-1,(i-j+1)/2)>max(j,(i-(j+1)+1)/2))j++; bestside[i]=max(j-1,(i-j+1)/2); //i=3 |___+ } scanf("%d",&T); while(T--){ scanf("%d%s",&n,a+1); ans=n; q[m=1]=0; for(i=1;i<=n;i++)if(a[i]=='+')q[++m]=i; q[++m]=n+1; if(m==3){ for(i=1;i<=n;i++)ans=min(ans,max(cal(0,i),cal(i,n+1))); printf("%d\n",ans); continue; } pre[0]=0; nxt[n+1]=0; for(i=1;i<=m;i++){ if(i>1)pre[q[i]]=q[i-1]; if(i<m)nxt[q[i]]=q[i+1]; } cnt=0; for(i=1;i<m;i++)gao(q[i],q[i+1]); sort(w+1,w+cnt+1,cmp); cnt=min(cnt,4); for(i=2;i<m;i++)work(q[i]); printf("%d\n",ans); } }
I. Counting Votes
留坑。
J. Efficiency Test
将环倍长,设$f[i][j]$表示在$i$点按$j$步长往右会跳到哪里,可以得到一棵树的结构,在树上DFS,询问时二分即可。
常数优化:将失败的父亲合并,同时剔除子树内不含询问的点。
时间复杂度$O(nk+n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=200010,M=55,E=N*M; int Case,n,m,nn,i,j,cnt,s[N]; int goal[N],ans[N]; int g[E],nxt[E],f[E],q[E],t; bool vip[E]; char a[N]; inline bool canjump(int x,int y){ if(x+y>nn)return 0; if(a[x+y]=='#')return 0; return s[x+y]-s[x-1]<=1; } inline void gao(int x){ //printf("gao %d\n",x); //for(int i=2;i<=t;i++)printf("%d %d\n",q[i],w[i]); //[2..t] int y=goal[x]; if(q[2]<y){ans[x]=-1;return;} int l=3,r=t,mid,o=2; while(l<=r){ mid=(l+r)>>1; if(q[mid]>=y)l=(o=mid)+1;else r=mid-1; } ans[x]=f[t]-f[o]; } void dfs(int x){ //printf("dfs %d\n",x); if(!vip[x])return; int A=x/(m+1); q[++t]=A; f[t]=f[t-1]; if(q[t-1]!=A)f[t]++; if(A&&A<=n&&x%(m+1)==m)gao(A); for(int i=g[x];i;i=nxt[i])dfs(i); t--; } int main(){ freopen("jumps.in","r",stdin); scanf("%d",&Case); while(Case--){ scanf("%d%d%s",&n,&m,a+1); //for(i=1;i<=n;i++)a[i]='.'; //a[1]='#'; nn=n*2; for(i=1;i<=n;i++)a[i+n]=a[i]; for(i=1;i<=nn;i++)s[i]=s[i-1]+(a[i]=='#'); //[2..m] is valid cnt=nn*(m+1)+m; for(i=0;i<=cnt;i++)g[i]=vip[i]=0; for(i=nn;i;i--)if(a[i]=='.'){ int y=0; for(j=2;j<=m;j++){ int x=i*(m+1)+j; if(canjump(i,j))y=(i+j)*(m+1)+j; f[x]=y; //printf("f[%d][%d]=%d\n",i,j,y); //nxt[x]=g[y]; //g[y]=x; } int x=i*(m+1)+m; while(!vip[x]){ vip[x]=1; x=f[x]; } } for(i=1;i<=nn;i++)if(a[i]=='.'){ for(j=2;j<=m;j++){ int x=i*(m+1)+j; if(!vip[x])continue; int y=f[x]; nxt[x]=g[y]; g[y]=x; } } for(i=j=1;i<=n;i++){ while(s[j]-s[i-1]<s[n])j++; goal[i]=j;//>=j //printf("goal[%d]=%d\n",i,j); } dfs(0); for(i=1;i<=n;i++){ if(a[i]=='#')printf("0 "); else printf("%d ",ans[i]); } puts(""); } }
K. Running Threads
考虑费用流建图:
- $S$向每条记录连边,流量$[0,1]$,费用$0$,表示一组的开始。
- 记录$i$向记录$j$连边,流量$[0,1]$,费用$0$,表示一组延续。
- 记录向线程连边,流量$[0,1]$,费用$w$,表示一组的结束。
- 每条线程向$T$连边,流量$[0,1]$,费用$0$,表示只接受最多一组记录。
- 每条记录拆点,中间连边,流量$[1,1]$,费用$0$。
那么两问分别对应这个有上下界的网络的最小/最大费用可行流。
#include<cstdio> typedef long long ll; const int N=1000,M=1000000; const ll inf=1LL<<60; int Case,n,m,i,j,a[N],b[N]; ll sum; int u[M],v[M],c[M],co[M],nxt[M],t,S,T,SS,TT,l,r,q[M]; int g[N],f[N]; bool vis[N]; ll d[N]; int in[N]; int tmp;ll ans; inline void add(int x,int y,int l,int r,int zo){ r-=l; in[x]-=l; in[y]+=l; if(!r)return; u[++t]=x;v[t]=y;c[t]=r;co[t]=zo;nxt[t]=g[x];g[x]=t; u[++t]=y;v[t]=x;c[t]=0;co[t]=-zo;nxt[t]=g[y];g[y]=t; } bool spfa(){ int x,i; for(i=1;i<=TT;i++)d[i]=inf,vis[i]=0; d[SS]=0; vis[SS]=1; l=r=M>>1; q[l]=SS; while(l<=r){ int x=q[l++]; if(x==TT)continue; for(i=g[x];i;i=nxt[i])if(c[i]&&co[i]+d[x]<d[v[i]]){ d[v[i]]=co[i]+d[x];f[v[i]]=i; if(!vis[v[i]]){ vis[v[i]]=1; if(d[v[i]]<d[q[l]])q[--l]=v[i];else q[++r]=v[i]; } } vis[x]=0; } return d[TT]<inf; } int main(){ freopen("threads.in","r",stdin); scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); sum=0; for(i=1;i<=m;i++)scanf("%d",&a[i]),sum+=a[i]; for(i=1;i<=n;i++)scanf("%d",&b[i]); S=n*2+m+1; T=S+1; SS=T+1; TT=SS+1; for(t=i=1;i<=TT;i++)g[i]=in[i]=0; add(T,S,0,N,0); for(i=1;i<=n;i++){ add(i,i+n,1,1,0); add(S,i,0,1,0); for(j=1;j<=m;j++)if(b[i]<=a[j])add(i+n,j+n*2,0,1,-b[i]); for(j=i+1;j<=n;j++)if(b[i]<b[j])add(i+n,j,0,1,0); } for(i=1;i<=m;i++)add(i+n*2,T,0,1,0); for(i=1;i<=TT;i++){ if(in[i]>0)add(SS,i,0,in[i],0); if(in[i]<0)add(i,TT,0,-in[i],0); } ans=0; while(spfa()){ for(tmp=N,i=TT;i!=SS;i=u[f[i]])if(tmp>c[f[i]])tmp=c[f[i]]; for(ans+=d[i=TT]*tmp;i!=SS;i=u[f[i]])c[f[i]]-=tmp,c[f[i]^1]+=tmp; } ans*=-1; printf("%lld ",sum-ans); S=n*2+m+1; T=S+1; SS=T+1; TT=SS+1; for(t=i=1;i<=TT;i++)g[i]=in[i]=0; add(T,S,0,N,0); for(i=1;i<=n;i++){ add(i,i+n,1,1,0); add(S,i,0,1,0); for(j=1;j<=m;j++)if(b[i]<=a[j])add(i+n,j+n*2,0,1,b[i]); for(j=i+1;j<=n;j++)if(b[i]<b[j])add(i+n,j,0,1,0); } for(i=1;i<=m;i++)add(i+n*2,T,0,1,0); for(i=1;i<=TT;i++){ if(in[i]>0)add(SS,i,0,in[i],0); if(in[i]<0)add(i,TT,0,-in[i],0); } ans=0; while(spfa()){ for(tmp=N,i=TT;i!=SS;i=u[f[i]])if(tmp>c[f[i]])tmp=c[f[i]]; for(ans+=d[i=TT]*tmp;i!=SS;i=u[f[i]])c[f[i]]-=tmp,c[f[i]^1]+=tmp; } printf("%lld\n",sum-ans); } } /* 3 6 3 8 12 7 5 3 7 3 4 9 3 3 1000000000 999999999 999999998 1 10 1000 5 3 9 5 8 9 3 6 5 8 */
L. Knights
设$f[i][a][b][c][d]$表示从上到下考虑前$i$行,最后$4$行的骑士跳跃方向分别为$a,b,c,d$的方案数。
需要根据可行性将$a$压缩到$3$,$b,c$压缩到$5$,$d$压缩到$7$才能通过。
#include<cstdio> #include<algorithm> using namespace std; const int N=550,P=1000000007; int cnt,id[3][5][5][7],p[N][4],g[N][9]; int Case,n,m,w,i,j,k,a[N],f[N][N],ans; char ch[N]; int v[N][N]; int dx[9]={0,2,2,1,1,-1,-1,-2,-2}; int dy[9]={0,-1,1,-2,2,-2,2,-1,1}; inline void up(int&a,int b){ a=a+b<P?a+b:a+b-P; } void init(){ for(int A=0;A<3;A++)for(int B=0;B<5;B++)for(int C=0;C<5;C++)for(int D=0;D<7;D++){ int now=cnt++; id[A][B][C][D]=now; p[now][0]=A; p[now][1]=B; p[now][2]=C; p[now][3]=D; } for(int A=0;A<3;A++)for(int B=0;B<5;B++)for(int C=0;C<5;C++)for(int D=0;D<7;D++){ int now=id[A][B][C][D]; for(int i=1;i<9;i++){ int nA=B,nB=C,nC=D,nD=i; if(nA>=3)nA=0; if(nB>=5)nB=0; if(nC>=5)nC=0; if(nD>=7)nD=0; g[now][i]=id[nA][nB][nC][nD]; } } } int main(){ freopen("knights.in","r",stdin); init(); scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%s",ch+1); for(j=1;j<=m;j++){ if(ch[j]=='*')a[i]=j; v[i][j]=ch[j]!='.'; } } for(i=0;i<=n;i++)for(j=0;j<cnt;j++)f[i][j]=0; f[0][0]=1; for(i=0;i<n;i++)for(j=0;j<cnt;j++)if(f[i][j]){ w=f[i][j]; int A=p[j][0],B=p[j][1],C=p[j][2],D=p[j][3]; if(A)v[i-3+dx[A]][a[i-3]+dy[A]]++; if(B)v[i-2+dx[B]][a[i-2]+dy[B]]++; if(C)v[i-1+dx[C]][a[i-1]+dy[C]]++; if(D)v[i+dx[D]][a[i]+dy[D]]++; for(k=1;k<9;k++){ int x=i+1+dx[k],y=a[i+1]+dy[k]; if(x>=1&&x<=n&&y>=1&&y<=m&&!v[x][y]) up(f[i+1][g[j][k]],w); } if(A)v[i-3+dx[A]][a[i-3]+dy[A]]--; if(B)v[i-2+dx[B]][a[i-2]+dy[B]]--; if(C)v[i-1+dx[C]][a[i-1]+dy[C]]--; if(D)v[i+dx[D]][a[i]+dy[D]]--; } ans=0; for(j=0;j<cnt;j++)up(ans,f[n][j]); printf("%d\n",ans); } }
M. Winning Cells
问题等价于两个$K-Nim$游戏,那么只要$A\bmod (K+1)\neq B\bmod (K+1)$则先手必胜。
反过来考虑计算必败态的个数,那么考虑$1$到$N$每个数模$K+1$的值,可以将这些数分成贡献不同的两组分别计算。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=100010; int T,n,k;long long ans; inline int fun(int a,int b){return a|b;} int main(){ freopen("chess.in","r",stdin); scanf("%d",&T); while(T--){ scanf("%d%d",&n,&k);k++; int m=n/k*k; int base=n/k; //[0..k-1] base //[1..n%k] base+1 ans=1LL*n*n; int A=n%k,B=k-A; ans-=1LL*base*base*B; base++; ans-=1LL*base*base*A; printf("%lld\n",ans); } }