开会
(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
乍一眼看像是没有上司的舞会,可是发现一个点不止会被儿子影响,还可能会被父亲或孙子影响。状态感觉好复杂,换个思路。
从叶子节点往上看,发现我们可以贪心取点。每层取点,如果取出来的点还没有被打上标记,就取它的父亲,并暴力更新被它父亲覆盖到的点,打上标记。这样一定能保证是最优的。
然后因为要分层,跑一边$Bfs$,顺便把每一层遍历到的点放入栈中,这样能保证取点顺序是从叶子节点往上取的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<stack> using namespace std; int n; struct Node { int v, nex; Node ( int v = 0, int nex = 0 ) : v ( v ), nex ( nex ) { } } Edge[200005]; int stot, h[100005]; void add ( int u, int v ) { Edge[++stot] = Node ( v, h[u] ); h[u] = stot; } int fa[100005]; void dfs ( int u, int f ) { fa[u] = f; for ( int i = h[u]; i; i = Edge[i].nex ) { int v = Edge[i].v; if ( v == f ) continue; dfs ( v, u ); } } queue < int > q; stack < int > stk; void Bfs ( ) { q.push ( 1 ); stk.push ( 1 ); while ( !q.empty ( ) ) { int x = q.front ( ); q.pop ( ); for ( int i = h[x]; i; i = Edge[i].nex ) { int v = Edge[i].v; if ( v == fa[x] ) continue; q.push ( v ); stk.push ( v ); } } } int vis[100005], ans; void Work ( ) { while ( !stk.empty ( ) ) { int u = stk.top ( ); stk.pop ( ); if ( !vis[u] ) { vis[fa[u]] = 1; for ( int i = h[fa[u]]; i; i = Edge[i].nex ) { int v = Edge[i].v; vis[v] = 1; } ans ++; } } } int main ( ) { freopen ( "CP.in", "r", stdin ); freopen ( "CP.out", "w", stdout ); 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, 0 ); ans = 0; Bfs ( ); Work ( ); printf ( "%d", ans ); return 0; }
围墙
(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]
一开始问题出现在不知道从哪开始从哪结束,因为是环不好处理,再加上$n$的数据范围太莽,数据结构也行不通叻。
首先发现,有一些区间可能被其他区间完全覆盖,这样的区间没有用,先筛掉。
然后我们将环展开并复制一遍,是环的常用处理方法(区间DP环也经常这样处理) 我们发现,可以把跨过分界点$n$的区间作为标准,只用找到下一次跨过分界点的最优值就可以叻。(第二次跨过的分界点就是$2n$叻)
我们可以把复制过后每段区间后缀最优区间通过二分处理出来。后缀最优区间指的是既接在当前区间后面,又使右端点延伸最远的区间。这样贪心下去一定是最优的。
比如对于$i$这段区间来说,$j1.j2.j3$都是它的后缀区间,最优的是$j2$。
处理完了后缀区间$nex$数组后,就可以统计答案叻。定义两个数组$f$和$g$,分别代表走了两次分界点的最少步数和终点区间。倒着遍历区间,两个数组都可以直接从它们的$nex$转移过来。
为什么要处理终点?如果起点是某个区间,终点也是它自己的话,它的贡献就被统计了两次,最后统计答案的时候就要减一。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; struct Node { int l, r; } bl[100005], q[200005]; bool cmp ( Node a, Node b ) { return a.l < b.l; } int n, m, nex[200005], f[200005], g[200005]; int main ( ) { freopen ( "gfw.in", "r", stdin ); freopen ( "gfw.out", "w", stdout ); scanf ( "%d%d", &n, &m ); for ( int i = 1; i <= m; i ++ ) scanf ( "%d%d", &bl[i].l, &bl[i].r ), bl[i].r += bl[i].l; sort ( bl + 1, bl + 1 + m, cmp ); int cnt = 0; for ( int i = m; i; i -- ) { while ( cnt && q[cnt].r <= bl[i].r ) cnt --; q[++cnt] = bl[i]; } reverse ( q + 1, q + 1 + cnt ); for ( int i = 1; i <= cnt; i ++ ) q[i+cnt] = q[i], q[i+cnt].l += n, q[i+cnt].r += n; for ( int i = 1; i <= ( cnt << 1 ); i ++ ) { int l = i, r = ( cnt << 1 ); while ( l < r ) { int mid = ( l + r + 1 ) >> 1; if ( q[mid].l <= q[i].r ) l = mid; else r = mid - 1; } nex[i] = l; } for ( int i = cnt * 2; i; i -- ) if ( q[i].r >= n * 2 ) f[i] = 1, g[i] = i; else f[i] = f[nex[i]] + 1, g[i] = g[nex[i]]; int ans = 1e9; for ( int i = 1; i <= cnt; i ++ ) if ( q[i].r >= n ) ans = min ( ans, f[i] - ( i == g[i] - cnt ) ); printf ( "%d\n", ans ); return 0; }
搬砖
(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
我们发现,我们要统计出的答案$ans$实际上是要使任意$X$和$Y$都满足$|L-X|+|R-Y|<=ans$恒成立,而求最大值最小很明显用二分。
将上式绝对值去掉可以发现就是一个线性规划!(刚学完全看不出来啊QAQ) 是四个关于$X-Y$和$X+Y$的式子,在坐标系上就是一个正方形,只要满足上式对于所有的搬砖计划路径成立就可以叻。
所以二分答案,在$check$里面判断上式是否可以找到满足条件的区域就可以叻。
#include<iostream> #include<cstdio> #define inf 1e9 using namespace std; int n, m; struct Node { int l, r; } qus[100005]; bool check ( int mid ) { int L1 = -inf, L2 = -inf, R1 = inf, R2 = inf; for ( int i = 1; i <= m; i ++ ) { if ( qus[i].r - qus[i].l > mid ) { L1 = max ( L1, qus[i].l + qus[i].r - mid ); L2 = max ( L2, qus[i].l - qus[i].r - mid ); R1 = min ( R1, qus[i].l + qus[i].r + mid ); R2 = min ( R2, qus[i].l - qus[i].r + mid ); } } return L1 <= R1 && L2 <= R2; } 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", &qus[i].l, &qus[i].r ); if ( qus[i].l > qus[i].r ) swap ( qus[i].l, qus[i].r ); } int l = 0, r = n; while ( l < r ) { int mid = ( l + r ) >> 1; if ( check ( mid ) ) r = mid; else l = mid + 1; } printf ( "%d", l ); return 0; }