Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PCK2017_pre_unoficial_editorial

Avatar for Luzhiled Luzhiled
November 02, 2017

 PCK2017_pre_unoficial_editorial

PCK2017予選の非公式解説です

Avatar for Luzhiled

Luzhiled

November 02, 2017
Tweet

Other Decks in Programming

Transcript

  1. 問1 お年玉 問題概要 • 2つ の 整数a、b(A君とB君のお年玉の値段)が与えられる. • 2人で分けたお年玉の金額 (a+b)/2

    を計算する. 講評・解法 • 変数宣言、四則演算、標準入出力処理が行えるかを問う最 も基本的な問題である. • 2つの整数はどちらも1000の倍数なので、必ず割り切れる. (端数処理をする必要はない.)
  2. #include <stdio.h> int main(){ int a, b; scanf("%d %d", &a,

    &b); printf("%d¥n", (a+b)/2); return 0; } 問1 Cによる解答例
  3. #include <iostream> using namespace std; int main(){ int a, b;

    cin >> a >> b; cout << (a+b)/2 << endl; return 0; } 問1 C++による解答例
  4. import java.io.*; import java.util.*; class Main{ private void compute(){ Scanner

    scanner = new Scanner(System.in); int a=scanner.nextInt(); int b=scanner.nextInt(); System.out.println((a+b)/2); } public static void main(String a[]){ new Main().compute(); } } 問1 Javaによる解答例
  5. #include <iostream> using namespace std; int main() { int m,

    f, b; cin>>m>>f>>b; if(b<=m){ cout << 0 << endl; }else if(b<=m+f){ cout << b-m << endl; }else{ cout << "NA" << endl; } } 問2 C++による解答例
  6. 問3 9月X日 問題概要 • 2017年9月X日の曜日を計算する. 講評・解法 • 解法1:剰余で求める. – X%7が0だったら木曜、1だったら金曜、・・・6だったら水曜である.

    – X%7で条件分岐をしてもよいが、配列を用いると実装が簡単. • 解法2:カレンダーを見て、9月の曜日をすべて書き写す.
  7. #include <iostream> using namespace std; string day[7]={"thu", "fri", "sat", "sun",

    "mon", "tue", "wed"}; int main() { int x; cin >> x; cout << day[x%7] << endl; } 問3 C++による解答例(解法1)
  8. 問4 エルの予約 問題概要 • スパコンを予約したい時間帯と、すでに予約されている時間 帯が与えられる. • 予約が重複しているかどうかを判定する. 講評・解法 •

    配列や基本的なループを用いたプログラムが実装できるかが問わ れている. • 解法1:使用される時間帯について、1つずつ重複を判定する. • 解法2:時刻ごとに予約されているかどうかを表す配列を用意する. – 時間が0以上1000以下、件数が100件以下より、 1000*100=10万程度の計算で間に合う. • 端点処理(開始/終了の時間を含むか?)に注意.
  9. 問4 エルの予約(解法2) • 端点処理について 2 3 4 5 6 7

    「ある点」から「別の点」までの区間を 配列で表す場合・・・ (例えば、「3から6まで」を表す区間は・・・)
  10. 問4 エルの予約(解法2) • 端点処理について 2 3 4 5 6 7

    配列の上では 「一方を含み、一方を含まない区間」に対応する (この場合は、「3から6」の区間が 配列の「 [3] 以上 [6] 未満」に対応) このような区間を半開区間といい、 プログラミングで有用 [1] [2] [3] [4] [5] [6] [7]
  11. #include <iostream> using namespace std; int main() { int a,

    b, N; cin >> a >> b >> N; int ans=0; for(int i=0; i<N; i++){ int s, f; cin >> s >> f; if(s < b && a < f) ans=1; } cout << ans << endl; } 問4 C++による解答例(解法1)
  12. #include <iostream> using namespace std; bool filled[1005]; int main() {

    int a, b, N; cin >> a >> b >> N; for(int i=0; i<1005; i++) filled[i]=false; for(int i=0; i<N; i++){ int s, f; cin >> s >> f; for(int j=s; j<f; j++) filled[j]=true; } int ans=0; for(int i=a; i<b; i++){ if(filled[i]) ans=1; } cout << ans << endl; } 問4 C++による解答例(解法2)
  13. #include <iostream> using namespace std; int gcd(int a, int b){

    if(b==0) return a; else return gcd(b, a%b); } int main() { int a, b; cin >> a >> b; cout << (a+1) + (b+1) - (gcd(a,b)+1) << endl; } 問5 C++による解答例
  14. #include <iostream> #include <algorithm> using namespace std; int d[300005]; int

    main() { int N; cin >> N; for(int i=0; i<N; i++) cin >> d[i]; for(int i=0; i<2; i++) { int a=0; for(int j=0; j<N; j++) { if(a<j*10){ cout << "no" << endl; return 0; } a=max(a, j*10+d[j]); } reverse(d, d+N); } cout << "yes" << endl; } 問6 C++による解答例
  15. 問7 積み荷の配置 問題概要 • 横4メートル、縦Hメートルの貨物室に、2メートル×2メートル の荷物を配置する. • 貨物室は1メートル四方の区画に分割され、荷物はその区画 の境界に沿って配置する. •

    貨物室のうちN個の区画は荷物が置けない. • 貨物室に荷物を置ける最大数を求める. 講評 • H≦10000、N≦40000. • 可能な配置をもれなく列挙して最大数を求める必要がある.
  16. 問7 積み荷の配置 • DP(動的計画法;Dynamic Programming)と呼ばれる手法の中でも、 Bit DP と呼ばれる技法を利用する. • dp[k][wxyz]

    := k-1段目までの配置が決まっており、k段目の埋まり 具合は wxyz であるとき、k段目以降にうまく配置したときの置ける 荷物の最大値. – wxyz は、各列の埋まっている/埋まっていないを1/0で(ビット で)表現した2進数(10進数だと0以上16未満). • dp[k][wxyz]の値はk, wxyzによってのみ決まり、不変なので、メモ化 によりこのdpを求める回数は N * 2^4 回. • 1回のdpテーブルの計算には、段の配置を5通りすべて試せばよ い. • 全体で N * 2^4 * 5 ≦ 800000 回程度の計算となり、間に合う.
  17. #include <iostream> using namespace std; int obs[10005]; int dp[10005][16]; int

    mask[5]={0, 3, 6, 12, 15}; int cnt[5] ={0, 1, 1, 1, 2}; int main() { int h, n; cin >> h >> n; fill(obs, obs+10005, 0); for(int i=0; i<n; i++){ int x, y; cin >> x >> y; obs[y]=obs[y] | (1<<x); } obs[h]=15; 問7 C++による解答例 fill(dp[h], dp[h]+16, 0); for(int i=h-1; i>=0; i--){ for(int j=0; j<16; j++){ dp[i][j]=0; for(int k=0; k<5; k++){ if((mask[k]&j)|(mask[k]&obs[i+1])) continue; dp[i][j]=max(dp[i][j], cnt[k]+dp[i+1][obs[i+1]|mask[k]]); } } } cout << dp[0][obs[0]] << endl; }
  18. 問8 ダンジョン 問題概要 • W×H の方眼があり、そのうちNマスには敵がいる. • 左上から出発した自機は、上下左右に1マスずつ移動できる. • 自機は、無制限に爆弾を使用し、自分と同じ行・列にいる敵

    を一掃できる. • 全ての敵を倒すのに必要な移動回数の最小値を求める. 講評 • W, H, N ≦100000. • 移動の方法は無数にあるため、探索の方法を考える.
  19. 問8 ダンジョン • 下に進む距離を決めたら、「自分より下のマスにいる敵のうち、 もっとも左にいる敵のx座標」まで進めばよい. • これは、以下の手順により求められる: – 各行に対して、「敵がいるx座標の最大値」を求める. –

    下から順番に、「自分の行の敵のx座標の最大値」と、「自分よ り下の敵のx座標の最大値」のうち大きい方を順番にもとめて いく. • 下に進む距離を1 ~ H まで全て試して、最大値を出す. – 計算量は O(H) になる. – H ≦ 100000 より実行可能.
  20. #include <iostream> using namespace std; int xmax[100005]; int main(){ int

    w, h, n; cin >> w >> h >> n; fill(xmax, xmax+h+1, 0); for(int i=0; i<n; i++) { int x, y; cin >> x >> y; xmax[y]=max(xmax[y], x); } for(int i=h-2; i>=0; i--){ xmax[i]=max(xmax[i], xmax[i+1]); } int ans=INT_MAX; for(int i=0; i<h; i++){ ans=min(ans, i+xmax[i+1]); } cout << ans << endl; } 問8 C++による解答例
  21. 問9 人気のユーザー名 問題概要 • 文字列 s と整数 k が与えられる. •

    s の隣り合う2文字をk回以下反転させてできる文字列のうち、 辞書順で最小のものを求める. 講評 • |s|≦200000、k≦1000000000. • 入れ替えの全ての組み合わせは膨大な量で、探索不可能.
  22. 問9 人気のユーザー名 • 1文字目から順番に、文字を決定し、順次追加していく. • 辞書順で最小の文字列を作るためには、後の文字列に関係なく、 取れる範囲で最も辞書順最小の文字をとる必要がある. – 例えば、3文字目の「b」を取るより、10文字目の「a」を取った方 が結果的に辞書順で小さくなる.

    • ある文字を追加するのに必要な入れ替え回数は、元の文字の先 頭からその文字までのうち、まだ使われていない文字数. tabinidetaika 入れ替え:1回 a tabinidetaika 入れ替え:8回 aa tabinidetaika 入れ替え:10回 aaa ・・・
  23. #include <iostream> using namespace std; int n, k; string str;

    int nxt[26]; int bit[200005]; void add(int i, int x){ i++; while(i<200005){ bit[i]+=x; i+=i&-i; } } int sum(int i){ i++; int ret=0; while(i){ ret+=bit[i]; i-=i&-i; } return ret; } 問9 C++による解答例 void gonext(int i){ while(nxt[i]<n && str[nxt[i]]!='a'+i) nxt[i]++; } int main(){ cin >> str >> k; n=str.length(); fill(bit, bit+200005, 0); for(int i=0; i<n; i++) add(i, 1); fill(nxt, nxt+26, 0); for(int i=0; i<26; i++) gonext(i); for(int i=0; i<n; i++){ for(int j=0; j<26; j++){ if(nxt[j]==n) continue; int cnt=sum(nxt[j]-1); if(cnt<=k){ k-=cnt; add(nxt[j], -1); cout << (char)('a'+j); nxt[j]++; gonext(j); break; } } } cout << endl; }
  24. 問10 道路網改修 講評 • N ≦ 10000、M≦100000. • グラフに関する知識と考察が必要. 解法

    • ある頂点 v から別の頂点へ向かう辺を「vの出次辺」、別の頂点か ら v に向かう辺を「vの入次辺」と呼ぶことにする. • 辺をいくつか追加して条件を満たすようになったグラフを「完成グラ フ」と呼ぶことにする. • 頂点数が1のグラフには新たに辺を追加することができないので、 答えは0である. • 以下、頂点数は2以上であるとする. • 完成グラフには必ず辺が1本以上存在する.
  25. 問10 道路網改修 • 完成グラフの任意の頂点には、出次辺が存在する. – 出次辺のない頂点 v があった場合、v から出発したときに、ど の頂点へも行くことができないため、完成グラフの辺を1つも通

    ることができない. • 完成グラフの任意の頂点には、入次辺が存在する. – 入次辺のない頂点 v があったとする.上の結果から、 v には必 ず出自辺が存在するが、 v 以外の頂点から出発したときに v に向かう辺がないので、v の出自辺を通れない. • 完成グラフ上で、任意の頂点から任意の頂点へ行くことができる. – 完成グラフ上のある頂点 s から頂点 t へ行けないとする.前述 のように t には出次辺が存在するが、このとき頂点 s からその 辺を通ることは不可能なため、矛盾する. • 逆に、グラフ上で任意の頂点から任意の頂点まで行ける場合、そ のグラフは完成グラフである. – 通っていない辺があるときは、その辺の始点に行ってからその 辺を通ればよい.
  26. 問10 道路網改修 • グラフの頂点のある部分集合について、その任意の頂点から任意 の頂点まで辺を辿って行くことができるとき、その頂点集合を「強 連結成分」という. – 完成グラフの条件は、全頂点が同じ強連結成分に属すること. • N頂点M辺の有向グラフは、O(N+M)で強連結成分に分解できる.

    – 1.適当な頂点からDFS(深さ優先探索)をする.このとき、「これ 以上進めない」状態になった頂点から番号をつける. – 2.辺をすべて逆にする. 3 0 2 1 4 5 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ (丸数字はたどる順番を表す) 3 0 2 1 4 5
  27. 問10 道路網改修 • N頂点M辺の有向グラフは、O(N+M)で強連結成分に分解できる. – 3.先程振った数字が大きい頂点からDFSをする.「これ以上進 めない」状態になったら、そこまで通ったすべての頂点が同じ 強連結成分に属する. – これを「強連結成分分解」という.

    • 全ての頂点が同じ強連結成分に属する場合、どの2頂点間も互い に行き来できるので、辺を追加する必要はない.答えは0である. ⑤ ④ ② (丸数字はたどる順番 を表す) 0 2 4 ① 5 ⑥ 1 ③ 3
  28. 問10 道路網改修 • 条件から、任意の終点から始点へ辺を辿って行ける必要がある. • 逆に、そうであれば、任意の頂点 s から頂点 t へ行くことができる.

    – s には必ずある終点 s' へつながる道がある.また t に行ける始 点 t' も必ずある.仮定より s' から t' に行く道があるので条件は 満たされる.
  29. 問10 道路網改修 • K = max(始点の個数, 終点の個数) とおく. • 始点には入次辺が、終点には出次辺があるようにしなければなら

    ないので、最低でも K 本の辺を追加しなくてはならない. • 実はK本の辺を追加することで条件を満たすことができる.
  30. 問10 道路網改修 • → 、 → 、…… → に、それぞれ辺を追加する. –

    これで、今見つけたすべての頂点に関するループができ、これ らの頂点間はすべて行き来できるようになった. 0 1 0 a 1 a 2 b b 3 2 4 c 3 0 0 1 1 2 4 便宜上、選ばれた頂点に 0, 1, 2, ..., 選ばれなかった 頂点に a, b, c, ... を振った.
  31. 問10 道路網改修 • 以上より、K 本の辺の追加によってグラフを完全グラフにできること が証明された. • 強連結成分分解にかかる時間はO(N+M)である. • 入次辺がない成分と出次辺がない成分の個数を調べるのにかか

    る時間はO(N+M)である. • 全体でO(N+M)で答えを求めることができ、時間制限に間に合う. • 実装の際、始点・終点の数を求めるために set 等のデータ構造を 使用した場合、O(N log N + M) となる.
  32. #include <iostream> #include <vector> #include <set> using namespace std; int

    n, m; int s[100005], t[100005]; vector<int> edge[10005], redge[10005]; bool used[10005]; vector<int> ord; int comp[10005]; set<int> usedin, usedout; void dfs1(int a){ if(used[a]) return; used[a]=true; for(int i=0; i<edge[a].size(); i++) dfs1(edge[a][i]); ord.push_back(a); } void dfs2(int a, int c){ if(used[a]) return; used[a]=true; comp[a]=c; for(int i=0; i<redge[a].size(); i++) dfs2(redge[a][i], c); } 問10 C++による解答例 int main(){ cin >> n >> m; for(int i=0; i<m; i++){ cin >> s[i] >> t[i]; edge[s[i]].push_back(t[i]); redge[t[i]].push_back(s[i]); } fill(used, used+n, false); for(int i=0; i<n; i++) dfs1(i); int cnt=0; fill(used, used+n, false); for(int i=n-1; i>=0; i--) if(!used[ord[i]]) dfs2(ord[i], cnt++); for(int i=0; i<m; i++){ if(comp[s[i]]!=comp[t[i]]){ usedout.insert(comp[s[i]]); usedin.insert(comp[t[i]]); } } int ans=0; if(cnt>1) ans=max(cnt-usedout.size(), cnt-usedin.size()); cout << ans << endl; }
  33. ໰ ಓ࿏໢վमʢதͷਓͷٙ໰ʣ தͷਓͰ͢ɻͪΐͬͱ͚ͩ໰୊ʹจ۟ΛݴΘ͍͖ͤͯͨͩ·͢ɻ ໰୊จͷ੍໿ʹΑΔͱɺ/ ௖఺ͷ਺ 㱢ɺ. ลͷ਺ 㱢ͱ͋ΔͷͰɺ ਤ ͷΑ͏ʹ௖఺ลͷάϥϑ΍ɺ

    ਤ ͷΑ͏ͳ / 㱢 ௖఺ลͷάϥ ϑ΋ߟ͑ΒΕ·͢ɻ͜ͷΑ͏ͳάϥϑʹରͯ͠ɺ৚݅Λຬͨ͢Α͏ͳลͷ ௥Ճͷ࢓ํ͸ͲͷΑ͏ͳ΋ͷͳͷͰ͠ΐ͏͔ʁ ໰୊จʹ͸͜ͷΑ͏ͳจݴ͕͋Γ·͢ɿ ʮ೚ҙͷ౎ࢢ͔Βग़ൃͨ͠ͱ͖ɺ૿ઃͨ͠΋ͷ΋ؚΊͯࠃ಺ͷ͢΂ ͯͷಓ࿏Λ̍౓Ҏ্௨ͬͯग़ൃͨ͠౎ࢢʹ໭Δ͜ͱ͕Ͱ͖Δɻʯ Ͱ͸ɺಓ࿏͕ຊͰ͋ΔΑ͏ͳಓ࿏໢͸͜ͷ৚݅Λຬͨ͢ͷͰ͠ΐ͏͔ʁ ᶃ৚݅Λຬͨ͢ͱ͢Δߟ͑ํ ࿦ཧతʹݴ͑͹ɺʮ͢΂ͯͷ˓˓ʹ͍ͭͯ˚˚Ͱ͋Δʯͱ͍͏จݴ͸ɺ˓˓ʹ͋ͨΔ΋ͷ͕ଘ ࡏ͠ͳ͍ͱ͖͸ৗʹਅʹͳΓ·͢ɻࠓճͷྫͰ͍͑͹ɺʮ͢΂ͯͷಓ࿏Λʙʜʜʯͱ͍͏৚݅ ͕ͦΕʹ͋ͨΓɺล͕ͷάϥϑ͸͜ͷ৚݅Λຬͨ͢͜ͱʹͳΓ·͢ɻ͜ͷͱ͖ɺ ਤ ɾ ਤ ͷΑ͏ͳάϥϑ͸͢Ͱʹ৚݅Λຬ͍ͨͯ͠ΔͷͰɺ౴͑͸ʢ͜ΕҎ্ลΛ௥Ճ͠ͳͯ͘΋ɺ͢ Ͱʹ͜ͷಓ࿏໢͸৚݅Λຬ͍ͨͯ͠ΔʣͱͳΓ·͢ɻͨͩɺݱࡏͷ"0+Ͱ͸͜Ε͸"$ʹ͸ͳΓ ·ͤΜɻ ᶄ৚݅Λຬͨ͞ͳ͍ͱ͢Δߟ͑ํ ໰୊จΛΑ͘ݟΔͱɺʮʜʜग़ൃͯ͠དྷͨ౎ࢢʹ໭Δ͜ͱ͕Ͱ͖Δɻʯͱ͍͏৚͕݅͋Γ·͢ɻ ʮ໭Δʯ͜ͱ͸ɺʮผͷ৔ॴ͔Βݩͷ৔ॴ΁ؼͬͯ͘Δʯͱ͍͏͜ͱͰ͢ɽඞͣʮ໭ͬͯʯ͜ ͳͯ͘͸ͳΒͳ͍ͷͳΒɺ׬੒ͨ͠άϥϑʹ͸ඞͣล͕͋Δ͸ͣͰ͢ɻ ਤ ௖఺͕ݸҎ্͋ ΔͳΒɺ ਤ ͷΑ͏ʹ͢΂ͯΛपճ͢ΔΑ͏ʹ/ຊͷลΛ௥ՃͰ͖·͢ɻ͔͠͠ࠓ౓͸ɺ ਤ ͷΑ͏ͳάϥϑʹ͸ɺղ͕ଘࡏ͠ͳ͘ͳͬͯ͠·͍·͢ɻ౎ࢢ͕ͭͰ͸ଞͷ౎ࢢ͔Βʮ໭Δʯ ͜ͱ΋Ͱ͖·ͤΜ͠ɺࣗ෼͔Βࣗ෼΁ͭͳ͕ΔΑ͏ͳಓ࿏Λ௥Ճ΋Ͱ͖ͳ͍͔ΒͰ͢ɻ ਤ ਤ ਤ
  34. ໰ ಓ࿏໢վमʢதͷਓͷٙ໰ʣ "0+ΛݟͯΈΔͱɺςετέʔεͷதʹ͸ / .ͷέʔε΋ɺ·ͨ /㱢 .ͷέʔε΋ଘࡏ͢ΔΑ͏Ͱ͢ɻਖ਼౴ͷίʔυ͔Βਪଌ͢Δʹɺ / .ͷͱ͖͸ɺ౴͑͸Ͱ͋ΔʢᶃͷղऍʹଇΔʣ /㱢

    .ͷͱ͖͸ɺ౴͑͸/Ͱ͋ΔʢᶄͷղऍʹଇΔʣ ͱ͍͏ղ౴ʹͳ͍ͬͯΔΑ͏Ͱ͢ɻ͜Ε͸࣮͸ɺʮάϥϑͷ௖఺Λશͯಉ ͡ڧ࿈݁੒෼ʹ͢Δͱ͖ʹඞཁͳ࠷খͷลͷ௥Ճຊ਺ʯʹ౳͍͠΋ͷͰ͢ɻ ͔֬ʹɺղઆͰઆ໌ͨ͠ͱ͓ΓɺҰൠʹ͜ͷ໰୊ͷ৚݅͸ɺʮάϥϑΛಉ ͡ڧ࿈݁੒෼ʹ͢Δʢ೚ҙͷ௖఺Λߦ͖དྷͰ͖ΔΑ͏ʹ͢Δʣʯ͜ͱͱݴ ͍׵͑ΒΕ·͕͢ɺͦͷٞ࿦͸ϖʔδ໨ͷ࠷ޙʹॻ͍ͨΑ͏ʹɺʮ׬੒ άϥϑʹ͸ඞͣล͕ଘࡏ͢ΔʯͱԾఆ͔ͨ͠Β੒Γཱͬͨ͜ͱͰ͢ɻʢ͜ Ε͸ઌ΄ͲͷᶄͰड़΂ͨΑ͏ͳɺʮ໭Δʯͱ͍͏จݴΛ೦಄ʹஔ͍ͨԾఆ Ͱ͢ʣ ਤ ਤ ਤ ͪ͝Όͪ͝Όͱॻ͖·͕ͨ͠ɺࢲͷ݁࿦͸ɺʮ໰୊จʹ΋ᐆດੑ͕͋Γɺղͳ͠ʹͳΔߟ͑ํ΋Ͱ͖ ͦ͏͚ͩͲɺຊ౰ʹ͜ͷ໰୊େৎ෉ͳͷ͔ͳʁʯͱ͍͏͜ͱͰ͢ɻ௕จࣦྱ͠·ͨ͠ɻ
  35. 問11 ネットワークの課金システム 問題概要 • N頂点の木があり、各辺には正のコストが定まっている. • 以下のようなクエリがQ個あるので、それらを処理せよ. – "add x

    b" : 頂点 x を端点にもつすべての辺のコストをx加算. – "send s t" : 頂点 s から頂点 t までのパス上の辺のうち、コスト が K の倍数でないもののコストを合計し、表示する. • 例:N=11, Q=3, K=5 1 0 2 3 4 5 6 7 8 9 10 3 2 5 3 9 2 4 3 10 1 1 0 2 3 4 5 6 7 8 9 10 3 2 5 3 9 2 4 3 10 1 1 0 2 3 4 5 6 7 8 9 10 5 2 7 5 9 2 4 3 10 1 1 0 2 3 4 5 6 7 8 9 10 5 2 7 5 9 2 4 3 10 1 初期状態 send 8 5 add 1 2 send 6 9 コスト:17 コスト:13 +2
  36. 講評 • N ≦ 100000、K ≦ 100000、Q ≦ 100000. •

    グラフとデータ構造に関する高度な知識が必要. • 以下、更新クエリ "add x d" を (x)+d と、送信クエリ "send s t" を [s-t] と略記することにする. • 全ての情報を逐次更新していくことを考える. – (x)+d クエリには、x に接続された O(N) 本の全ての辺の更新の 必要がある. – [s-t] クエリには、s から t へのパス上にある O(N) 本の辺につい て計算する必要がある. • 全体でO(NQ) となって間に合わない. 問11 ネットワークの課金システム
  37. 解法1 • 1 クエリあたりにかかる計算量を O(N) から減らしたい. • まず、(x)+d クエリの扱いを考える. •

    各辺のコストをそのまま管理しようと、(x)+d クエリに対しては、x に 直接接続されている全ての辺に対して d を足す処理を行う必要が ある.これは最悪 O(N) となる. • そこで、頂点ごとに変数 c[i] をもち、(x)+d クエリが来たら c[x] に d を足すようにする. • このとき、2頂点 p, q を結ぶ辺のコストは、(元のコスト) + c[p] + c[q] となる. 問11 ネットワークの課金システム 4 5 6 7 9 8 3 2 4 1 6 +0 +0 +0 +0 +0 +0 4 5 6 7 9 8 3 2 4 1 6 +2 +0 +0 +0 +0 +0 4 5 6 7 9 8 3 2 4 1 6 +2 +0 +6 +0 +0 +0 2+0+0=2 6+0+0=6 2+2+0=4 6+0+0=6 2+2+6=4 6+6+0=12 (4)+2 (6)+6
  38. • 次に、[s-t] クエリの扱いを考える. • s から t までのパス上にある辺の数は O(N) 本であるので、その辿

    る辺の数を減らしたい. • そこで、クエリの平方分割をする. • Q 個あるクエリを、 個ずつのグループに分割し、グループごと にクエリを順次処理する.(合計で 個のグループができる.) • 注目しているグループについて、「そのグループ内のクエリで指定 されている頂点」のみからなる木を生成する. – この木の頂点数は( )になる. • すると、 パスに含まれる辺の数は最大でも( )本程度となるの で、1つのクエリに( )で答えることができる. 問11 ネットワークの課金システム
  39. • 再構成された木の頂点は、「クエリに実際に含まれる頂点」(赤い 頂点)と、「頂点同士をつなぐために必要な分岐部分の頂点」(緑 色の頂点)からなる. – このようにすると、[s-t] クエリにおいて、s から t のパスは、再構

    成された部分木にも必ず含まれるようになる. 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 [17-7] (13)+2 [4-1] (17)+6 [26-17] 例) N=33, K=5, Q=25 で、 ある連続する 個(=5個)の クエリが以下だった場合 0 2 1 7 17 13 26 4 +2 +3 +0 +0 +0 +0 +0 +0 +0 +4 +0 +0 +0 +7 +0 +0 +0 +0 +0 +2 +0 +0 +0 +0 +0 +0 +0 +0 +2 +1 +0 +0 1 7 4 1 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 2 6 11 +2 +3 +0 +0 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4
  40. • 赤い頂点の数はクエリの数の2倍で 2 以下. • 緑の頂点は赤い頂点の数以下.(赤い頂点を1つずつ追加していく ことを考えると、1つの追加毎に緑の頂点は高々1つ必要なため) • よって、このグラフの頂点数は となる.

    問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 [17-7] (13)+2 [4-1] (17)+6 [26-17] 例) N=33, K=5, Q=25 で、 ある連続する 個(=5個)の クエリが以下だった場合 0 2 1 7 17 13 26 4 +2 +3 +0 +0 +0 +0 +0 +0 +0 +4 +0 +0 +0 +7 +0 +0 +0 +0 +0 +2 +0 +0 +0 +0 +0 +0 +0 +0 +2 +1 +0 +0 1 7 4 1 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 2 6 11 +2 +3 +0 +0 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4
  41. • 再構成された木における、各頂点間のコスト合計を予め計算する. • ただし、元の木の2本以上の辺を1本にまとめるとき、頂点に接続さ れている辺のコストは合計せず、残しておく.(図の紫・水色の辺) – (x)+d クエリで、辺のコストが 途中で K

    の倍数になったり、Kの 倍数でなくなったりすることもあるため. • この計算のために、1回の木の再構成には O(N) の時間がかかる. 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 [17-7] (13)+2 [4-1] (17)+6 [26-17] 例) N=33, K=5, Q=25 で、 ある連続する 個(=5個)の クエリが以下だった場合 0 2 1 7 17 13 26 4 +2 +3 +0 +0 +0 +0 +0 +0 +0 +4 +0 +0 +0 +7 +0 +0 +0 +0 +0 +2 +0 +0 +0 +0 +0 +0 +0 +0 +2 +1 +0 +0 1 7 4 1 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 2 6 11 +2 +3 +0 +0 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4
  42. • 計算例 問11 ネットワークの課金システム [17-7] (13)+2 [4-1] (17)+6 [26-17] 例)

    N=33, K=5, Q=25 で、 ある連続する 個(=5個)の クエリが以下だった場合 0 2 1 7 17 13 26 4 +0 11 +2 +3 +0 +0 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4 0 2 1 7 17 13 26 4 11 +2 0 2 1 7 17 13 26 4 +0 11 +2 +3 +2 +0 +0 4 7 8 4 2 0 3 5 0 2 1 7 17 13 26 4 11 +6 0 2 1 7 17 13 26 4 11 +2 +3 +6 +0 +0 4 3 9 5 2 0 3 3 0 4 +0 +2 +3 +0 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4 +0 +0 +0 3 9 5 3 9 8 3 0 4 +0 +2 +3 +2 +0 +0 +0 +0 4 7 3 9 5 3 9 8 8 4 2 0 3 5 3 0 4 +0 +2 +0 +0 7 3 9 8 8 4 5 5+0=5 9 3+3=6 7+3+0=10 3+0=3 9 8+0=8 2+2=4 4+2+3=9 7+2+3=13 8+2=10 4+0=4 0 3+0=3 5+0+0=5 5+6=11 9 3+3=6 4+2+3=9 2+2=4 0 3+0=3 3+0=3 0 4+0=4 [17-7] : 9+6+3+9+8=35 [4-1] : 5+0+4+9+13+4=35 [26-17] : 4+0+3+3+0+4+9+6+9+11=49 (13)+2 (17)+6
  43. • 一回のグループあたり、 – 必要な頂点を含む木を再構成するのに . – (x)+d クエリに答えるのに(1). – [s-t]

    クエリに答えるのに( ). – よってグループ内の 個のクエリに答えるのに ⋅ = (). • グループの個数は 個あるので、全体の計算量は + ⋅ + -となる. • N ≦ 100000、K ≦ 100000、Q ≦ 100000 から、 ⋅ ≈ ⋅ ≈ 30 000 000. • 制限時間は 6 秒なので、実行時間に間に合う. 問11 ネットワークの課金システム
  44. 解法2 • ある [s-t] について、s と t には共通の親がある. – 共通の親のうち、最も下にあるもの(最も

    s と t に近いもの)を、 最近共通祖先(LCA)と呼ぶ. • 頂点 s と頂点 t のLCAを u とすると、s から t までのコストは、 (u から s までのコスト)+(u から t までのコスト) =((根から s)- (根から u))+((根から t ) - (根から u)) =(根から s)+(根からt) - 2*(根から u) で求められる. • 根から任意の頂点までのコストを高速に求めたい. 問11 ネットワークの課金システム s t 共通の親 ←LCA 根 u s t 根から u のコスト s から u のコスト t から u のコスト
  45. • 木を重軽分解(Heavy-Light Decomposition, HL分解)する. • それぞれの頂点を根とする部分木の頂点数を求める. • 各頂点の直接の子のうち、最も頂点数が多いものにつながってい る辺を Heavy

    辺(オレンジ)、それ以外を Light 辺(黒)とする. 問11 ネットワークの課金システム 33 22 1 8 7 3 2 3 1 1 1 12 4 4 3 3 1 1 3 2 1 2 8 7 4 3 1 1 1 1 1 1 10 33 22 1 8 7 3 2 3 1 1 1 12 4 4 3 3 1 1 3 2 1 2 8 7 4 3 1 1 1 1 1 1 10 Heavy Light
  46. • このとき、任意の頂点から根までのパス 上に、Light 辺は高々log5 回だけ通る. – Light 辺を上がると、部分木の大きさ は必ず2倍以上になるため. •

    すなわち、頂点から根までのパスは、 高々log5 回の連続する Heavy 辺から なる. • 連続する Heavy 辺が隣り合うように頂点 を一列に並べると、任意のパスがlog5 個の連続する部分列で表される. 問11 ネットワークの課金システム 33 22 1 8 7 3 2 3 1 1 1 12 4 4 3 3 1 1 3 2 1 2 8 7 4 3 1 1 1 1 1 1 10 33 22 12 4 3 1 8 7 3 1 3 2 1 12 4 3 1 4 3 2 1 Heavy Light • 頂点の列の連続する一部分のコストを高速に求めたい. • BIT を用いる.
  47. 2 7 7 9 9 • セグメント木の各ノードに、頂点間のコストを保存する. – 頂点 w

    から x までをカバーするノードには、頂点 w から x まで のコストが保存されている. • 頂点 w から x に対応するノードと、 頂点 y から z のに対応するノー ドの親ノードが保持するコストは、(wからxまでのコスト)+(yからzま でのコスト)+(xとyを結ぶ辺のコスト)となる. 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 +2 +3 +0 +0 +0 +0 +0 +0 +0 +4 +0 +0 +0 +7 +0 +0 +0 +0 +0 +2 +0 +0 +0 +0 +0 +0 +0 +2 +1 +0 +0 1 7 6 1 9 3 5 2 1 4 1 5 3 2 3 5 2 3 2 3 8 4 1 5 1 2 6 3 2 1 3 5 0 11 10 9 15 16 17 13 25 24 0-11 0 0 0 0 0 0 0 0 0 0 10-9 15-16 17-13 25-24 0-9 15-13 25-7 0-13 18 14 10 32
  48. 2 3 0 7 5 0 7 5 0 9

    3 7 9 2 4 • セグメント木の1つの要素に、 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 +2 +3 +0 +0 +0 +0 +0 +0 +0 +4 +0 +0 +0 +7 +0 +0 +0 +0 +0 +2 +0 +0 +0 +0 +0 +0 +0 +2 +1 +0 +0 1 7 6 1 9 3 5 2 1 4 1 5 3 2 3 5 2 3 2 3 8 4 1 5 1 2 6 3 2 1 3 5 0 11 10 9 15 16 17 13 25 24 0-11 0 2 2 0 7 4 0 3 0 0 2 0 0 5 4 0 3 0 0 5 0 0 7 0 0 3 0 0 9 7 10-9 15-16 17-13 25-24 0-9 15-13 25-7 0-13 18 2 0 14 5 0 10 3 0 32 2 0
  49. • {c[x, y], p[x, y], a[x, y]} と {c[w, z],

    p[w, z], a[w, z]} が分かっていて、y が z の直接の親であったとする. • y と z を結ぶ辺の • 、x から z までのコスト情報は以下のように計算できる: – c[x, z] = c[x, y] + a[x, y] + p[w, z] + c[w, z] – p[x, z] = p[x, y] – a[x, z] = a[w, z] • これらを利用して、任意の頂点から根までの 問11 ネットワークの課金システム
  50. • 重軽分解には O(N). • [s-t] クエリに対しては、(log5 )個の区間それぞれに(log5 ) の計算量がかかるので、 (

    log 5). • (x)+d クエリに対しては、x から x の親に向かう辺と、x から xの子に 向かう辺のうち Heavy 辺(高々1本)に対してBITの更新処理が必要 なので、計算量は(log ). • 合計の計算量は( + log 5)で間に合う. 問11 ネットワークの課金システム
  51. 解法3 • 木の頂点を、根から DFS したときの行き掛け順に並べる. – 任意の頂点 v の部分木は、並べた後の配列において、v を先

    頭とする連続する部分に対応する. 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 1 7 4 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 6 0 11 12 10 9 19 29 18 15 16 17 13 25 24 1 30 21 28 7 14 6 8 20 部分木 部分木 部分木 部分木 3 1 2
  52. • この配列上で、根から各頂点までのコストを計算する.この時点で はKの倍数の辺も無視せずそのまま加算することにする. • 辺のコストを変更すると、この配列はどのように変化するか. 問11 ネットワークの課金システム 0 11 12

    10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 1 7 4 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 6 0 11 12 10 9 19 29 18 15 16 17 13 25 24 1 30 21 28 7 14 6 8 20 3 0 4 5 7 9 14 20 16 10 13 18 11 14 16 19 17 19 23 25 26 16 22 23 1 2
  53. • たとえば、頂点11と頂点13をつなぐ辺のコストを、7+5=12 にする. • すると、頂点 13 を根とする部分木に属するすべての頂点について、 根からのコストが 5 増える.

    • 右の配列においては、頂点13から頂点20までの範囲のすべての コストに、一様に +5 が加算される. 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 1 7 4 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 6 0 11 12 10 9 19 29 18 15 16 17 13 25 24 1 30 21 28 7 14 6 8 20 3 0 4 5 7 9 14 20 16 10 13 18 11 14 16 19 17 19 23 25 26 16 22 23 1 2 --->12 +5 0 4 5 7 9 14 20 16 10 13 18 16 19 21 24 22 24 28 30 31 16 22 23
  54. • さらに、頂点13から25、14、21それぞれにつながる辺のコストにも 5 を加算する. • すると、右の配列では、頂点13の部分木にあたる部分のうち、頂 点13自身を除いたものすべてに一様に5が加算される. 問11 ネットワークの課金システム 0

    11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 1 7 4 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 6 0 11 12 10 9 19 29 18 15 16 17 13 25 24 1 30 21 28 7 14 6 8 20 3 0 4 5 7 9 14 20 16 10 13 18 11 14 16 19 17 19 23 25 26 16 22 23 1 2 --->12 +5 0 4 5 7 9 14 20 16 10 13 18 16 19 21 24 22 24 28 30 31 16 22 23 --->8 --->13 --->10 +5 +5 +5 0 4 5 7 9 14 20 16 10 13 18 16 24 26 29 27 29 33 35 36 16 22 23
  55. • 結果的に、(x)+d クエリは、右の配列では、「xの部分木にあたる要 素すべてに一様に +d をし、さらに x の部分木にあたる要素のうち x を除く部分に

    +d をする」という操作で実現できる. • 区間への一様な加算は、BIT 等の操作を使って実現できる.(この 計算に使うBITを、「コスト計算BIT」とよぶことにする.) 問11 ネットワークの課金システム 0 11 12 10 9 15 16 19 17 18 29 13 25 14 21 24 6 7 1 28 30 8 32 2 22 23 26 27 5 4 3 20 31 1 7 4 5 2 6 5 3 3 1 2 3 2 3 8 4 1 5 1 3 9 2 1 3 5 3 5 2 1 6 0 11 12 10 9 19 29 18 15 16 17 13 25 24 1 30 21 28 7 14 6 8 20 3 0 4 5 7 9 14 20 16 10 13 18 11 14 16 19 17 19 23 25 26 16 22 23 1 2 --->12 +5 0 4 5 7 9 14 20 16 10 13 18 16 19 21 24 22 24 28 30 31 16 22 23 --->8 --->13 --->10 +5 +5 +5 0 4 5 7 9 14 20 16 10 13 18 16 24 26 29 27 29 33 35 36 16 22 23
  56. • 次に、コストが K の倍数の辺を無視する方法について考える. • (x)+d クエリを実行した後、コスト計算BITにおいて、コストがKの倍 数である辺によって加算されている範囲を、0 によって加算されて いるように直したい.

    • しかし、この処理は、一つの (x)+d クエリに対して、x に直接接続さ れている辺すべてに対して行わなければならないため、(x)+d クエ リ一つだけで O(N) の時間がかかってしまう. • 更新された辺のコストを実際に考慮するのは、[s-t] クエリにおいて、 根から s までのパス、または根から t までのパス上にその辺があ るときのみでいい. • そこで、s または t から根までのパス上に、過去の(x)+d クエリに よって更新された辺があったとき初めて、辺の長さの判定をすれ ば、計算量は削減される. • この作業の計算量を評価する. 問11 ネットワークの課金システム
  57. • 簡単のため、(x)+d クエリが6 個連続した後、[s-t] クエリが7 個連 続した場合を考える. • 下のような木があって、そのうち赤丸で示した7 個の頂点それぞ

    れに (x)+d クエリが操作されたとする.このとき、赤く塗られた辺の コストが変化している. 問11 ネットワークの課金システム
  58. • 実は、これらのクエリが任意の順番で置かれているときも、同様の ことが言える. • 証明はやや複雑である. • まず、「根から頂点 u までのパス上にある •

    6 個の(x)+d クエリと7 個の [s-t] クエリ、合計 Q 個のクエリがある とする. . – [s-t] クエリに関しては • まず、Q 個のクエリに 0, 1, 2, ... と順に番号を振る. 問11 ネットワークの課金システム
  59. #include <iostream> #include <cmath> #include <vector> using namespace std; typedef

    long long ll; ll k; ll MOD(int a){ if(a%k==0) return 0; else return a; } vector<pair<int, ll> > path[100005]; int par[100005]; int dep[100005]; ll cst[100005]; void dfs(int a){ for(int i=0; i<path[a].size(); i++){ int d=path[a][i].first; if(d==par[a]) continue; par[d]=a; dep[d]=dep[a]+1; cst[d]=path[a][i].second; dfs(d); } } ll add[100005]; 問11 C++による解答例(解法1)
  60. bool used[100005]; int cnt[100005]; int cntdfs(int a){ cnt[a]=used[a]; for(int i=0;

    i<path[a].size(); i++){ int d=path[a][i].first; if(d!=par[a]) cnt[a]+=cntdfs(d); } return cnt[a]; } int ming_par[100005]; ll ming_cst[100005]; int ming_bef[100005]; 問11 C++による解答例(解法1) int main(){ int n; cin >> n >> k; for(int i=0; i<n-1; i++){ int a, b; ll c; cin >> a >> b >> c; path[a].push_back(make_pair(b, c)); path[b].push_back(make_pair(a, c)); } par[0]=dep[0]=cst[0]=0; dfs(0); int q; cin >> q; for(int i=0; i<q; i++){ string str; ll a, b; cin >> str >> a >> b; qry[i]={str[0], a, b}; } for(int i=0; i<n; i++) add[i]=0;
  61. int sq=(int)(sqrt(q)); for(int i=0; i<sq; i++){ int s=i*sq, t=(i+1)*sq; if(i==sq-1)

    t=q; used[0]=true; for(int j=1; j<n; j++) used[j]=false; for(int j=s; j<t; j++){ if(qry[j].t=='a') used[qry[j].a]=true; else used[qry[j].a]=used[qry[j].b]=true; } cntdfs(0); for(int j=1; j<n; j++) if(0<cnt[j] && cnt[j]<cnt[par[j]]) used[par[j]]=true; 問11 C++による解答例(解法1)
  62. for(int j=1; j<n; j++){ if(!used[j]) continue; ming_cst[j]=0; if(used[par[j]]){ ming_par[j]=par[j]; }else{

    int p=par[j]; while(!used[par[p]]){ ming_cst[j]+=MOD(cst[p]+add[p]+add[par[p]]); p=par[p]; } ming_bef[j]=p; ming_par[j]=par[p]; } } 問11 C++による解答例(解法1)
  63. for(int j=s; j<t; j++){ if(qry[j].t=='a'){ add[qry[j].a]+=qry[j].b; }else{ int s=qry[j].a, t=qry[j].b;

    ll ans=0; while(s!=t){ if(dep[s]<dep[t]) swap(s, t); int p = ming_par[s]; if(dep[s]-dep[p]>1){ int q = ming_bef[s]; ans+=MOD(add[s]+add[par[s]]+cst[s]); ans+=MOD(add[q]+add[par[q]]+cst[q]); ans+=ming_cst[s]; }else{ ans+=MOD(add[s]+add[p]+cst[s]); } s=p; } cout << ans << endl; } } } } 問11 C++による解答例(解法1)
  64. #include <iostream> #include <vector> #include <cmath> using namespace std; typedef

    long long ll; vector<pair<int, ll> > path[100005]; int par[100005]; int dep[100005]; int siz[100005]; ll cst[100005]; int hev[100005]; void dfs(int a){ siz[a]=1; for(int i=0; i<path[a].size(); i++){ int d=path[a][i].first; if(d==par[a]) continue; par[d]=a; dep[d]=dep[a]+1; cst[d]=path[a][i].second; dfs(d); siz[a]+=siz[d]; if(!hev[a] || siz[hev[a]]<siz[d]) hev[a]=d; } } 問11 C++による解答例(解法2)
  65. ll k; ll add[100005]; ll edgecost(int a){ ll ret=add[a]+add[par[a]]+cst[a]; return

    ret%k ? ret : 0; } int pos[100005]; int hl_par[100005]; ll bit[100005]; ll sum(int i){ ll ret=0; while(i){ ret+=bit[i]; i-=i&-i; } return ret; } void set(int i, ll x){ x-=sum(i+1)-sum(i); i++; while(i<100005){ bit[i]+=x; i+=i&-i; } } 問11 C++による解答例(解法2) ll cost(int a, int b){ int p=hl_par[a], q=hl_par[b]; if(p==q) return abs(sum(pos[a])- sum(pos[b])); if(dep[p]<dep[q]){ swap(p, q); swap(a, b); } return sum(pos[a]) - sum(pos[p]) + edgecost(p) + cost(par[p], b); } int main(){ int n; cin >> n >> k; for(int i=0; i<n-1; i++){ int a, b; ll c; cin >> a >> b >> c; path[a].push_back( make_pair(b, c)); path[b].push_back( make_pair(a, c)); } for(int i=0; i<n; i++) hev[i]=0; par[0]=dep[0]=cst[0]=0; dfs(0);
  66. for(int i=0; i<n; i++) add[i]=0; int cnt=0; for(int i=0; i<n;

    i++){ if(i && hev[par[i]]==i) continue; int j=i; do{ pos[j]=cnt++; hl_par[j]=i; }while(j=hev[j]); } for(int i=0; i<100005; i++) bit[i]=0; for(int i=0; i<n; i++) set(pos[i], edgecost(hev[i])); int q; cin >> q; for(int i=0; i<q; i++){ string s; ll a, b; cin >> s >> a >> b; if(s[0]=='a'){ add[a]+=b; set(pos[a], edgecost(hev[a])); if(a) set(pos[a]-1, edgecost(a)); }else{ cout << cost(a, b) << endl; } } } 問11 C++による解答例(解法2)