【9.8校内测试】【贪心/树规】【环覆盖问题】【线性规划】

开会

(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
0 1

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;
}

猜你喜欢

转载自www.cnblogs.com/wans-caesar-02111007/p/9609805.html