Codeforces #281 (Div.2 only)
昨日のCodeforcesはめずらしく良くできた気がするのでざっくり解説しておきます。
A. Vasya and Football
問題
サッカーのようなゲームが行われていて、90分間の間にホーム側の選手、アウェイ側の選手でどの番号の選手がイエローカード、レッドカードをもらったのかが与えられる。なお、イエローカードを2枚もらうと自動的にレッドカードとなる。このとき、各選手が初めてレッドカードをもらった時間を出力せよ。
解法
どうやら、問題の設定的に、イエローカード3枚とかレッドカード2枚とかをもらうことがあるようなので、それに注意して普通にシミュレーションする。
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;
#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl
typedef long long lint;
typedef pair<lint,lint> pll;
struct Card {
int m;
int t;
int num;
int color;
bool operator<(const Card& c) const {
return m < c.m;
}
};
string team[2];
int n;
vector<Card> cards;
void coding() {
int mm, nn;
char tt[2], cc[2];
cin >> team[0] >> team[1];
scanf("%d", &n);
cards.clear();
REP(i,n) {
scanf("%d %s %d %s", &mm, tt, &nn, cc);
int t = tt[0] == 'h' ? 0 : 1;
int c = cc[0] == 'y' ? 1 : 2;
Card card = { mm, t, nn, c };
cards.push_back(card);
}
sort(ALL(cards));
map<int,int> amap;
map<int,int> hmap;
REP(i,n) {
map<int,int>& mp = cards[i].t == 0 ? amap : hmap;
mp[cards[i].num] += cards[i].color;
if(mp[cards[i].num] >= 2) {
printf("%s %d %d\n", team[cards[i].t].c_str(), cards[i].num, cards[i].m);
mp[cards[i].num] = -1000000;
}
}
}
// #define _LOCAL_TEST
int main() {
#ifdef _LOCAL_TEST
clock_t startTime = clock();
freopen("a.in", "r", stdin);
#endif
coding();
#ifdef _LOCAL_TEST
clock_t elapsedTime = clock() - startTime;
cout << endl;
cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
cout << "This is local test" << endl;
cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}
B. Vasya and Wrestling
問題
2人の選手がレスリングの試合を行っていて、互いに技を繰り出している。現在2人の繰り出した技のリストが時系列順にならんでいるので、どちらの選手が勝ったのかを判断してほしい。2人の点数が異なる場合には、点数が多い方がかつ。もし同じときには、技のならびが辞書順に大きい方が勝つ。もしそれも同じなら最後に技を繰り出した方が勝ちとする。
解法
Aよりめんどくさくない気がする。普通にシミュレーションをすればいい。ただし、合計がintの範囲に収まらないことがあるので、それだけ注意する。
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;
#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl
typedef long long lint;
typedef pair<lint,lint> pll;
int n;
vector<lint> a;
vector<lint> b;
void coding() {
lint sa, sb, w, last;
while(scanf("%d", &n) != EOF) {
sa = 0;
sb = 0;
a.clear();
b.clear();
REP(i,n) {
scanf("%I64d", &w);
last = w;
if(w > 0) {
sa += w;
a.push_back(w);
} else {
w = abs(w);
sb += w;
b.push_back(w);
}
}
if(sa != sb) {
printf("%s\n", sa > sb ? "first" : "second");
} else if(a != b) {
printf("%s\n", a > b ? "first" : "second");
} else {
printf("%s\n", last > 0 ? "first" : "second");
}
}
}
// #define _LOCAL_TEST
int main() {
#ifdef _LOCAL_TEST
clock_t startTime = clock();
freopen("b.in", "r", stdin);
#endif
coding();
#ifdef _LOCAL_TEST
clock_t elapsedTime = clock() - startTime;
cout << endl;
cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
cout << "This is local test" << endl;
cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}
C. Vasya and Basketball
問題
現在2つのチームがバスケットボールのようなゲームを行っていて、チームAとチームBがゴールを決めたときに、何メートル離れたところからシュートを打ったかが与えられている。このときシュートがある距離d以下であれば2点が、それより大きければ3点が与えられる。dを適当に調節して、AとBの点数差がAにとって最も有利になるようにせよ。
解法
問題の大きさはシュートの本数が$ 2 \times 10^9$となっている。単純に考えると全ての距離にたいして、点数差がどうなるのかを調べれば良いのだが、それだと時間が足りない。
ここでポイントは各チームがシュートを打った距離だけが答えになりうるということで、それを調べるだけなら$2 \times 10^5$個を調べれば良いだけなので、時間が足りる。
あとは各チームについてシュートを距離順にソートしておいて、何本が2点シュートで何本が3点シュートなのかを二分探索で求められるようにしておく。
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;
#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl
typedef long long lint;
typedef pair<lint,lint> pll;
int n, m;
lint as[200011];
lint bs[200011];
void solve() {
sort(as, as+n);
sort(bs, bs+m);
vector<lint> vs;
vs.push_back(0);
vs.push_back(1ll<<60);
REP(i,n) vs.push_back(as[i]);
REP(i,m) vs.push_back(bs[i]);
sort(ALL(vs));
vs.erase(unique(ALL(vs)), vs.end());
int ma = -1<<30;
int mpa = -1<<30;
int id = 0;
REP(i,vs.size()) {
int ta = upper_bound(as, as+n, vs[i]) - as;
int tb = upper_bound(bs, bs+m, vs[i]) - bs;
int pa = 2 * ta + 3 * (n - ta);
int pb = 2 * tb + 3 * (m - tb);
if(ma < pa - pb) {
ma = pa - pb;
mpa = pa;
id = i;
} else if(ma == pa - pb && mpa < pa) {
mpa = pa;
id = i;
}
}
int ta = upper_bound(as, as+n, vs[id]) - as;
int tb = upper_bound(bs, bs+m, vs[id]) - bs;
int pa = 2 * ta + 3 * (n - ta);
int pb = 2 * tb + 3 * (m - tb);
printf("%d:%d\n", pa, pb);
}
void coding() {
while(scanf("%d", &n) != EOF) {
REP(i,n) scanf("%I64d", as+i);
scanf("%d", &m);
REP(i,m) scanf("%I64d", bs+i);
solve();
}
}
// #define _LOCAL_TEST
int main() {
#ifdef _LOCAL_TEST
clock_t startTime = clock();
freopen("c.in", "r", stdin);
#endif
coding();
#ifdef _LOCAL_TEST
clock_t elapsedTime = clock() - startTime;
cout << endl;
cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
cout << "This is local test" << endl;
cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}
D. Vasya and Chess
問題
2人が横nマス縦nマスの盤面でクイーンを動かすゲームをしている。初期状態ではプレイヤーAのクイーンが(1, 1)に、プレイヤーBのクイーンが(1, n)に置かれており、それ以外のマスには緑色のチップが置かれている。各プレイヤーは自分のターンの時にクイーンの動ける範囲でチップが置いてある場所か、相手のクイーンが置いてある場所に移動できる。もしチップと相手のクイーンが置いてある位置に移動できなくなればそのプレイヤーの負けとなる。また、相手にクイーンを取られても負けとなる。このとき盤面の大きさnが与えられるので、互いに最適な手を打った時にどちらが勝つのかを判定せよ。またプレイヤーAが勝つときには、その第1手を出力せよ。
解法
なぜかここからスポーツの問題ではなくなる。
それはともかく、この問題はnの最大値が$2 \times 10^9$なので、grundy数とかを計算してがんばるのは難しそうなことに気づく。
となれば何か法則があるはず。結論からいうと、その法則とはnが偶数のときはプレイヤーAが右に1マス動けば勝利できる。その後の手は、プレイヤーBが打った手と線対称になるように打っていけば必ず勝利できる。
逆に、盤面の大きさnが奇数の時は、プレイヤーBがプレイヤーAの打った手と線対称になる手を打ち続けることで必ず勝利できる。
これにさえ気づければコードはせいぜい数行程度。
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;
#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl
typedef long long lint;
typedef pair<lint,lint> pll;
void coding() {
int n;
while(scanf("%d", &n) != EOF) {
if(n % 2 == 0) {
printf("white\n");
printf("1 2\n");
} else {
printf("black\n");
}
}
}
// #define _LOCAL_TEST
int main() {
#ifdef _LOCAL_TEST
clock_t startTime = clock();
freopen("d.in", "r", stdin);
#endif
coding();
#ifdef _LOCAL_TEST
clock_t elapsedTime = clock() - startTime;
cout << endl;
cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
cout << "This is local test" << endl;
cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}
E. Vasya and Polynomial
問題
係数が正のn次多項式P(x)(nは任意)が与えられている。このときP(t) = a, P(P(t)) = bであることが分かっている。このような等式を満たす多項式の個数をすべて数え上げて10^9+7で割った余りを答えよ。
解法
まず一般的な観察としてP(t) = aなので、2つ目の式をP(a) = bと書き直すことができます。この気づきが以外に重要(?)だと思います。
では特殊なケースから考えます。t=a=b=1の時にはx^nが任意のnについてP(x)の条件を満たすので、答えはinfになります。
次に、t=aであるときは、a=bならばP(x)=aと、P(x)=xの1通りが答えになります。もしa!=bであれば、それはあり得ないので答えは0です。
次に、t!=aであるときは、a=bならばP(x)=aだけが答えになります。
で、上の場合以外のものが一般ケースとしてがんばって計算しないといけないものになります。
ポイントとして多項式の係数が全て正であるのでP(x)はx>0の範囲では単調増加です。なので、t, a, bも全て正であることからt < a < bが満たされなければいけません。そうでなければ答えは0です。
次にP(x) = a_0 + Q(x)xという風にn-1次式Q(x)をつかって書き直せることを利用します。
これを使うと
a = a_0 + Q(t)t
b = a_0 + Q(a)a
が同時に成り立たなければならず、またQ(t)とQ(a)がどちらも整数であることからa_0としてありうるものは
(a – a_0) % t = 0
(b – a_0) % a = 0
を満たさなければならないことになります。
これを満たすものは上の2式を満たす最小のa_0にtとaの最小公倍数を足していったものになりますので、それを全て列挙します。
そうしたらa_0が決まるので、同時にQ(t)およびQ(a)がいくつになるのかも決まります。
あとはQ(x)をP(x)のようにみなして上記の計算を再帰的に繰り返していくだけです。
で、最後にaもbも0となるようなケースが出てきたら、その時は1を返してあげます。
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <queue>
#include <bitset>
#include <string>
using namespace std;
#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define PB(a) push_back(a)
#define MP(i,s) make_pair(i,s)
#define SZ(a) (int)(a).size()
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a,b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl
#define PRT3(a,b,c) cerr << (a) << ", " << (b) << ", " << (c) << endl
typedef long long lint;
typedef pair<lint,lint> pll;
const lint mod = 1000000007;
lint t, a, b;
lint gcd(lint x, lint y) {
if(x > y) swap(x, y);
while(x != 0) {
lint t = y % x;
y = x;
x = t;
}
return y;
}
lint lcm(lint x, lint y) {
return x / gcd(x, y) * y;
}
lint dfs(lint d1, lint d2, lint g, lint a, lint b) {
// printf("%I64d %I64d %I64d %I64d %I64d n", d1, d2, g, a, b);
if(a == 0 && b == 0) return 1;
else if(a == 0 || b == 0) return 0;
if(d1 != 1 && a % d1 != b % d2) return 0;
lint base = b % d2;
lint ret = 0;
while(base <= a && base <= b) {
lint na = (a - base) / d1;
lint nb = (b - base) / d2;
ret = (ret + dfs(d1, d2, g, na, nb)) % mod;
base += g;
}
return ret;
}
void solve() {
printf("%I64d\n", dfs(t, a, lcm(t, a), a, b));
}
void coding() {
while(scanf("%I64d %I64d %I64d", &t, &a, &b) != EOF) {
if(t == 1 && a == 1 && b == 1) printf("infn");
else if(t == a) {
printf("%d\n", a == b ? 2 : 0);
}
else if(a == b) {
printf("1\n");
}
else if(t > a || a > b) printf("0\n");
else {
solve();
}
}
}
// #define _LOCAL_TEST
int main() {
#ifdef _LOCAL_TEST
clock_t startTime = clock();
freopen("e.in", "r", stdin);
#endif
coding();
#ifdef _LOCAL_TEST
clock_t elapsedTime = clock() - startTime;
cout << endl;
cout << (elapsedTime / 1000.0) << " sec elapsed." << endl;
cout << "This is local test" << endl;
cout << "Do not forget to comment out _LOCAL_TEST" << endl << endl;
#endif
}
まとめ
今回は初めて5問ともPretestを通すことができました。結果的に言うとCを定数の間違えで、Eを条件判定のミスで落としたので3問しか当たりませんでしたが、立てた方針は間違っていなかったので、今まででは一番良い結果だったと思います。
特にEの問題はコンテスト直後に僕がSubmitしなおしたときには、まだ11人しかできてなかったので、結構難しかったのかな?と思います。はい、ただの自慢です。
それでは、今回の解説を終わります。最後までお読みいただきましてありがとうございました。