一场摸你赛
2018年7月22日
(请选手务必仔细阅读本页内容)
一.题目概况
中文题目名称 |
开会 |
围墙 |
搬砖 |
英文题目名称 |
CP |
gfw |
bricks |
可执行文件名 |
CP |
gfw |
bricks |
输入文件名 |
CP.in |
gfw.in |
bricks.in |
输出文件名 |
CP.out |
gfw.out |
bricks.out |
每个测试点时限 |
1秒 |
1秒 |
1秒 |
测试点数目 |
10 |
10 |
10 |
每个测试点分值 |
10 |
10 |
10 |
题目类型 |
传统 |
传统 |
传统 |
二.提交源程序文件名
对于pascal语言 |
CP.pas |
gfw.pas |
bricks.pas |
对于C语言 |
CP.c |
gfw.c |
bricks.c |
对于C++语言 |
CP.cpp |
gfw.cpp |
bricks.cpp |
三.运行内存限制
内存上限 |
512M |
512M |
512M |
四.注意事项
1、 文件名(程序名和输入输出文件名)可能是大写的,以上面的表格为准。
2、 C/C++中函数main()的返回值类型必须是int,程序正常结束时的返回值必须是0。
开会
(CP.pas/c/cpp)
【问题描述】
开会,是对所有人时间的浪费,是对集体的谋杀。
山区学校的一些学生之间的关系似乎好得有点过头,以至于传出了一些(在风纪委员们看来)不好的绯闻。具体地,有n个学生,n-1条绯闻,每条绯闻的主角都是俩学生。记者们的恶趣味保证任意两个学生,可以通过若干条绯闻直接或间接地联系在一起。
于是学校打算邀请一些学生参加座谈会。
校长相信,假如邀请了某位学生x来开会,那么就能够震慑到x本人,以及和x在同一条绯闻里的学生们。
矿泉水是宝贵的,校长想知道最少需要请多少人来开会,才有可能震慑到所有同学。
【输入】
第一行是 n 表示学生数。
之后n-1行,每行俩整数x,y,表示学生x和y之间有绯闻。( x≠y,但不一定x<y )
【输出】
一行,一个整数表示最少要邀请多少人。
【输入输出样例】
CP.in |
CP.out |
5 1 3 5 2 4 3 3 5
|
2 |
【数据范围】
对于前10%的数据,n<=15
对于前30%的数据,n<=2000
对于接下来30%的数据,n<=10^5,且有俩学生需要通过n-1条绯闻才能扯上关系。
对于前100%的数据,n<=10^5,1<=x,y<=n
【样例解释】
可以选择邀请学生2&3,或者是邀请学生3&5
围墙
(gfw.pas/c/cpp)
【问题描述】
天台的围栏不知道为啥被人破坏了。
卓司教主为了他的信徒能够放心地起舞,决定用自己从中央之国买来的GFW(GirlFriends Wallpaper)来把天台围上。
天台可以看作一个周长为C的圆,教主有n张gfw。为了美观,教主在从空中考察天台之后决定,对于第i张gfw(它有着L_i的长度),要使用的话左端必须位于位置X_i(从圆上的一个定点顺时针出发走X_i所到达的位置,显然定点在哪没影响)。
为了节能环保,教主希望用最少的gfw覆盖这个圆。圆上的某个位置可以被多张gfw覆盖。
【输入格式】
第一行俩整数C和n,表示周长和gfw的数量。
接下来n行,每行俩整数,第i行的数分别表示X_i和L_i。
【输出格式】
一行一个整数,表示最少需要多少张gfw。
【输入输出样例】
gfw.in |
gfw.out |
5 3 1 2 3 3 |
2 |
【数据范围】
对于前15%的数据,保证C,n<=200
对于接下来10%的数据,保证C<=50,n<=10^5
对于接下来20%的数据,保证C<=1000,n<=1000
对于接下来35%的数据,保证C,n<=10^5,
对于100%的数据,保证C<=10^9,n<=10^5,0<=X_i<C,1<=L_i<=C
【样例解释】
选择第2,3张gfw。 第二张覆盖了[1,3],第三张覆盖了[3,5]和[0,1]
搬砖
(bricks.pas/c/cpp)
【问题描述】
燕子飞到温暖的南方去了。
分别后的十年里,小N孤身在家乡生活,迫于生计而找了份搬砖的工作(等等这什么鬼)。
共有n个工地,第i个工地和第i+1个直接相连(1<=i<n),工地1和n并不直接相连。
从工地i走到i+1或者从i+1走到i都需要耗费1点时间。
有m个运输任务,第i个要求小N把砖头从工地L_i运到R_i。
天才的小N有了个绝妙的主意,假如在工地X和Y之间搞个传送站,那么在X可以直接不需要时间地到Y(Y到X也一样)。
因为预算限制,小N最多建一个传送站。
小N想通过合理地放置传送站,使得所有运输任务所需时间的最大值最小。
【输入格式】
第一行两个整数n,m,分别表示n个工地,m个运输任务。
接下来m行,其中第i行俩整数L_i,R_i。
【输出格式】
输出一行,一个整数,表示可能的最小的 任务所需时间最大值。
【输入输出样例】
bricks.in |
bricks.out |
5 2 1 3 2 4 |
1 |
【数据范围】
对于前15%的数据,保证n,m<=200
对于接下来10%的数据,保证n<=50,m<=10^5
对于接下来10%的数据,保证n<=1000,m<=1000
对于接下来35%的数据,保证n,m<=10^5,
对于100%的数据,保证n<=10^8,m<=10^5
题解:
第一题:原题
#include<bits/stdc++.h> using namespace std; const int M = 1e5 + 10 ; int h[M], dep[M], fa[M], tot, col[M]; struct edge{int nxt, v;}G[M << 1]; void add(int u, int v){ G[++tot].v = v; G[tot].nxt = h[u]; h[u] = tot; } void dfs(int u, int f){ fa[u] = f; dep[u] = dep[f] + 1; for(int i = h[u]; i; i = G[i].nxt){ int v = G[i].v; if(v == f)continue; dfs(v, u); } } struct node{ int id, dep; bool operator < (const node &rhs) const { return dep < rhs.dep; } }; priority_queue <node> Q; int main(){ freopen("CP.in","r",stdin); freopen("CP.out","w",stdout); int n, cnt = 0; scanf("%d",&n); for(int i = 1; i < n; i++){ int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); } dfs(1, 1); for(int i = 1; i <= n; i++) Q.push((node){i, dep[i]}); while(!Q.empty()){ int u = Q.top().id; Q.pop(); if(col[u])continue; int z = fa[u]; col[z] = 1; cnt++; for(int i = h[z]; i; i = G[i].nxt){ int v = G[i].v; col[v] = 1; } } printf("%d\n", cnt); }
第二题:环上的区间覆盖;我们把环复制两倍,所以只要我们的线段覆盖了C---2*C就可以了(这里我们强制每个点都走到2*C)
首先我们先把没有用的线段删除,然后把剩下的线段复制一遍(相当于后移C步),此时线段的l, r一定是递增的;
我们统计的答案就是:未复制前的线段刚好可以跨过第一个终点C,他走的最后一个终点2*C需要的最少线段;
一般的区间覆盖,确定了起点,后面的选择一定是一定的,我们二分处理每条线段的下一条线段是哪条nxt;
然后统计每个线段跳几步到第二个终点f, 和他结束的线段; ans = min(f[ i ] - (i 又回到了i 对应的线段) )i 属于未复制的线段
#include<bits/stdc++.h> using namespace std; const int M = 1000005; int C, n, ans = 2e9; struct Point{int l, r, len, id;}p[M], q[M]; bool cmp(Point A, Point B){ if(A.l == B.l)return A.len > B.len; return A.l < B.l; } int f[M], g[M], nxt[M], cnt; int main(){ freopen("gfw.in","r",stdin); freopen("gfw.out","w",stdout); scanf("%d%d", &C, &n); for(int i = 1; i <= n; i++){ scanf("%d%d", &p[i].l, &p[i].len); p[i].r = p[i].len + p[i].l; } sort(p + 1, p + 1 + n, cmp); int lst = 1; q[++cnt] = p[1]; for(int i = 2; i <= n; i++){ if(p[i].r > p[lst].r)q[++cnt] = p[i], lst = i; } for(int i = 1; i <= cnt; i++)q[i + cnt].l = q[i].l + C, q[i + cnt].r = q[i].r + C; for(int i = 1; i <= (cnt<<1); i++){ int lf = i, rg = cnt<<1, ans; while(lf <= rg){ int mid = (lf + rg) >> 1; if(q[mid].l <= q[i].r)ans = mid, lf = mid + 1; else rg = mid - 1; } nxt[i] = ans; } for(int i = (cnt<<1); i; i--) if(q[i].r >= (C<<1))f[i] = 1, g[i] = i; else f[i] = f[nxt[i]] + 1, g[i] = g[nxt[i]]; for(int i = 1; i <= cnt; i++) if(q[i].r >= C) { ans = min(ans, f[i] - (i == g[i] - cnt)); //printf("%d %d %d %d %d %d\n", f[i], i, cnt, q[cnt].r, C, g[i]); } printf("%d\n", ans); }
第三题:二分性质明显,暴力就是去寻找一个x-y的区间;
x-y 的区间满足abs(x - L) + abs(y - R) <= mid;
我就做到这个地方卡了,然后二分乱压缩X,Y, 随机得分;
正解就是才学的线性规划,即使知道了我也不会去绝对值,上课白学了。。。
两个绝对值对应一个矩形,我们只需判段是否在这个区间有解即可;
约束条件是: L - R- mid <= x- y <= mid + L - R L + R - mid <= x + y <= mid + L + R;
如果存在上述区间即可,因为上面的式子解出来在端点一定是整数,合法
#include<bits/stdc++.h> using namespace std; #define RG register const int M = 1e5 + 10; struct sta{ int l, r, len; }p[M]; bool cmp(sta A, sta B){ if(A.len == B.len)return A.r > B.r; return A.len > B.len; } inline int ab(int a){return a >= 0 ? a : -a;} int n, m, ans = 0; bool check(int k){ int a = -2e9, c = -2e9, b = 2e9, d = 2e9; for(int i = 1; i <= m && p[i].len > k; i++){ a = max(a, p[i].l + p[i].r - k); b = min(b, p[i].l + p[i].r + k); c = max(c, p[i].l - p[i].r - k); d = min(d, p[i].l - p[i].r + k); } if(a > b || c > d)return 0; return 1; } int main(){ freopen("bricks.in","r",stdin); freopen("bricks.out","w",stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= m; i++){ scanf("%d%d", &p[i].l, &p[i].r); if(p[i].r < p[i].l)swap(p[i].l, p[i].r); p[i].len = p[i].r - p[i].l; } sort(p + 1, p + 1 + m, cmp); int lf = 0, rg = p[1].len; while(lf <= rg){ int mid = (lf + rg) >> 1; if(check(mid))rg = mid - 1, ans = mid; else lf = mid + 1; //fprintf(stderr, "%d\n",mid); } printf("%d\n", ans); }