ABC136 C問題 Build Stairs
問題文
左右一列に 個のマスが並んでおり、左から 番目のマスの高さは です。
あなたは各マスについて 度ずつ次のいずれかの操作を行います。
- マスの高さを 低くする。
- 何もしない。
操作をうまく行うことでマスの高さを左から右に向かって単調非減少にできるか求めてください。
制約
- 入力は全て整数である。
解法の方針
- 左から順に見ていって、減らせるものは1減らす
ここで、減らせるもの= 左の数字より大きいもの
つまり、一番左の数字は必ず減らせる
- 操作が終わったら、条件を満たしているか確認する
注意点
最初、次のように解こうとしたが、testcase_03、04でWAが出てしまった
誤った手順
- 左の数-右の数=1のとき、左の数を1減らす
- 操作が終わったら、条件を満たしているか確認する
これだと、(1,2,2,2,1)のようなものが来たとき、Noを出力してしまう
これは、左側だけ見れば成り立っているが、右から2番目の2に来た時に減らしても、それまでの2が減っておらず、合わなくなるためである
減らせるものは減らすという貪欲法チックな思考が必要
解答例
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i, n) for (int i = 0; i < (n); ++i) int main(){ int n; cin>>n; if(n==1){ cout<<"Yes"<<endl; return 0; } vector<int> vec(n); rep(i,n) cin>>vec[i]; vec[0]-=1; rep(i,n-1){ if(vec[i]<vec[i+1]){ vec[i+1]-=1; } } rep(i,n-1){ if(vec[i]>vec[i+1]){ cout<<"No"<<endl; return 0; } } cout<<"Yes"<<endl; return 0; }
学んだこと
貪欲法的考え方は常に持っておくべき
後ろから見ると考えやすいかも
ABC137 D問題 Summer Vacation
問題文
件の日雇いアルバイトがあり、 件目の日雇いアルバイトを請けて働くと、その 日後に報酬 が得られます。
あなたは、これらの中から 日に 件まで選んで請け、働くことができます。
ただし、請けたことのある日雇いアルバイトは選べません。
今日から 日後まで( 日後を含む)に得られる報酬の合計の最大値を求めてください。
なお、日雇いアルバイトは今日から請けて働くことができます。
制約
- 入力は全て整数である。
解法の方針
- 最終日から数えていき、できるようになった仕事を集合に追加する
- 集合にはprioriuty_queueを用いる
注意点
- 最終日から遡る必要がある
解答例
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i, n) for (int i = 0; i < (n); ++i) int main(){ int n,m; cin>>n>>m; vector<vector<int>> jobs(m); //何日目に解禁される仕事かを保持するvector rep(i,n){ int a,b; cin>>a>>b; if(a>m) continue; jobs[m-a].push_back(b); //m-a日目に解禁される要素として追加 比較はbでするのでbだけ追加 } priority_queue<int> q; ll ans=0; for(int i=m-1;i>=0;--i){ //m-1日目から遡ってvectorの要素をpriority_queueに移す for(int b : jobs[i]){ q.push(b); } if(!q.empty()){ //空でtop()すると壊れる ans += q.top(); //ansに収入を足す q.pop(); //やった仕事は消す } } cout<<ans<<endl; }
学んだこと
- 逆順に見ていく
- 貪欲法
- priority_queue
.push()で要素を追加し、.top()で取り出すと、大きいものから順に出てくるコンテナ
知らなかったのでいちいちソートするのか?と思っていたが、これを使うとO(logN)でできるらしい
ABC137 C問題
問題文
文字列 に含まれる文字を何らかの順序で並べることで得られる文字列を の アナグラム と呼びます。
例えば、greenbin
は beginner
のアナグラムです。このように、同じ文字が複数回現れるときはその文字をちょうどその回数だけ使わなければなりません。
個の文字列 が与えられます。それぞれの文字列は長さが で英小文字からなり、またこれらの文字列はすべて異なります。二つの整数 の組であって、 が のアナグラムであるようなものの個数を求めてください。
制約
- は長さ の文字列である。
- の各文字は英小文字である。
- はすべて異なる。
解法の方針
- 入力文字列をsort
- mapを用いて同じものを数える
- それぞれの(count)(count-1)/2を計算する
- 10^5同士をかけるところでllにしないとオーバーフローする
解法1(map)
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i, n) for (int i = 0; i < (n); ++i) int main() { int n; cin >> n; map<string, int> mp; rep(i, n) { string s; cin >> s; sort(s.begin(), s.end()); mp[s]++; //ない要素が来たら0初期化 } ll ans = 0; for (auto& p : mp) { int s = p.second; ans += (ll)s*(s - 1) / 2; } cout << ans << endl;
解法2(vector)
文字列を入れたvectorをsortして隣同士が一致するかどうか見る
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i, n) for (int i = 0; i < (n); ++i) int main(){ int n; cin>>n; vector<string> vec; rep(i,n){ string s; cin>>s; sort(s.begin(),s.end()); vec.push_back(s); } sort(vec.begin(),vec.end()); ll count=1; ll ans=0; rep(i,n-1){ if(vec[i]==vec[i+1]){ count++; }else{ ans+=count*(count-1)/2; count=1; } } ans+=count*(count-1)/2; cout<<ans<<endl; }
学んだもの
map-連想配列
1つ目の要素をキーにした配列
持っていないキーへの操作が来たら0初期化して追加してくれる
unordered-map
順番を気にしない代わりに速いmap
今回はこれでも通る
「組み合わせの数」の数え方
総数を数え上げ、(総数)(総数-1)/2を計算する
範囲for文
for(代入される変数 : 配列名)で書ける
続く{}内に代入される変数を書くことで、指定した配列の要素が順番に代入される
代入される変数の宣言規則は以下(下のURLより)
変数宣言 | e を変更可能か? | コンテナ内の要素を変更可能か? |
---|---|---|
const auto& e | No | No |
auto& e | Yes | Yes |
auto e | Yes | No |