Codeforces Round #127 (Div. 1)
tourist回嫌い(直球)
色々やってしまい1完でした。
A. 以下の様な条件を満たす行列を考える。
- n*nの正方行列である
- 全ての要素が0または1
- 行列は上下左右対称すなわちAi, j = An - i + 1, j and Ai, j = Ai, n - j + 1が成り立つ
- 上下左右に隣り合う要素同士が同時に1となることはない
そのような行列の内、1をx個要素としてもつものを構成することの出来る最小のnを求めよ。
なんか本当にヤバそうだなあと思いながら適当にやったら6WAぐらいして諦めた。
計算量は問題にならないので、n=1から順に試していけば良い。
そして基本的にまずnが偶数か奇数かで場合分けしないといけない。
nが偶数の時は結構簡単で、最後の条件と対称性から、中央の2行2列は0で埋まる。すると小さい正方形4つに分かれるイメージになって、それらの正方形には最後の条件以外を特に考えないで埋める事ができるから、xが4の倍数であってかつx/4 <= MAX_PLACE( (n-2)/2)であればよい。ここでMAX_PLACE(t)はt*tの正方形に最後の条件を満たすように入れることの出来る1の最大数。
nが奇数の時が辛い。nが奇数の時、nが偶数の時と同様に中央の1行1列以外の場所に1が存在する時は、勝手に対称性から4つ発生するので、4の倍数ずつ増える。
中央の1行1列でかつ、完全な中央でない場所に1が存在する時は、対称性から2つ発生するので2の倍数ずつ増える。
完全に中央に1が存在する時は、対称性を考えなくていいので1個だけ増やせる。
その上で、中央が1かそうでないかによって、置くことの出来る数が変わる。
まず、中央が1である時、すなわちxが奇数の時、他の場所に、1は、0 <= a <= MAX_PLACE( (n-1)/2) ∧ 0 <= b <= ( (n-1)/4) * 2を満たす様な(a, b)で表せる4 * a + 2 * b個であれば置くことが出来る。
中央が1でない時、すなわちxが偶数の時、(n-1)/2が奇数の時、中央に1がないことによって、2 * bのbが2増えるため、これを考慮しないと落ちる。分かってたけど実装しなかったのはカス。
もうちょっと良い方法ないのかなあ。
B. n, m, Cが与えられるので、Σ[i=1..n]Σ[j=1..m] C[i][j] * D{(4X, 4Y), (4j-2, 4i-2)}^2 を最小化するようなX, Yと最小化された値を求めよ。ただしDはユークリッド距離
なんかユークリッド距離とマンハッタン距離を頭の中で取り違えるやらかしが発生したせいでめちゃくちゃ悩んだ。距離に2乗がついてるから次元が分解出来ないンゴねえって感じ。最終的に、定点に対するマンハッタン距離の関数D(X, Y)は2次元で離散な凸関数になっていて、2乗しても凸のままで、凸関数の和は凸関数だから、結局全体として2次元の凸関数だから3分探索すればエエやんけと思って、O(NM * log3/2(N) * log3/2(M))を書いたらTLEして落ちた(概算が10^9なので当たり前、概算しましょう)。でTLEしてオイオイオイとなった時にユークリッド距離であることに気づく。ユークリッド距離の2乗はX, Yの項が分離するから次元が分解できるンゴねえ....
よって適当に上の目的関数をXとYの項に分解して同じように3分探索すると、logが1つ消えて通った。
C. n個の頂点がn-1本の辺によって接続され連結になっていて、このグラフは道である。各辺には人が通れる回数の上限が決まっている。好きな頂点から初めて好きに辺を通って良い時、最大で何回移動することが出来るか。
n-1本の辺の通ることの出来る上限回数を左からE1, E2, ..., En-1とする。
ちょっと考察すると、全体で移動出来る回数は、1 <= a <= b <= c <= d <= n-1を用いて、Σ[i=a..d-1] Ei - |{Ei | Eiが奇数 i=a..b-1)}| - |{Ei | Eiが偶数 i=b..c-1)}| - |{Ei | Eiが奇数 i=c..d)}|と表すことが出来、これの最大を求めれば良いことが分かる。これはどうしてかというと、何度も同じ辺を行き来することが出来るから、とりあえず最大化するためにはi番目の辺を通る時はEi-2回往復してしまうと、残り上限2回をどう通るかによって、行って引き返してきたり、行ったまま帰ってこなかったり、そもそも通らなかったりする(当たり前)。これは何を意味しているかというと、全体の経路はかならず、(1度通って引き返してくる辺たち) (1度通ったきり引き返して来ない辺たち) (1度通って引き返してくる辺たち)という並びになってないとおかしい。要は偶数回通る辺、奇数回通る辺、偶数回通る辺というふうに連続して存在している(もしくはこれらの内存在しない辺の集合があってもいい)。図にするとこんな感じ。まあ自分で書いたら分かると思う。
後は計算量と、初めから上限回数が1の辺の処理が問題になる。
とりあえず後者は一旦無視して考えると、流石にさっきの上の式の4変数を総当りするとO(n^4)になって間に合わないので、自明な変数を削る所から始める。
まずa, dはなくてもいい。これはa, dは左端と右端に設定した方が絶対に通れる回数が大きくなって得だから。
bは流石にないと困るので、これは総当りすることにする(bの総当りでO(n))。
これによって、固定したbに対して、最大の移動回数を与えるようなcを効率良く求めたいと考えられる。
これは結構簡単で、要は「bit列が与えられる。bit列をある場所で分割して、それより左側にあるbitを全て反転させる。この時、立っているbitの数を最小化するにはどの位置で分割すれば良いか。」という問題と同じ。
これは、左から、
diff[idx] := 位置idxまでに出現した1の数と位置idxまでに出現した0の数の差
を累積和的に計算して、segment treeなどに突っ込んでおけば、Range Max Queryによって、maxidx = argmax(diff[idx])が分かって、位置maxidxで反転させると、立っているbitは最小化されて|{Ei | Eiが奇数 i=b..n-1)}| - (diff[maxidx] - diff[b-1])になることが分かる。ちなみに実際には反転させないケースもあるので、|{Ei | Eiが奇数 i=b..n-1)}| - max(diff[maxidx] - diff[b-1], 0)。
さて問題は上限回数が初めから1回であるような辺が含まれている時で、これがかなり罠。なんか上までの考察はすぐ終わったのに本当に嘘&バグらせで辛い思いをした。
さて、上限回数が1回である辺は、当然2回通ることが出来ないから、aからb-1やcからdに含まれては行けない。よって、最も右に存在する1の場所pつまりEp=1となる最大のpを記憶して(1がなければp = 1)、RMQのクエリの区間の左端をpに設定する必要がある(つまり[p, n-1]にクエリを投げる)。これだけだと、RMQを[0, p-2]に飛ばしていないわけだからpより右側の辺を一切通らないケースを考慮していなくて嘘っぽいけど、ある辺iについてそこを通る回数はEi - {0, 1}でこれは常に0以上であるから、さっきも言った通り全ての道を通ったケースが、全ての道を通らないケースに比べて劣ることは絶対にない。
累積和を取ったりindexをいじったりする系の奴、考察が終わっていてもバグりまくるの辛いしどうにかしてくれ。
自分向けメモ: コーナーケースとかヤバそうなケース思いついたらとりあえずメモっとけ。考察もう少し丁寧に。嘘が入らない程度に考察は少しずつ積み重ねる。飛躍出来るだけしない。
Codeforces Round #177 (Div. 1)
受験終わって初回virtual。3完。辛い。
A. k種類の小文字アルファベットから成るちょうど長さnの文字列であって、隣り合う文字が全て異なるものの内、辞書順最小のものは何か。
流石にaから始まるのが辞書順最小で、次は隣り合う文字は異なってないといけないからbで、次はaでと考えると、ababab...となる。後は末尾にk-2種類の文字、それも辞書順最小なので当然'c' ~ 'c'+k-3を登場させると良いけど、ちょっと実装がだるい。k=1とかに気をつける
B. σはS={1,..., n}のS→Sな写像であって、「∀t(1 <= t <= k)について∃x(x∈N)が存在して、σ^x(t)=1」∧「∀t(k < t <= n)についてσ^x(t)=1を満たすxが存在しない」を満たす。σは何通りあるか。
こういった写像はdirected pseudoforestと対応するので、自明に1個のdirected pseudotreeと、{k+1, .. , n}→{k+1, .., n]の任意の写像1つの数え上げ。後者は自明に(n-k)^(n-k)。いちおうdirected pseudotreeと書いたけど、実はこの場合は有向辺と無向辺が一対一に対応して数え上げられるので、pseudotreeでいいはず。なんかpseudotreeといえば、閉路の各頂点に、0個以上の木がくっついてる形をしているわけだけれども、道しかくっついていないという嘘想定を何故かしてしまって死んだ。この良く分からん勘違い、春合宿でもやってしまったけどマジで意味が分からん。
結局やることとしては、σ^x(1)=1となるxが存在するわけだから、pseudotreeの閉路上に頂点1は存在する。後は、どうにでも出来ると思うけど、例えば閉路がt個の頂点から構成されるとすると、閉路上にある頂点は残りt-1個に関しては自由なので(n-1)C(t-1)通り。そしてt-1個の並びは自由なので(t-1)!通り。
後は木をこの閉路につけるわけだけど、ここで、一般に、無向グラフにおいて、十分に大きいnについて、n個の互いに非連結な成分C1 ~ Cnをn-1本の辺で連結にする時、異なる連結のさせ方は(π[i=1..n]Ci) × {(Σ[1..n] |Ci|)^(n-2)}通りある。これは∀iで|Ci|=1のとき、cayleyの公式と一致する。
以上から答えは、(n-k)^(n-k) × [Σ[t=1..k-1]{(t-1)! × (k-1)C(t-1) × t × k^(t-k-1)} + (k-1)!] となる(t == kのとき、t-k-1 = -1なことに注意する)。
C. 0~nの順列P0~Pnについて美しさを Σ[i=0..n] (i⊕Pi)で定義する(⊕はXOR)。可能な美しさの最大値と最大値を与える順列1つを出力せよ。
等式 A⊕B = A+B - ((A&B) << 1)を用いると(ここで&はAND)、Σ[i=0..n] (i⊕Pi) ≦ Σ(i+Pi) = n(n+1)であって、上界がn(n+1)であることが分かる。
貪欲に順列を構成すると、上界を達成することが出来ることを示す。
まず
- 2進数でnがk桁で表されるとすると、k桁で表される全ての整数tについて、t⊕x = 2^k-1となるようなxがただ1つ存在する。これはt&x = 0となるようなxがk-1桁以下で表される整数にただ1つ存在することによる。
- t1⊕x1 = 2^k-1, t2⊕x2 = 2^k-1の時、t1≠t2 ⇒ x1≠x2である
- 上記の条件でt⊕x = 2^k-1となる時、A&B = 0 ⇔ A+B = A⊕Bを考えるとt+x = t⊕x = 2^k-1である。
ここで、n以下でかつk桁で表される全ての整数の集合、すなわち、”離散な"閉区間(面倒くさいのでこう呼ぶ)S = [2^(k-1), n]に対して、T = {x | t∈S, t⊕x = 2^k-1}とすれば、t+x = t⊕xより、Tは離散な閉区間[2^k-1-n, 2^(k-1)-1]であり、S∩T=∅かつS∪T = [2^k-n-1, n]であるから、これらを全て消費すると、離散な閉区間[0, 2^k-n-2]が残るので、残った離散な閉区間に対して、同じ問題を再帰的に解いていくと良い。
こうして構成された順列は、全ての項について、i⊕Pi = i+Piが成り立っているので、上の考察からこれは上界を与えている。
なんか実装力が衰えすぎていたのと、この考察中々にややこしいのでめちゃくちゃなバグが発生しまくった。反省。
自分向けメモ: 貪欲の下界上界を先に意識するパターン忘れない。一回やったら流石にcombinatoricsは解けて...
四則演算するテスト
前書いたスタックを使って
前々から気になってた逆ポーランド記法に変換して処理する四則演算器書いた。
infix2postfix.h
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
enum Priority {NIL, LOW, MIDDLE, HIGH,};
static void check_argument(char **string, stack *st)
{
make_stack(for_string);
char *tmp;
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(*st);
return;
}
*tmp = ' ';
push_stack(st, tmp);
while(1){
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(*st);
return;
}
if(**string == '.' || **string == '_' || **string == '@' || ('A' <= **string && **string <= 'Z') || ('a' <= **string && **string <= 'z')){
*tmp = **string;
push_stack(&for_string, tmp);
++(*string);
}else if(**string == '('){
}else{
while(top_stack(&for_string) != NULL){
push_stack(st,top_stack(&for_string));
pop_stack(&for_string);
}
--(*string);
return;
}
}
}
static int check_constant(char **string, stack *st)
{
make_stack(for_string);
char *tmp;
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(*st);
return;
}
*tmp = ' ';
push_stack(st, tmp);
while(1){
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(*st);
return;
}
if('0' <= **string && **string <= '9'){
*tmp = **string;
push_stack(&for_string, tmp);
++(*string);
}else{
while(top_stack(&for_string) != NULL){
push_stack(st,top_stack(&for_string));
pop_stack(&for_string);
}
--(*string);
return;
}
}
}
static int ret_prio(char str)
{
enum Priority;
if(str == '(' || str == ')')return NIL;
if(str == '+' || str == '-')return LOW;
if(str == '*' || str == '/' || str == '%')return MIDDLE;
if( ('a' <= str && str <= 'z') || ('A' <= str && str <= 'Z') || str == ' ' || str == '_' || str == '@' || str == '.' || ('0' <= str && str <= '9'))return HIGH;
return -1;
}
char *convert(char *formula,char for_convert[])
{
int i = 0;
make_stack(st);
while(*formula != '\0'){
char *tmp;
while(*formula == ' ') ++formula;
if(*formula == '('){
tmp = (char *)malloc(sizeof(char));
*tmp = *formula;
push_stack(&st, (void *)tmp);
}else if(*formula == ')'){
while(*(char *)top_stack(&st) != '('){
if(top_stack(&st) != NULL){
*(for_convert+i) = *(char*)top_stack(&st);
++i;
free(top_stack(&st));
pop_stack(&st);
}else{
puts("error");
break_stack(st);
return NULL;
}
}
pop_stack(&st);
}else if( ('a' <= *formula && *formula <= 'z') || ('A' <= *formula && *formula <= 'Z') || *formula == '_' || *formula == '@' || *formula == '.'){
check_argument(&formula, &st);
}else if('0' <= *formula && *formula <= '9'){
check_constant(&formula, &st);
}else if(*formula == '+' || *formula == '-' || *formula == '*' || *formula == '/' || *formula == '%'){
while(top_stack(&st) != NULL && ret_prio(*formula) <= ret_prio(*(char *)top_stack(&st))){
*(for_convert+i) = *(char*)top_stack(&st);
++i;
free(top_stack(&st));
pop_stack(&st);
}
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(st);
return NULL;
}
*tmp = ' ';
push_stack(&st, (void *)tmp);
tmp = (char *)malloc(sizeof(char));
if(tmp == NULL){
break_stack(st);
return NULL;
}
*tmp = *formula;
push_stack(&st, (void *)tmp);
}else{
break_stack(st);
return NULL;
}
++formula;
}
while(top_stack(&st) != NULL){
if(*(char*)top_stack(&st) == '('){
int m;
puts("error");
break_stack(st);
for(m=0;m!=i;++m){
*(for_convert+m)='\0';
}
return NULL;
}
*(for_convert+i) = *(char*)top_stack(&st);
++i;
free(top_stack(&st));
pop_stack(&st);
}
break_stack(st);
return 0;
}
eval4rules.h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include "stack.h"
double evaluation(char *formula,jmp_buf for_error){
make_stack(st);
double Ret;
char p[20]="";
while(*formula != '\0'){
const char *var_for_atoi;
if('0' <= *formula && *formula <= '9'){
double *num = (double*)malloc(sizeof(double));
if(num == NULL){
break_stack(st);
longjmp(for_error,-2);
}
while('0' <= *formula && *formula <= '9'){
char chr[2] = "";
chr[0] = *formula;
strcat(p,chr);
++formula;
}
var_for_atoi = p;
*num = atof(var_for_atoi);
push_stack(&st,num);
strcpy(p,"");
}else if(*formula == '+' || *formula == '-' || *formula == '*' || *formula == '/'){
double *num = (double*)malloc(sizeof(double));
if(num == NULL){
break_stack(st);
longjmp(for_error,-2);
}
double a,b;
if(top_stack(&st) == NULL){
break_stack(st);
longjmp(for_error,-1);
}else{
a = *(double*)top_stack(&st);
free(top_stack(&st));
pop_stack(&st);
}
if(top_stack(&st) == NULL){
break_stack(st);
longjmp(for_error,-1);
}else{
b = *(double*)top_stack(&st);
free(top_stack(&st));
pop_stack(&st);
}
switch(*formula){
case '+':
*num = a+b;
break;
case '-':
*num = b-a;
break;
case '*':
*num = a*b;
break;
case '/':
*num = b/a;
break;
}
push_stack(&st,num);
++formula;
}else if(*formula != ' '){
break_stack(st);
longjmp(for_error,-1);
}
++formula;
}
if(top_stack(&st) == NULL){
break_stack(st);
longjmp(for_error,-1);
}
Ret = *(double*)top_stack(&st);
free(top_stack(&st));
pop_stack(&st);
break_stack(st);
return Ret;
}
main.c
#include <stdio.h>
#include <setjmp.h>
#include "infix2postfix.h"
#include "eval4rules.h"
int main()
{
int i;
double n;
char formula[300];
char Result[10000] = "";
jmp_buf env;
i = setjmp(env);
if(i == 0){
scanf("%s",formula);
convert(formula,Result);
n = evaluation(Result,env);
printf("n:%f\n",n);
return 0;
}else if(i == -1){
puts("SyntaxError");
return -1;
}else if(i == -2){
puts("Stack memory limits");
return -1;
}
}
テストなので字句解析とかすっ飛ばしてるけど今ちゃんと書いてるので。
あと、エラー処理適当かなと。
stack,queueの実装
出来上がったと思うので一応うp
stack
#define make_stack(x); stack x;x.last=NULL;
#define make_stack_p(x); stack *(x)=(stack*)malloc(sizeof(stack));(x)->last=NULL;
#define break_stack(x); while(top_stack(&x)!=0x00000000){pop_stack(&x);}
#define break_stack_p(x); while(top_stack(x)!=0x00000000){pop_stack(x);}free(x);
struct _Stack{
void *content;
struct _Stack *front;
};
typedef struct{
struct _Stack *last;
}stack;
void *top_stack(stack *st){
if(st->last == NULL){
return (void*)0x00000000;
}
return st->last->content;
}
int push_stack(stack *st, void *variable){
struct _Stack *tmp;
tmp = (struct _Stack*)malloc(sizeof(struct _Stack));
if(tmp == NULL)return -1;
if(st->last == NULL){
tmp->front = NULL;
}else{
tmp->front = st->last;
}
tmp->content = variable;
st->last = tmp;
return 0;
}
void *pop_stack(stack *st){
if(st->last == NULL)return NULL;
if(st->last->front == NULL){
st->last = NULL;
}else{
struct _Stack *tmp = st->last;
st->last = st->last->front;
free(tmp);
}
return NULL;
}
queue
#define make_queue(x) queue x;x.first=NULL
#define make_queue_p(x) queue *(x)=(queue*)malloc(sizeof(queue));(x)->first=NULL
#define break_queue(x) while(top_queue(&x)!=0x00000000){pop_queue(&x);}
#define break_queue_p(x) while(top_queue(x)!=0x00000000){pop_queue(x);}free(x);
struct _Queue{
void *content;
struct _Queue *next;
};
typedef struct{
struct _Queue *first;
struct _Queue *last;
}queue;
void *top_queue(queue *que){
if(que->first == NULL)return 0x00000000;
return que->first->content;
}
int push_queue(queue *que, void *variable){
struct _Queue *tmp;
tmp = (struct _Queue*)malloc(sizeof(struct _Queue));
if(tmp == NULL)return -1;
tmp->content = variable;
if(que->first == NULL)que->first = tmp;
que->last = tmp;
return 0;
}
void *pop_queue(queue *que){
if(que->first == NULL)return NULL;
if(que->first->next == NULL){
que->first = NULL;
que->last = NULL;
}else{
struct _Queue *tmp = que->first;
que->first = que->first->next;
free((void*)tmp);
}
return NULL;
}
scanf
後輩の質問を受けた。
中1は基本的にC言語かJavaの勉強をしていて、AOJ解けって言ってた。
「AOJのVolume5の501がわかりません」
要は、文字列を変換するリストと、文字列が与えられるので変換後の文字列を出力しろと(nの上限決まってなかったりで疑問なんだが)。
んで、その中1はちゃんと自分の書いたソース載せてたのでちょっと書き加えた。
#include <stdio.h>
int main()
{
int n,m,i,j;
char a[1000],b[1000];
while(1){
scanf("%d",&n);
if(n == 0)return 0;
for(i = 0;i < n;i++){
scanf(" %c %c", &a[i], &b[i]);
}
scanf("%d", &m);
for(i = 0;i < m;i++){
char c;
scanf(" %c",&c);
for(j = 0;j < n;j++){
if(c == a[j]){
c = b[j];
break;
}
}
printf("%c",c);
}
puts("");
}
}
ハマったのがscanf。
はじめ、
scanf("%d", &n);
scanf("%c",&c[i]);
という風にしていたんだけれども、
アウトプットミスってるし
入力待ちがどうも想定してるインプットより少ないのでおかしいと思った。
出力よく見てると改行されてるので、scanf("%c\n",&c[i])にした。
すると、アウトプットは正しくなったが、逆にインプットを多く求められた。
結構ハマったあげく、調べてみるとこういうふうにあった。
http://www.pc.uec.ac.jp/sp/hshrkw/edu/program/b1/Ex2-1b.htm
初歩的なことなんだけど忘れてるとハマる。
結局、scanf(" %c",&c[i])にすることでうまく動いた。
気をつけよう(戒め)