Southwestern Europe Regional Contest 2015 题解
题目链接:http://codeforces.com/gym/101128
题目数7/10 Rank 34/209
A:
题意:给出一张n个点的有向图表示一家有n个员工的公司的隶属图,u->v表示u是v的上司,现在老板要提拔一些人,但是规定如果一个员工被提拔,那么他的上司也要被提拔,现给出两个整数a和b表示一区间,求三个值,第一个值表示如果提拔a个人那么这n个中必须要被提拔的人数,第二个值表示如果提拔b个人那么这n个人中必须要被提拔的人数,第三个值表示就算提拔b个人也不会被提拔的人数
解法:前两个值是同种求法,假设要提拔x个人2,那么一个人是必须被提拔的意思是如果不提拔这个人,那么所有被其支配的人不会被提拔,而剩下的人数不够x,所以对原图从每个点开始dfs,所有被其支配的点打上时间戳便于统计(不然可能会重),计算出每个点的num值表示其支配的人数(包括自身),如果n-num[i] < x说明只选拔x个人时i必须要被提拔 。对于第三个值,在提拔b个人的条件下,一个人一定不会被提拔的意思是如果提拔这个人那么需要提拔他的一系列上司,而这些人的数量已经超过b了,所以对反图进行上述同样的dfs操作,得到的每个点的num值表示要提拔这个人至少要提拔的人数,如果num[i]
> b说明这个人一定不会被提拔。
#include <bits/stdc++.h> using namespace std; const int maxn = 5e5+5; vector <int> g1[maxn]; vector <int> g2[maxn]; int a, b, n, m, num1[maxn], num2[maxn]; bool vis[maxn]; int main() { while(~scanf("%d %d %d %d", &a, &b, &n, &m)){ for(int i=0; i<maxn; i++) g1[i].clear(); for(int i=0; i<maxn; i++) g2[i].clear(); for(int i=1; i<=m; i++){ int u, v; scanf("%d %d", &u,&v); u++;v++; g1[u].push_back(v); g2[v].push_back(u); } queue <int> q; for(int i=1; i<=n; i++){ memset(vis,0,sizeof(vis)); vis[i]=1; int cnt=0; q.push(i); while(!q.empty()){ int u=q.front(); q.pop(); cnt++; for(int v:g1[u]){ if(vis[v]) continue; vis[v]=1; q.push(v); } } cnt--; num1[i]=n-cnt; } for(int i=1; i<=n; i++){ memset(vis, 0, sizeof(vis)); vis[i]=1; int cnt=0; q.push(i); while(!q.empty()){ int u = q.front(); q.pop(); cnt++; for(int v : g2[u]){ if(vis[v]) continue; vis[v]=1; q.push(v); } } num2[i] = cnt; } int ans1 = 0, ans2 = 0, ans3 = 0; for(int i=1; i<=n; i++){ if(num1[i]<=a) ans1++; if(num1[i]<=b) ans2++; if(num2[i]>b) ans3++; } printf("%d\n", ans1); printf("%d\n", ans2); printf("%d\n", ans3); } return 0; }
B:留坑
C:
题意:给n件白色货物染色,先把这些货物按顺序放好,然后选定一个颜色C和一个数量F,之后把前F个颜色为C的货物染成X颜色,把剩余的颜色为C的货物染成Y颜色,X和Y是与所有出现过的颜色不用的两种不同颜色,问最少需多少颜料才能把每件货物都染上不同颜色
解法:合并果子变形,因为可以自定义顺序,倒过来看染色,每次就是把两种不同颜色的合并染成另一种颜色,代价是所需颜料之和
#include <bits/stdc++.h> using namespace std; typedef long long LL; int main() { int T, n, x; scanf("%d", &T); while(T--) { scanf("%d", &n); priority_queue<LL,vector<LL>,greater<LL>>q; for(int i=1; i<=n; i++){ scanf("%d", &x); q.push(x); } LL ans = 0; while(q.size()>1) { LL a = q.top(); q.pop(); LL b =q.top(); q.pop(); a += b; ans += a; q.push(a); } printf("%lld\n", ans); } return 0; }
D:
题意:给出两个规则的正n面体和正m面体的骰子,每次投x面的骰子面朝上的面是1,2,…,x的概率相同,均为1/x,问两个骰子的点数之和最可能是哪些数
解法:暴力for
#include <bits/stdc++.h> using namespace std; int a[50]; int main() { int n,m; while(~scanf("%d %d", &n,&m)) { int mx = 0; for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) { a[i+j]++; if(a[i+j]>mx) mx=a[i+j]; } for(int i=2; i<=40; i++){ if(a[i]==mx){ printf("%d\n", i); } } } return 0; }
E:
题意:n个小木条,一段前面有一个小箭头,给出第一个小木条的非箭头端端点横坐标以及每个小木条箭头端的坐标,现在要从下往上把这n’个木条按顺序叠放好,要求相邻两个小木条必须有一个共同端点且有交叠部分,问小木条有多少种放法
解法:dp[i][j][0]和dp[i][j][1]分别表示第i个小木条的非箭头端点在j,朝左或朝右的方案数,之后根据第i-1个小木条箭头朝向以及两个木条要有重叠进行转移即可,时间复杂度O(n^2)
这个DP转移,只需要考虑下每个木条的方向和两个木条重叠,在纸上画一下讨论一下转移即可。
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int maxn = 2222; const LL mod = (1LL<<31)-1; LL dp[maxn][maxn][2]; int n, a[maxn]; //dp[i][j][0]和dp[i][j][1]分别表示第i个小木条的非箭头端点在j,朝左或朝右的方案数 int main() { while(~scanf("%d", &n)) { for(int i=0; i<=n; i++) scanf("%d", &a[i]); memset(dp, 0, sizeof(dp)); dp[1][a[0]][1] = 1; for(int i=2; i<=n; i++){ for(int j=a[i]+1; j<=n+1; j++){ dp[i][j][0]+=dp[i-1][j][0]; if(dp[i][j][0]>=mod) dp[i][j][0]-=mod; } if(a[i-1]>a[i]){ for(int j=1; j<a[i-1]; j++){ dp[i][a[i-1]][0]+=dp[i-1][j][1]; if(dp[i][a[i-1]][0]>=mod) dp[i][a[i-1]][0]-=mod; } } for(int j=1; j<a[i]; j++){ dp[i][j][1]+=dp[i-1][j][1]; } if(a[i-1]<a[i]){ for(int j=a[i-1]+1; j<=n+1; j++){ dp[i][a[i-1]][1]+=dp[i-1][j][0]; if(dp[i][a[i-1]][1]>=mod) dp[i][a[i-1]][1]-=mod; } } } LL ans = 0; for(int i=1; i<=n+1; i++){ ans = (ans+dp[n][i][0]+dp[n][i][1])%mod; } printf("%lld\n", ans); } return 0; }
F:
题意:给出一张地图,分为高地和低地,高低地的交界线上划有红线,现在要开小车跨过每条红线,当改变小车开的地形的时候,比如从高地开往低地,就需要多耗油A单位,也可以花B的耗油量抬高地形或者降低地形,问跨越所有红线所用的最少耗油量。
解法:我们发现对于每条红线,我们需要花A去跨越地形或者花B去抬高或者降低地形,所以我们将不同地形划分为两个集合,构成二分图,红线左右块连流量为A的边,汇点连高地,流量为B,源点连低地,流量为B,那么源汇的最小割就是征服这些红线需要的最小代价,求源汇最大流即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 3010; const int maxm = 2000010; const int inf = 0x3f3f3f3f; struct G { int v, cap, next; G() {} G(int v, int cap, int next) : v(v), cap(cap), next(next) {} } E[maxm]; int p[maxn], T; int d[maxn], temp_p[maxn], qw[maxn]; //d顶点到源点的距离标号,temp_p当前狐优化,qw队列 void init() { memset(p, -1, sizeof(p)); T = 0; } void add(int u, int v, int cap) { E[T] = G(v, cap, p[u]); p[u] = T++; E[T] = G(u, 0, p[v]); p[v] = T++; } bool bfs(int st, int en, int n) { int i, u, v, head, tail; for(i = 0; i <= n; i++) d[i] = -1; head = tail = 0; d[st] = 0; qw[tail] = st; while(head <= tail) { u = qw[head++]; for(i = p[u]; i + 1; i = E[i].next) { v = E[i].v; if(d[v] == -1 && E[i].cap > 0) { d[v] = d[u] + 1; qw[++tail] = v; } } } return (d[en] != -1); } int dfs(int u, int en, int f) { if(u == en || f == 0) return f; int flow = 0, temp; for(; temp_p[u] + 1; temp_p[u] = E[temp_p[u]].next) { G& e = E[temp_p[u]]; if(d[u] + 1 == d[e.v]) { temp = dfs(e.v, en, min(f, e.cap)); if(temp > 0) { e.cap -= temp; E[temp_p[u] ^ 1].cap += temp; flow += temp; f -= temp; if(f == 0) break; } } } return flow; } int dinic(int st, int en, int n) { int i, ans = 0; while(bfs(st, en, n)) { for(i = 0; i <= n; i++) temp_p[i] = p[i]; ans += dfs(st, en, inf); } return ans; } const int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; int n, m, A, B; char mp[55][55]; bool check(int x, int y){ return (x>=0&&x<n&&y>=0&&y<m); } int main() { while(scanf("%d %d %d %d", &n,&m,&A,&B) != EOF) { for(int i=0; i<n; i++) scanf("%s", mp[i]); int s = n*m+1, t = s+1; init(); for(int i=0; i<n; i++){ for(int j=0; j<m; j++){ if(mp[i][j]=='.') add(s, i*m+j, B); else add(i*m+j, t, B); for(int k=0; k<4; k++){ int x = i+dir[k][0], y = j+dir[k][1]; if(check(x,y)){ add(i*m+j,x*m+y,A); } } } } int ans = dinic(s,t,t+1); printf("%d\n", ans); } return 0; }
G:
题意:有p叠牌,每叠牌有若干张,Alice和Bob轮流拿牌,Alice先手,每次一个人可以从任一叠牌拿走0~k张,但是要保证该叠至少剩一张牌,然后如果这叠牌最上面一张的点数不大于剩余牌数那么就从这叠牌中拿走这些牌,否则当前拿牌的人输,两人足够机智,问谁必胜
解法:SG函数。组合游戏,算出每叠牌的sg值后求异或和,非零则先手必胜,否则后手必胜,对于每叠的sg值暴力求即可,时间复杂度O(pnk)。注意一个地方,就是输入的时候是倒着输入的,所以我们求sG函数需要倒着枚举。
#include <bits/stdc++.h> using namespace std; const int maxn = 1010; int p, k, n, a[maxn], sg[maxn], flag[maxn]; int main() { while(~scanf("%d %d", &p,&k)) { int ans = 0; while(p--) { scanf("%d", &n); for(int i=1; i<=n; i++) scanf("%d", &a[i]); sg[0]=0; for(int i=1; i<=n; i++){ memset(flag,0,sizeof(flag)); for(int j=0; j<=k; j++){ if(i-j>0){ if(i-j-a[i-j]<0) continue; flag[sg[i-j-a[i-j]]] = 1; } } int j=0; while(flag[j]) j++; sg[i] = j; } ans ^= sg[n]; } if(ans) puts("Alice can win."); else puts("Bob will win."); } return 0; }
H:
题意:问区间[x,y]中有多少数的二进制表示是ABAB..AB型或者A型的,其中A是n个1,B是m个0,n,m>0
解法:直接枚举n,m和二进制表示的长度即可,然后暴力for
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL x, y; LL solve(int n, int m, int l){ LL ans = 0; for(int i=0; i<l; i++) if((i%(n+m)<n)) ans=2LL*ans+1; else ans=2LL*ans; return ans; } int main() { while(~scanf("%lld%lld", &x,&y)){ int ans=0; for(int i=1; i<=63; i++){ LL temp = solve(i, 0, i); if(temp>=x&&temp<=y) ans++; for(int j=1; j<=63; j++){ for(int k=i+j; k<=63; k++){ if(k%(i+j)==0||k%(i+j)==i){ LL temp = solve(i, j, k); if(temp >= x && temp <= y) ans++; } } } } printf("%d\n", ans); } return 0; }
I:留坑
J:给出l个大点和s个小点,问有多少小点被三个大点组成的三角形覆盖。第一行一整数l表示大点的数量,之后l行每行两个整数x,y表示该大点的横纵坐标,然后输入一整数s表示小点数量,最后s行每行两个整数x和y表示该小点的横纵坐标(3<=l<=10000,1<=s<=50000,0<=x,y<=2^30)。
解法:实际上就是对所有的红色的点求凸包,然后判断在凸包里面的(边上也算)的黑点有多少个,开始理解错题意了,导致求的是在外面多少个,wa了6发才知道。关键点在于如何判断一个点是否和凸包有交,射线法是O(n)的,所以要通过二分的方式确定位置,直接抄红书模板AC。复杂度O(nlogn)
#include <bits/stdc++.h> using namespace std; const double eps = 1e-8; int cmp(double x) { if(fabs(x) < eps) return 0; if(x > 0) return 1; return -1; } struct point { double x, y; point() {} point(double a, double b): x(a), y(b) {} friend point operator + (const point &a, const point &b) { return point(a.x + b.x, a.y + b.y); } friend point operator - (const point &a, const point &b) { return point(a.x - b.x, a.y - b.y); } friend point operator / (const point &a, const double &b) { return point(a.x / b, a.y / b); } friend bool operator == (const point &a, const point &b) { return cmp(a.x - b.x) == 0 && cmp(a.y - b.y) == 0; } }; double det(const point &a, const point &b) { return a.x * b.y - a.y * b.x; } struct polygon_convex { vector<point>P; polygon_convex(int Size = 0) { P.resize(Size); } /* void debug() { printf("%d\n", P.size()); for(int i = 0; i < P.size(); i++) { printf("%.5f %.5f\n", P[i].x, P[i].y); } printf("***********\n"); } */ }; bool cmp_less(const point &a, const point &b) { return cmp(a.x - b.x) < 0 || cmp(a.x - b.x) == 0 && cmp(a.y - b.y) < 0; } polygon_convex conver_hull(vector<point>a) { polygon_convex res(2 * a.size() + 5); sort(a.begin(), a.end(), cmp_less); a.erase(unique(a.begin(), a.end()), a.end()); int m = 0; for(int i = 0; i < a.size(); i++) { while(m > 1 && cmp(cmp(det(res.P[m - 1] - res.P[m - 2], a[i] - res.P[m - 2]))) <= 0) --m; res.P[m++] = a[i]; } int k = m; for(int i = int(a.size() - 2); i >= 0; i--) { while(m > k && cmp(det(res.P[m - 1] - res.P[m - 2], a[i] - res.P[m - 2])) <= 0) --m; res.P[m++] = a[i]; } res.P.resize(m); if(a.size() > 1) res.P.resize(m - 1); return res; } bool containOn(const polygon_convex &a, const point &b) { int n = a.P.size(); #define next(i) ((i+1)%n) int sign = 0; for(int i = 0; i < n; i++) { int x = cmp(det(a.P[i] - b, a.P[next(i)] - b)); if(x) { if(sign) { if(sign != x) return false; } else sign = x; } } return true; } int containOlogn(const polygon_convex &a, const point &b) { int n = a.P.size(); point g = (a.P[0] + a.P[n / 3] + a.P[2 * n / 3]) / 3.0; int l = 0, r = n; while(l + 1 < r) { int mid = (l + r) / 2; if(cmp(det(a.P[l] - g, a.P[mid] - g)) > 0) { if(cmp(det(a.P[l] - g, b - g)) >= 0 && cmp(det(a.P[mid] - g, b - g)) < 0) r = mid; else l = mid; } else { if(cmp(det(a.P[l] - g, b - g)) < 0 && cmp(det(a.P[mid] - g, b - g)) >= 0) l = mid; else r = mid; } } r %= n; int z = cmp(det(a.P[r] - b, a.P[l] - b)) - 1; if(z == -2) return 1; return z; } vector<point>v; int main() { int n, m; while(scanf("%d", &n) != EOF) { v.clear(); for(int i = 0; i < n; i++) { double x, y; scanf("%lf%lf", &x, &y); v.push_back(point(x, y)); } polygon_convex ans; ans = conver_hull(v); scanf("%d", &m); int sum = 0; for(int i = 0; i < m; i++) { point temp; scanf("%lf%lf", &temp.x, &temp.y); if(containOlogn(ans, temp)) sum++; } printf("%d\n", sum); } return 0; }