JOI模擬予選2015-2016参加記

JOI模擬予選に参加しました。
難易度はおおむね例年の予選と同じだが、問1が若干難しく、問3が簡単な印象。
備忘録ということで、コードを貼りつけておきます。

1 けんだま

問題

玉がA個、本体がB個ある。玉を1個増やすのにC[円]かかり、本体を1個増やすのにD[円]かかる。
予算X[円]で最大いくつのけん玉を作れるか。

解法

y個のけん玉を作るとする。
このときかかる金額は、C * max(0, y - A) + D * max(0, y - B)[円]である。
したがって、これがX以下となるようなyの最大値を出力すればよい。

コード

#include <iostream>
#include <algorithm>
using namespace std;

signed main()
{
    int a, b, c, d, x;
    int y;
    
    cin >> a >> b >> c >> d >> x;
    for( y = 0; y < 20000; y++ ){
        int cost = c * max(0, y - a) + d * max(0, y - b);
        if( cost > x )
            break;
    }
    cout << y - 1 << endl;
    return 0;
}

signedはintみたいなもの。

2 回文

問題

N文字の文字列S = s[1]s[2]…s[N]が与えられる。
3つの文字列
{s[i]s[i+1]…s[i+X-1]}
{s[i+X]s[i+X+1]…s[i+X+Y-1]}
{s[i+X+Y]s[i+X+Y+1]…s[i+2*X+Y]}
が全て回文となるようなiの個数を計算せよ。

解法

愚直にシミュレーションします。
文字列{s[l]s[l+1]…s[r-1]}が回文かどうかを判定する関数を作ると実装が楽でした。

コード

#include <iostream>
#include <string>
using namespace std;

int n, x, y;
string s;

bool ok( int l, int r ){
    string t, u;
    for( int i = l; i < r; i++ )
        t += s[i];
    for( int i = r - 1; i >= l; i-- )
        u += s[i];
    if( t == u )
        return true;
    return false;
}

signed main()
{
    int i;
    
    cin >> n >> x >> y;
    cin >> s;
    
    int ans = 0;
    for( i = 0; i < s.length() - 2 * x - y + 1; i++ ){
        if( ok(i, i + x) && ok(i + x, i + x + y) && ok(i + x + y, i + 2*x + y) )
            ans++;
    }
    cout << ans << endl;
    return 0;
}

3 渦巻きに歩く問題

問題

H行W列のグリッドがある。各マスには(1, 1), (H, W)などの座標が付けられている。
私は左上のマス(1, 1)から右渦巻きに移動していき、通ったマスに色を塗ることを繰り返す。
最初は青を塗り、次は赤、その次は青、というように青と赤を交互に塗っていく。
このとき、N個の座標(r1, c1), (r2, c2), …, (rN, cN)にあるマスはそれぞれ何色で塗られていたでしょうか。
青で塗られていたマスの数, 赤で塗られていたマスの数を空白区切りで出力せよ。
(制約)
1 <= H, W <= 109
1 <= N <= 1000

解法

ラフな考え

手で試すと、市松模様になることが分かる。
したがって、マスの座標を(a, b)とするとそのマスはa + bが偶数なら青、a + bが奇数なら赤で塗られている。

厳密な考え

移動先のマスの座標を(a, b)すると、移動するごとにa + bの偶奇が入れ替わる。
塗る色も入れ替わる。最初はa + bは偶数で塗る色は青である。
したがって、マスの座標を(a, b)とするとそのマスはa + bが偶数なら青、a + bが奇数なら赤で塗られている。
というわけで、(ri + ci) % 2 == 0となるiの個数、(ri + ci) % 2 == 1となるiの個数を計算すればよい。

コード

#include <iostream>
using namespace std;

int h, w, n;
int r, c;

signed main()
{
    int i;
    cin >> h >> w >> n;
    
    int bcnt = 0;
    int rcnt = 0;
    for( i = 0; i < n; i++ ){
        cin >> r >> c;
        if( (r + c) % 2 == 0 )
            bcnt++;
        else
            rcnt++;
    }
    cout << bcnt << " " << rcnt << endl;
    return 0;
}

4 鉄道に乗る問題

問題

駅1, 2, 3, …, Nがこの順で並んでいる。
私は時刻0に駅Sにいる。いくら時間がかかってもよいので駅Tにつきたい。そのような電車の乗り方が何通りあるかを計算せよ。
ただし、乗り換え時間は無視できるものとする。電車の情報は以下の通り。
電車はM本ある。
i本目の電車は時刻A[i]に駅B[i]を発車し、駅B[i] + C[i], B[i] + 2C[i], …の順で止まる。なお、到着時刻(=発車時刻)はそれぞれA[i] + C[i], A[i] + 2C[i], …となる。
(制約)
1 <= N <= 100
1 <= M <= 100
1 <= A[i] <= 100
1 <= B[i] <= N
1 <= B[i] + C[i] <= N
(C[i]が負になる場合もある)

解法

次に乗れる電車は、現時点での(時刻, 駅, さきほど降りた電車)から分かる。
また、この3つを持っておけば、電車Xに乗って駅Yで降りた直後の(時刻, 駅, さきほど降りた電車)も分かる。
今いる駅 = Tになれば駅に行けたことが分かる。
したがって、(時刻, 駅, さきほど降りた電車の番号)を引数にした再帰関数を組むことができる。
ただし、再帰関数を愚直に組むと時間超過するので、一工夫する。
同じ(時刻, 駅, さきほど降りた電車の番号)場合の再計算が無駄なので、最初に計算した結果を配列にメモしておき、
再び同じ(時刻, 駅, さきほど降りた電車の番号)に遷移したときは、その結果を返すようにする。
つまり、(時刻, 駅, さきほど降りた電車の番号)を状態としてDPする。
遷移(X, Yを決めるところ)は複雑なので注意。

コード

#include <iostream>
#include <cmath>
using namespace std;

int n, m, s, t;
int a[100], b[100], c[100];

int dep;                //デバッグ用(探索の深さ)
int dp[201][101][100];  //dfs(jikoku, eki, ressya)の返した値

//「時刻jikokuに駅ekiにいて列車ressyaから降りた」状態から「駅tへ行く方法の数」を返す
int dfs( int jikoku, int eki, int ressya ){
    int i, j;
    int ret = 0;

    /*for( i = 0; i < dep; i++ ) cout << "・";
    cout << " 時刻" << jikoku << " 駅" << eki;
    cout << endl;*/
    
    if( ressya >= 0 && dp[jikoku][eki][ressya] != -1 )
        return dp[jikoku][eki][ressya];
        
    if( eki == t )
        ret++;  //駅tについたから1通り加算する
    
    for( i = 0; i < m; i++ ){
        //降りた直後に同じ電車には乗れない
        if( i == ressya )
            continue;
        //駅ekiに停車しない電車には乗れない
        if( (eki - b[i]) / c[i] < 0 || abs(eki - b[i]) % abs(c[i]) != 0 )
            continue;
        //駅ekiを通過してしまった電車には乗れない
        if( jikoku > a[i] + abs(b[i] - eki) )
            continue;
        //電車iに乗って駅jで降りる
        for( j = eki + c[i]; 1 <= j && j <= n; j += c[i] ){
            dep++;
            ret += dfs(a[i] + abs(b[i] - j), j, i);//次の駅からの行き方を足す
            ret %= 100000;      //逐次余りを取る
            dep--;
        }
    }
    dp[jikoku][eki][ressya] = ret;
    return ret;
}

signed main()
{
    int i;
    cin >> n >> m >> s >> t;
    for( i = 0; i < m; i++ ){
        cin >> a[i] >> b[i] >> c[i];
    }
    
    for( i = 0; i < 201; i++ ){
        for( int j = 0; j < n + 1; j++ ){
            for( int k = 0; k < m; k++ ){
                dp[i][j][k] = -1;
            }
        }
    }
                
    cout << dfs(0, s, -1) << endl;
    return 0;
}

もっときれいなコードを書きたかった。
想定解とは少し違うらしい。

5 楽しい楽しい花の水やり

問題

花1, 2, …, Nがある。0日目の花iのみずみずしさはA[i]である。花のみずみずしさは日付が変わる瞬間にB[i]減少する。
みずみずしさが0以下になった花は枯れてしまう。
私は好きな時刻に何度でも「みずやり」をすることができる。「みずやり」は次の2ステップで行われる。
1.整数iを決める
2.花i - K, i - K + 1, …, i + KのみずみずしさをC増やす。
T日目にどの花も枯れていないようにしたい。しかし「みずやり」も重労働なので「みずやり」の回数もなるべく少なくしたい。
「みずやり」の回数の最小値を計算せよ。
(制約)
1 <= N <= 5 * 10 ^ 6
1 <= T <= 10 ^ 7
1 <= K <= N / 2
1 <= A[i], B[i] <= 10 ^ 7
1 <= C <= 10 ^ 7
なお、a ^ bはaをb回かけたものである。
(補足)
A[i], B[i]は乱数で生成する。生成の仕方はコードを見て察してください。

解法

まず、全ての入力はlong long型変数で管理したほうが良い。

考察1

水やりを全く行わなかったら、花iのみずみずしさは、A[i] - B[i] * Tとなる。
花iにX[i]水がかかったら、花iのみずみずしさは、水やりをしたタイミングによらず
A[i] - B[i] * T + C * X[i]
となる。
少なくともこれが0を超えている必要がある。逆に、これが0になるような最小のX[i]をMx[i]としたとき、
花iにはMx[i]回以上水がかかれば良い。
水やりは0日目にまとめて行おう。なお、水やりの中心位置は
花1 + K, 2 + K, …のようにずらしていくのが最善である。
具体的には、
花1~1 + 2Kに「みずやり」をする。
花1にMx[1]回以上水をやったら(花1が枯れなくなったら)花2~2 + 2Kに「みずやり」をする。
花2にMx[2]回以上水をやったら(花2が枯れなくなったら)花3~3 + 2Kに「みずやり」をする。

を繰り返せばよい。
これを愚直にやるとO(NK)で答えを計算できる。

考察2

上記の「みずやり」シミュレーションを高速化したい。具体的には
・X[i]の値を求める
・X[i+1]~X[i+2K]の値をX[i]減らす
というクエリを高速に処理したい。
これは区間加算1点クエリのセグメントツリーでできる。
もしくは累積和を行えばよい。私はセグメントツリーで解いたが、累積和(いもす法)を使えば
実行が速く実装も楽である。

コード

#include <iostream>
#include <algorithm>
#define int long long   //intがlonglongに置換される
using namespace std;

int N, K, C, T;
int Sa, Xa, Ya, Za;
int Sb, Xb, Yb, Zb;
void input(){
    cin >> N >> K >> C >> T;
    cin >> Sa >> Xa >> Ya >> Za;
    cin >> Sb >> Xb >> Yb >> Zb;
}
void generate(int A[], int B[]){
    for( int i = 1; i <= N; i++ ){
        A[i] = Sa + 1;
        B[i] = Sb + 1;
        Sa = (Xa * Sa + Ya) % Za;
        Sb = (Xb * Sb + Yb) % Zb;
    }
}

int A[5000001], B[5000001];
int X[5000001]; //X[i] = 花iに水をかける回数の最小値

const int depth = 23;
class Segtree{
public:
    int data[20000000];
    void init(){
        for( int i = 0; i < 20000000; i++ ) data[i] = 0;
    }
    void add( int l, int r, int v, int a = 0, int b = (1 << depth) - 1, int ind = 0, int dep = 0 ){
        if( dep > depth ) return;
        if( b < l || a > r ) return;
        if( l <= a && b <= r ){
            data[ind] += v;
        }
        else{
            add( l, r, v, a, a + (b - a + 1) / 2 - 1, ind * 2 + 1, dep + 1 );
            add( l, r, v, a + (b - a + 1) / 2, b, ind * 2 + 2, dep + 1 );
        }
    }
    int getNum( int x ){
        int ret = 0;
        x += (1 << depth) - 1;
        ret += data[x];
        while( x > 0 ){
            x = (x - 1) / 2;
            ret += data[x];
        }
        return ret;
    }
}seg;

void generateX(){
    for( int i = 1; i <= N; i++ ){
        //A[i] - B[i] * T + C * X[i] > 0となるX[i]の最小値を求める
        X[i] = max((int)0, (B[i] * T - A[i]) / C + 1);
    }
}

void segInit(){
    seg.init();
    for( int i = 1; i <= N; i++ ){
        seg.add(i, i, X[i]);
    }
}

//花1 + Kを中心に水をかける→花2 + Kを中心に水をかける→…
//水やり回数を返す
int solve(){
    int center;
    int ans = 0;
    
    for( center = 1 + K; center <= N + K; center++ ){
        int left = center - K;
        int right = min(N, center + K);
        
        //max(0, X[left])回水やりをする
        //①max(0, X[left])を求める
        int kaisu = max((int)0, seg.getNum(left));
        
        //②答えを更新
        ans += kaisu;
        
        //③X[left]~X[right]に-max(0, X[left])足す
        seg.add(left, right, -max((int)0, kaisu));
    }
    return ans;
}

signed main()
{
    input();
    generate(A, B);
    generateX();
    segInit();
    cout << solve() << endl;
    return 0;
}

高級な道具を使ってしまった。

6 完全に忘れました

ごめんなさい

感想

JOI予選模擬にもかかわらずガチ参加できるほど良問ぞろいでした。
部外者が勝手に参加してすみませんでした。
運営陣は素晴らしい。はっきりわかんだね。

Markdown記述のテスト


問題文

[input.txt]には行数h、列数w、文字列S1,…,Shが書かれてます。
S1~Shは、#と (空白)から成るw文字の文字列です。
形式は以下の通りです。

[input.txt]
h w
S1
S2

Sh

ぬぬぬ君は[input.txt]を加工することで、0と1から成るテキストを作りたいです。
加工は以下の手順で行います。
①1行目(h wが書かれている行)を削除する
② (空白1文字)を0に、#を1に置き換える
③テキストを左90度回転する
④テキストを上下反転する

ぬぬぬ君はこの加工を手で行っていたのですが、疲れからか黒塗りの車に衝突してしまい、
手が使えなくなってしまいました。

そんなぬぬぬ君を見たあなたは、彼のテキスト加工を手伝うことにしました。

それでは、[input.txt]を加工し、加工後のテキストを[output.txt]に書き込む
プログラムを作成してください。ただし、リダイレクトを用いてはいけません。
すなわち実行形式は、
./a.out
または
~.exe
のようにします。

入力[input.txt]の制約

1 <= h, w <= 100

入出力例

例1

//input.txt
4 5
##  #
#  # 
 ## #
 # ##

//output.txt
1100
1011
0010
0101
1011
//手順1を終えたとき
##  #
#  # 
 ## #
 # ##

//手順2を終えたとき
11001
10010
01101
01011

//手順3を終えたとき
1011
0101
0010
1011
1100

//手順4を終えたとき
1100
1011
0010
0101
1011

例2

//input.txt
3 26
# ### #   # #   # ### #  #
# # # #   #  # #  ##  #  #
# ### ### #   #   ### #  #

//output.txt
111
000
111
101
111
000
111
001
001
000
111
000
100
010
001
010
100
000
111
111
101
000
111
000
000
111

JOI2012予選4「Hot Days」

解けたので、備忘録のため、コードを適当に貼ります(ひどい)。
コードの貼り方が分からないので、この記事は何回か更新されます。


#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

int d, n;
int t[200];
int a[200], b[200], c[200];
int dp[201][200];			//dp[day][fuku] = sumの最大値
int ans = -1145141919;

void makeAns(){
	int day, fuku, i;
	
	//初期化
	for( day = 0; day < d + 1; day++ ){
		for( fuku = 0; fuku < n; fuku++ )
			dp[day][fuku] = -114514;
	}
	for( fuku = 0; fuku < n; fuku++ ){
		if( a[fuku] <= t[0] && t[0] <= b[fuku] )
			dp[1][fuku] = 0;	//0日目に服fukuを着た場合
	}
	
	//dp配列の更新
	for( day = 1; day < d; day++ ){
	for( fuku = 0; fuku < n; fuku++ ){
	//day日目の服を決める(遷移)
	for( i = 0; i < n; i++ ){
		if( a[i] <= t[day] && t[day] <= b[i] ){
			//day日目に服iを着る
			dp[day + 1][i] = max(dp[day + 1][i], dp[day][fuku] + abs(c[fuku] - c[i]));
		}
	}
	}
	}
	
	//答えを計算する
	for( i = 0; i < n; i++ ){
		ans = max(ans, dp[d][i]);
	}
}

int main()
{
	int i, j;
	cin >> d >> n;
	for( i = 0; i < d; i++ ) cin >> t[i];
	for( i = 0; i < n; i++ ) cin >> a[i] >> b[i] >> c[i];
	makeAns();
	cout << ans << endl;
	return 0;
}

テスト投稿

  • 登録が簡単で驚いた
  • 公共の場なのでうかつなことを呟けない()
  • 窓を全開にした部屋と同じ
  • 初投稿 LANを飛び出す 緊張感 ネットリテラシー ネットリテラシー
既にネットリテラシーを守っていない気がする

このブログは、ソースコード置き場になる予定です。
有用性は皆無