2016-2017 ACM-ICPC, Egyptian Collegiate Programming Contest (ECPC 16) 题解
题目链接:http://codeforces.com/gym/101147
2017/8/27日训练赛,题目情况9/11,Rank 4/79。
A. The game of Osho
题意:定义一个子游戏,B,N可以从N减掉B^k要求B^k小于等于N,当N变成0,该哪个人选,哪个人就输了,给出G个这样的游戏,问最后的输赢情况。
解法:组合游戏,SG打表发现规律,然后求Nim和判断即可。
#include <bits/stdc++.h> using namespace std; typedef long long LL; //int cal(LL b, LL g){ // if(g==0) return 0; // bool h[10]; // memset(h,0,sizeof(h)); // LL bb=1; // while(bb<=g){ // int t=cal(b,g-bb); // if(t<10) h[t]=1; // if(b==1) break; // bb*=b; // } // for(int x=0;x<10; x++) // if(!h[x]) return x; //} int main() { // for(int x=1; x<=5; x++){ // for(int y=1; y<=5; y++){ // printf("%d %d %d\n", x,y,cal(x,y)); // } // } freopen("powers.in","r",stdin); int T; scanf("%d", &T); while(T--) { int n; scanf("%d", &n); int ans = 0; for(int i=0; i<n; i++){ int b,g; scanf("%d%d",&b,&g); if(b&1) ans^=g&1; else{ if(g%(b+1)<=b-1) ans^=(g%(b+1))&1; else ans^=2; } } if(ans) puts("1"); else puts("2"); } return 0; }
B. Street
题意:给了一个平面,然后给了一些矩形有靠左边界的,也有靠着右边界的,现在问要从高度为0跑到高度问n+1,最少需要多少距离,你在矩形里面和边缘走是不需要时间的,题目给的是矩形的长宽,和离底面的距离和往哪靠。
解法:讨论两个矩形的距离,这里要分3种情况,然后建图跑最短路,Floyd T了,改成Dij AC。
队友代码:
C. The Wall
留坑。
D. Popcorn
题意:求n个人选k个人的方法数。
解法:就是C(n,k)。
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL C[22][22]; int main() { freopen("popcorn.in", "r", stdin); memset(C, 0, sizeof(C)); for(int i=0; i<=20; i++){ C[i][0]=1; for(int j=1; j<=i; j++){ C[i][j] = C[i-1][j-1]+C[i-1][j]; } } int T,n,m; scanf("%d", &T); while(T--) { scanf("%d %d", &n,&m); printf("%lld\n", C[n][m]); } return 0; }
E. Jumping
题意:一个人可以从1-n的任意位置出发,每个位置有一个d[i]表示这个人在这个位置可以向左跳d[i],也可以向右调d[i],问这个人从每个位置出发最后到达n的步数,不能到达输出-1。
解法:我们直接把n点看成起点,做一个最短路或者BFS,就可以求出N点到每个点的最短路了,不能到达就是dis[i]=INF的时候。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5+2; int dis[maxn]; bool f[maxn]; vector <int> G[maxn]; int main() { freopen("jumping.in","r",stdin); int T,n; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=1; i<=n; i++) G[i].clear(); for(int i=1; i<=n; i++){ int x; scanf("%d", &x); if(i-x>=1){ G[i-x].push_back(i); } if(i+x<=n){ G[i+x].push_back(i); } } memset(dis, 0x3f, sizeof(dis)); memset(f, 0, sizeof(f)); dis[n] = 0; queue <int> q; q.push(n); f[n] = 1; while(q.size()){ int u = q.front(); q.pop(); for(int v : G[u]){ if(!f[v]){ dis[v] = dis[u]+1; f[v] = 1; q.push(v); } } } for(int i=1; i<=n; i++){ if(dis[i] > n) dis[i] = -1; printf("%d\n", dis[i]); } } return 0; }
F. Bishops Alliance
题意:给了一个n*n的棋盘(n<=100000),然后棋盘上有m个皇后,问把这些皇后放到一个对角线上,当且仅当a[i]和a[j]的横坐标之差between i and j is at least equal to pi2 + pj2 + C描述能力捉急),a[i]和a[j]才可以放到一个对角线上。问最多可以放的象棋的个数。
解法:把一条对角线的棋子取出来之后,就是判断满足这个关系的棋子的个数,可以发现这个类似于LIS的DP,dp[i] = max(dp[j])+1, 其中j<=x - p[i]*p[i] - C +1,然后求出最大值后再更新到x+p[i]*p[i]的位置就可以 了。
复杂度:O(nlogn)
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6+5; typedef long long LL; struct node{ int x,y,p; node(){} node(int x,int y,int p):x(x),y(y),p(p){} bool operator < (const node &rhs) const{ return x<rhs.x; } }a[maxn],b[maxn]; int C; namespace BIT{ int c[maxn]; void init(){ memset(c, 0, sizeof(c)); } inline int lowbit(LL x){ return x&-x; } void add(LL pos, int val){ for(LL i=pos; i<maxn; i+=lowbit(i)) c[i] = max(c[i], val); } void Clear(LL pos){ for(LL i=pos; i<maxn; i+=lowbit(i)) c[i] = 0; } int query(LL pos){ pos = min(pos, (LL)maxn-1); int ret = 0; for(LL i = pos; i>0; i-=lowbit(i)) ret = max(ret, c[i]); return ret; } } using namespace BIT; vector <int> v1[maxn*2]; vector <int> v2[maxn*2]; int cal(vector <int> &v){ int n = v.size(); if(n == 0) return 0; for(int i=0; i<n; i++) b[i] = a[v[i]]; sort(b, b+n); int ret = 0; for(int i=0; i<n; i++){ LL pos = b[i].x - (LL)b[i].p*b[i].p - C + 1; int d = query(pos)+1; ret = max(ret, d); pos = b[i].x + (LL)b[i].p*b[i].p; add(pos, d); } for(int i=0; i<n; i++){ LL pos = b[i].x + (LL)b[i].p*b[i].p; Clear(pos); } return ret; } int main() { freopen("bishops.in","r",stdin); int T; scanf("%d", &T); while(T--){ int n,m; scanf("%d %d %d", &n,&m,&C); init(); for(int i=0; i<=n*2; i++){ v1[i].clear(); v2[i].clear(); } for(int i=1; i<=m; i++){ scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].p); v1[a[i].x+a[i].y].push_back(i); v2[a[i].x-a[i].y+n].push_back(i); } int ans = 0; for(int i=0; i<=n*2; i++){ ans = max(ans, cal(v1[i])); ans = max(ans, cal(v2[i])); } printf("%d\n", ans); } return 0; }
G. The Galactic Olympics
题意:抽象出来的题意:将p个物体划分成k个非空的可辨别的(可以理解为盒子有编号)集合的方法数。
解法:这个就是典型的第二类斯特林数。
第二类Stirling数 S(p,k) S(p,k)的一个组合学解释是:将p个物体划分成k个非空的不可辨别的(可以理解为盒子没有编号)集合的方法数。 k!S(p,k)是把p个人分进k间有差别(如:被标有房号)的房间(无空房)的方法数。 S(p,k)的递推公式是:S(p,k)=k*S(p-1,k)+S(p-1,k-1) ,1<= k<=p-1 边界条件:S(p,p)=1 ,p>=0 S(p,0)=0 ,p>=1
#include <bits/stdc++.h> using namespace std; typedef long long LL; int dp[3005][3005]; const int mod = 1e9+7; int main() { freopen("galactic.in", "r", stdin); dp[1][1] = 1; for(int i=2; i<=3000; i++){ dp[i][i] = 1; for(int j=1; j<=i; j++){ dp[i][j] = ((LL)j*dp[i-1][j]+dp[i-1][j-1])%mod; } } int T; scanf("%d", &T); while(T--) { int n, k; scanf("%d %d", &n,&k); if(k > n){ puts("0"); } else{ int ans = dp[n][k]; for(int i=1; i<=k; i++) ans = (LL)ans*i%mod; ans %= mod; printf("%d\n", ans); } } return 0; }
H. Commandos
题意:给了一个3维的楼房,里面有些地方有人质,现在警察在第10层(1,1)的位置,给了这些警察可以行走的限制,问最多可以救出多少人质。
解法:就是3维的数塔问题,直接DP即可。
#include <bits/stdc++.h> using namespace std; int dp[12][12][12]; int val[12][12][12]; int main() { freopen("commandos.in", "r", stdin); int T, n; scanf("%d", &T); while(T--){ memset(dp, 0, sizeof(dp)); memset(val, 0, sizeof(val)); scanf("%d", &n); for(int i=0; i<n; i++){ int f,x,y,h; scanf("%d %d %d %d", &f,&x,&y,&h); val[f][x][y] += h; } for(int f=10; f>=1; f--){ for(int x=1; x<=10; x++){ for(int y=1; y<=10; y++){ dp[f][x][y] = max(dp[f][x][y], dp[f+1][x][y]); dp[f][x][y] = max(dp[f][x][y], dp[f][x-1][y]); dp[f][x][y] = max(dp[f][x][y], dp[f][x][y-1]); dp[f][x][y] += val[f][x][y]; } } } printf("%d\n", dp[1][10][10]); } return 0; }
I. On the way to the park
题意:平面上给了一些圆的信息,然后给了一个半径R,就是你可以确定一个大圆R,这个大圆是在X轴上移动的,问这个大圆最多包含多少小圆,求这些小圆的半径和。
解法:我们把那些不可能被包含的小圆忽略,然后让剩下的小圆和大圆作相切,可以求出在什么范围内大圆可以包含这个小圆,然后把左端点打一个-r[i]的标记右端点打一个r[i]标记,排序之后求和并且更新最大值就是答案了。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5+10; typedef long long LL; int n,m; int x[maxn],y[maxn],r[maxn]; pair <double,int> a[maxn*3]; int main() { freopen("walk.in","r",stdin); int T; scanf("%d", &T); while(T--) { scanf("%d %d", &n,&m); int cnt = 0; for(int i=1; i<=n; i++){ scanf("%d %d %d", &x[i],&y[i],&r[i]); if(y[i]<0) y[i]=-y[i]; if(y[i]+r[i]>m) continue; int d = m-r[i]; double dx=sqrt(1.0*d*d-1.0*y[i]*y[i]); a[++cnt] = make_pair(x[i]-dx, -r[i]); a[++cnt] = make_pair(x[i]+dx, r[i]); } LL sum=0,ans=0; sort(a+1,a+cnt+1); for(int i=1; i<=cnt; i++){ sum += -a[i].second; ans = max(ans, sum); } printf("%lld\n", ans); } return 0; }
J. Whistle's New Car
题意:给了n个点的图,图上每个点代表一个城市,并且拥有x[i]的值表示在这个城市可以加x[i]的油,定义城市i的吸引度为i有多少个子节点,只有在这个节点加油并且能跑到i点的节点个数,两个城市之间有边权代表路程。
解法:考虑到i点向上最远可以跑到哪个城市,在这个城市打上一个-1的标记,并且在当前点打上+1的标记,然后回溯的时候累加标记就可以 了。
#include <bits/stdc++.h> using namespace std; const int maxn = 5e5+10; typedef long long LL; vector <pair<int,int> > G[maxn]; int x[maxn]; int dep[maxn]; int stk[maxn]; int ans[maxn]; void dfs(int u, int fa, int d){ stk[d] = u; int L = -1, R = d; while(L < R){ int mid = (L+R+1)/2; if(dep[stk[mid]]>=dep[u]-x[u]) R=mid-1; else L=mid; } if(L>=0) --ans[stk[L]]; ++ans[u]; for(auto &p : G[u]){ int v = p.first; if(v == fa) continue; dep[v] = dep[u] + p.second; dfs(v, u, d+1); ans[u] += ans[v]; } } int main() { freopen("car.in", "r", stdin); int T,n; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=1; i<=n; i++){ G[i].clear(); scanf("%d", &x[i]); ans[i]=0; } for(int i=1; i<n; i++){ int a,b,c; scanf("%d%d%d", &a,&b,&c); G[a].emplace_back(b,c); G[b].emplace_back(a,c); } dfs(1,0,0); for(int i=1; i<n; i++){ printf("%d ", ans[i]-1); } printf("%d\n", ans[n]-1); } return 0; }
K. Touristic Trip
留坑。