srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

yukicoder No.393 2本の竹

問題

問題概要

二本の竹がある。二本の竹から任意に注文された竹の長さを切る。多くの注文に答えられるように切るとき、最大いくつの注文に答えられるかを求める問題。

解法

まず注文された竹の長さを昇順にする。竹が短いものから注文に答えていくのは貪欲に考えれば当たりまえである。あとはdpで解けばいい。単純に考えると、dpの添え字としてn1の竹の長さとn2の竹の長さ、何個目の竹の注文かを添え字の情報として持てばよいだろう。しかし3つの添え字に持ってしまうと、1 <= n1, n2 <= 100000なので計算量はO(n1n2md)となりTLEとなってしまう。
しかし、2本の竹の長さの和と、注文の竹の長さの累積和を利用すればn1の竹の長さからn2の竹の長さもわかるので、n2を添え字の情報として持っていなくてもいいということになる。このようにすると計算量は O(n1
md)となりACとなる。
この問題を少しDAG上の探索として見てみる。 以下で考察するのは入力例
1
10 10
3
7 7 5
の時である。   図1, 図2 図1は添え字の情報を3つ持った時のDAGである。図2は添え字の情報を2つ持ったと時。dpの添え字の要素はDAGで考えると頂点の要素となるものである。頂点どうしをつなぐ辺には重みをつければよく、今回は1つ竹を切れたら、その時の値を+1すればよいので、辺の重みは単純に1である。DAGは配列で表すことができるので、DAGで状態を表すこととができれば、それは何次元かの配列で表すことができるので、dpで解くことが可能となる。グラフで考えるとすこし漸化式立てやすいかも。そして、頂点として存在しないものは-1を入れておくことで、DAGグラフにでてこないないものとして扱える。

ミス

はじめはdpの添え字が3つになってしまい、TLE解法しか思いつかなかった。2つの竹の長さの和と注文された竹の長さの累積和を求めておけばどちらか一方の竹の長さだけわかっていれば、状態を決定できることが思いつかなかった。

コード

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i,n) for(int i=0;i<(n);i++)

int dp[61][100001];//dp[何本目の竹を見ているか][n1の竹の長さ] = 何本の竹の注文に答えたか
//条件を満たすものはdpの1つ目の添え字の値とその配列の値は一致するのでbool型でもできる
int main(void){
    int d; cin >> d;
    while(d){
        int n1, n2; cin >> n1 >> n2;
        int sum = n1 + n2; 
        int max_n = max(n1, n2);
        int m; cin >> m;

        vector<int> a(m + 1);
        a[0] = 0;
        rep(i, m) cin >> a[i + 1];//1origin
        sort(a.begin(), a.end());//昇順

        vector<int> s(m + 1);
        s[0] = 0;
        rep(i, m) s[i + 1] += s[i] + a[i + 1];//累積和

        //初期化
        rep(i, 61)rep(j, 100001) dp[i][j] = -1;//-1はその値が存在しないことを示す
        dp[0][n1] = 0;//初期値

        for (int i = 0; i < m; ++i){
            for (int j = 0; j <= n1 ; ++j){
                //n1から切り出す
                if(j - a[i + 1] >= 0 && dp[i][j] != -1){//n1から竹を切り出すことが可能
                    dp[i + 1][j - a[i + 1]] = dp[i][j] + 1;
                }
                //n2から切り出す
                if(sum - j - s[i] >= a[i + 1] && dp[i][j] != -1){//n2から竹を切り出すことが可能
                    dp[i + 1][j] = dp[i][j] + 1;
                }
            }
        }
        int ans = -2;//小さい値を入れておく
        rep(i, 61) rep(j, 100001) ans = max(ans, dp[i][j]);//注文に答えた数が最大のものを探す
        printf("%d\n", ans);
        d--;
    }

}