【8.20校内测试】【DP】【二分+贪心】

一开始想的贪心,可是发现贪心的问题太多了啊!只能保证当前最优,全局完全无法考虑。

所以正解是dp。预处理出前缀和,枚举每个区间,在每个点记录$now[i]$表示以$i$这个塔结尾的塔组目前的高度。$dp[i]$表示以$i$这个塔结尾最多能分成多少组。如果$dp[i]$可以更新成更优值,则直接更新$dp$和$now$值,否则如果$dp$值相同,则尽量使$now$值最小。

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;

ll n, h[5005], pre[5005], now[5005], dp[5005];

int main ( ) {
    freopen ( "tower.in", "r", stdin );
    freopen ( "tower.out", "w", stdout );
    scanf ( "%I64d", &n );
    for ( int i = 1; i <= n; i ++ )
        scanf ( "%I64d", &h[i] ), pre[i] = pre[i-1] + h[i];
    for ( int i = 1; i <= n; i ++ )
        for ( int j = 0; j < i; j ++ ) {
            if ( pre[i] - pre[j] >= now[j] ) {
                if ( dp[i] < dp[j] + 1 ) dp[i] = dp[j] + 1, now[i] = pre[i] - pre[j];
                else if ( dp[i] == dp[j] + 1 ) {
                    now[i] = min ( now[i], pre[i] - pre[j] );
                }
            }
        }
    printf ( "%I64d", n - dp[n] );
    return 0;
}

显然(?)也是dp,$dp[i][j]$表示完成了$i$天,此时能力值为$j$时能做的最多工作量。$fine[i]$是预处理出的能力值为$i$时的效率最高的工作需要的时间。$qwq[i][j]$表示每个上完课后的第1天$i$,能力值为$j$,上课最短的时间。$pre[i]$表示过了$i$天可以得到的最大$dp$值。

枚举天数,能力值。

每次更新时可以选择不作为,即$dp[i][j]$直接从$dp[i-1][j]$转移过来。

如果第$i$天是某次刚上完课的第二天,则可以选择上课,从上课开始之前的最优$dp$值转移过来,即上课开始那一天的$pre$值。

如果当前天数和$fine[j]$满足可以工作,则从不工作那天转移过来,可以选择工作一次。

【注意】$dp$值初始化是负无穷,保证如果第$i$天不能满足$j$的能力值,则一定不会被更新,或去更新$pre$值。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
using namespace std;

int T, S, N, fine[10005], qwq[10005][105], pre[10005], dp[10005][105];

struct LRN {
    int m, l, a;
} stdy[105];

int main ( ) {
    freopen ( "wrk.in", "r", stdin );
    freopen ( "wrk.out", "w", stdout );
    scanf ( "%d%d%d", &T, &S, &N );
    memset ( qwq, 0x3f3f3f3f, sizeof ( qwq ) );
    for ( RG int i = 1; i <= S; i ++ ) {
        int m, l, a;
        scanf ( "%d%d%d", &m, &l, &a );
        qwq[l+m][a] = min ( qwq[l+m][a], l );
    }
    memset ( fine, 0x3f3f3f3f, sizeof ( fine ) );
    for ( RG int i = 1; i <= N; i ++ ) {
        int c, d;
        scanf ( "%d%d", &c, &d );
        fine[c] = min ( fine[c], d );
    }
    for ( int i = 1; i <= 100; i ++ )
        fine[i] = min ( fine[i], fine[i-1] );
    memset ( dp, -0x3f3f3f3f, sizeof ( dp ) );
    dp[0][1] = 0;
    for ( int i = 1; i <= T; i ++ ) {    
        for ( int j = 1; j <= 100; j ++ ) {
            dp[i][j] = dp[i-1][j];
            if ( qwq[i][j] != 0x3f3f3f3f ) dp[i][j] = max ( dp[i][j], pre[i-qwq[i][j]] );
            if ( i - fine[j] >= 0 ) dp[i][j] = max ( dp[i][j], dp[i-fine[j]][j] + 1 );
            pre[i] = max ( dp[i][j], pre[i] );
        }
    }
    printf ( "%d", pre[T] );
    return 0;
}

很有趣的一道题。题目给出的性质是只有一个点的度数大于等于3,先找出这个点作为root。

bfs预处理出点距离的邻接矩阵,二分答案,每次去check的时候是选择root周围可以覆盖它的点,割掉这个点去更新答案。设当前枚举的点是$x$,先标记$x$可以覆盖的所有点,再dfs去计算没有被标记的点的数量,就是被拆分开的一条条链上的点的数量,每条链上需要的树洞是$ceil ( \frac{1.0 * now}{2*len+1} )$,$now$是这条链上的点数,注意因为自己也会算进去,所以一个点能覆盖的点数是$2*len+1$。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;

int n, m, k, root;
int dis[2005][2005], d[2005];

int stot, tov[4000004], nex[4000005], h[2005];
void add ( int u, int v ) {
    tov[++stot] = v;
    nex[stot] = h[u];
    h[u] = stot;
}

queue < int > q;
void bfs ( int s ) {
    queue < int > q; q.push ( s );
    while ( !q.empty ( ) ) {
        int x = q.front ( ); q.pop ( );
        for ( int i = h[x]; i; i = nex[i] ) {
            int v = tov[i]; d[x] ++; d[v] ++;
            if ( !dis[s][v] && v != s )
                dis[s][v] = dis[s][x] + 1, q.push ( v );
        }
    }
}

int now, v[2005];

void dfs ( int u ) {
    now += ( v[u] = 1 );
    for ( int i = h[u]; i; i = nex[i] )
        if ( !v[tov[i]] )
            dfs ( tov[i] );
}

int work ( int x, int len ) {
    int s = 0;
    memset ( v, 0, sizeof ( v ) );
    for ( int i = 1; i <= n; i ++ )
        v[i] = ( dis[i][x] <= len );
    for ( int i = 1; i <= n; i ++ )
        if ( !v[i] ) now = 0, dfs ( i ), s += ceil ( 1.0 * now / ( 2 * len + 1 ) );
    return s;
}

bool check ( int x ) {
    int ans = 0x3f3f3f3f;
    for ( int i = 1; i <= n; i ++ )
        if ( dis[root][i] <= x ) {
            ans = min ( ans, work ( i, x ) );
        }
    return ans < k;
}

int main ( ) {
    freopen ( "holes.in", "r", stdin );
    freopen ( "holes.out", "w", stdout );
    scanf ( "%d%d%d", &n, &m, &k );
    for ( int i = 1; i <= m; i ++ ) {
        int x, y;
        scanf ( "%d%d", &x, &y );
        add ( x, y ); add ( y, x );
    }
    for ( int i = 1; i <= n; i ++ ) {
        bfs ( i );
        if ( d[i] > 2 ) root = i;
    }
    if ( !root ) {
        cout << ceil ( 1.0 * ( n - k ) / ( k << 1 ) );
        return 0;
    }
    int l = 0, r = n, ans;
    while ( l <= r ) {
        int mid = ( l + r ) >> 1;
        if ( check ( mid ) ) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    cout << ans;
    return 0;
}

猜你喜欢

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