メモ

yukicoderでゆるふわgolf

yukicoder No.588 空白と回文

問題はこちら
No.588 空白と回文 - yukicoder

まず線対称の軸となるべき場所を固定する。
(そのような場所は、文字の中心か、文字と文字の間となる)
中心から外側へ向かって、各文字が回文を構成するかどうかをチェックする。
中心に対して対称な位置にある文字が同じでない場合、それは空白にするしかなく、同じ場合は残すことで、最大の長さの文字列を得ることができる。

char s[1010];
l,r,m,k,M,n;
main(){
	n=strlen(gets(s));
	for(m=0;m<2*n;m++){// m/2文字目を軸とする(例えば「1/2文字目」は0文字目と1文字目の間を表す)
		k=0;
		for(l=m/2,r=m-l;l>=0&&r<n;l--,r++)if(s[l]==s[r])k+=l==r?1:2;
		if(M<k)M=k;
	}
	printf("%d",M);
}

この方針で縮めると次のようになる

char s[1010];
l,m,k,M,n;
main(r){
	for(n=strlen(gets(s));m<2*n;l=++m/2,M=fmax(M,2*k-m%2))
		for(r=m-l,k=0;~l&&s[r];)k+=s[l--]==s[r++];
	printf("%d",M);
}

別の方針の方が短くなる。
まず軸を固定するところは同じだが、そのあとの探索を「元の文字列の各文字について」行っていく。
もとの文字列でi番目の文字は、対称の軸がm/2にあるとき、m-i文字目と対応するので、これが一致するかどうかを見る

char s[1010];
n,m,M,i,k;
main(){
	n=strlen(gets(s));
	for(m=0;m<2*n;m++){//対称の軸
		k=0;
		for(i=0;i<n;i++)if(m>=i&&m-i<n&&s[i]==s[m-i])k++;
		//i文字目のうつる先が元の文字列の中であり、文字が一致している時にのみ、回文を構成する
		if(M<k)M=k;
	}
	printf("%d",M);
}

これをぎゅっとする

char s[1010];
m,M,i,k;
main(n){
	for(n=strlen(gets(s));m<2*n;M<c?M=c:0,c=i=0,m++)
		for(;i<n;i++)c+=m>=i&m-i<n&s[i]==s[m-i];
	printf("%d",M);
}

for文圧縮

for(n=strlen(gets(s));m<2*n;M<c?M=c:0,c=i=0,m++)for(;i<n;i++)c+=m>=i&m-i<n&s[i]==s[m-i];
for(n=strlen(gets(s));i<n?c+=m>=i&m-i<n&s[i]==s[m-i],++i:(M<c?M=c:0,c=i=0,++m<2*n););
for(n=strlen(gets(s));c+=m>=i&m-i<n&s[i]==s[m-i],++i<n?:(M<c?M=c:0,c=i=0,++m<2*n););

最後に配列サイズをごまかして完成

char s[9];
m,M,i,c;
main(n){for(n=strlen(gets(s));c+=m>=i&m-i<n&s[i]==s[m-i],++i<n||(M<c?M=c:0,c=i=0,++m<2*n););printf("%d",M);}

126B

yukicoder No.571 3人兄弟(その2)

問題はこちら
No.571 3人兄弟(その2) - yukicoder

その1と同様
a[身長][体重]=人 という配列を作っても良いが
a[身長*(大きな数)-体重]=人 とすることで1次元配列にできる

a[30010],t,s;
main(){
	for(int i=1;i<=3;i++){
		scanf("%d%d",&t,&s);
		a[t*101-s]=i;
	}
	for(int i=30000;--i;)if(a[i])printf("%c\n",'A'+a[i]-1);
}

体重の最大値が100なので(大きな数)は101以上である必要があるように思えるが、実際には体重の値は51通りしかないので、51以上の値でよい。
これにより配列サイズが126*257以下で良くなり、1B短縮できる

a['~~'],p,q;main(i){for(i=1<<14;--i;a[i]&&printf("%c\n",68+a[i]))a[~scanf("%d%d",&p,&q)?p*64-q:0]=-i%4;}

104B

yukicoder No.570 3人兄弟(その1)

問題はこちら
No.570 3人兄弟(その1) - yukicoder

場合分けでも簡単に解けるが、別の方針で考える
a[身長]=人 という配列を作って身長が大きい方からチェックする

a[999],t;
main(){
	for(int i=1;i<=3;i++){
		scanf("%d",&t);
		a[t]=i;
	}
	for(int i=200;i>100;i--)if(a[i])printf("%c\n",'A'+a[i]-1);
}

これをぎゅっとする。atoi(NULL)はセグフォになるらしいので適当に回避する

a[999];main(i){for(i=208;--i;a[i]&&printf("%c\n",68+a[i]))a[atoi(gets(a)?:"")]=-i%4;}

85B

yukicoder No.587 七対子

問題はこちら
No.587 七対子 - yukicoder

各文字が何個あるかを数える。
ちょうど2個ある文字がちょうど6種類あるとき、その時に限り、残りの1種類と同じ文字を加えることで7ペアにできる

a[999],ans,c,i;
main(){
	for(i=0;i<13;i++)a[getchar()]++;
	for(i='a';i<='z';i++){
		if(a[i]==2)c++;
		if(a[i]==1)ans=i;
	}
	if(t==6)printf("%c",x);
	else printf("Impossible");
}

2つのfor文のindexの範囲が被っていないので、そのまま合体させることができる

a[999],t,x;
main(i){
	for(;i<128;a[++i]==1?x=i:0)a[getchar(t+=a[i]==2)]++;
	printf(t-6?"Impossible":"%c",x);
}

104B

別方針も考えたが110Bにしかならず

a[999],s,t;
main(x){
	for(;(x=getchar())-10;s+=1<<4/++a[x])t^=1<<x;
	printf(s-136?"Impossible":"%c",x=log2(t)+96);
}
//s+=++a[x]==2 //aabbccddeefffで死ぬ
//s+=2/a[x] //1+1=2+0なのでaabbccdd eeefghで死ぬ
//s+=1<<2/a[x] //2+2+2=4+1+1なのでaabbcc ddddefghで死ぬ

2018/01/25追記

aが余っているのでxの代わりにつかうことができる。またそれにより出力がputsでよくなるため大幅に短縮できる。

それから自明な1Bの短縮を合わせて計9B短縮

a[999],t;
main(i){
	for(;i<128;a[++i]&1?*a=i:0)a[getchar(t+=a[i]==2)]++;
	puts(t-6?"Impossible":a);
}

95B

yukicoder No.589 Counting Even

問題はこちら
No.589 Counting Even - yukicoder

nを2進法で表したときの1の個数をpopcnt(n)とおく
結論からいうとN+1-2^popcnt(N)が答え
方針はいろいろあるがいずれの場合も奇数が2^popcnt(N)個あることを示す

パスカルの三角形
(以下、「n段目の左からm番目」という表現で、_{n}C_{m}に対応するマスを指すものとする)
パスカルの三角形 偶奇 - Google 検索

       1        ←0段目
      1 1       ←1段目
     1 0 1      ←2段目
    1 1 1 1     ←3段目
   1 0 0 0 1    ←4段目
  1 1 0 0 1 1   ←5段目
 1 0 1 0 1 0 1  ←6段目
1 1 1 1 1 1 1 1 ←7段目

パスカルの三角形を睨むと、規則的な構造をしていることが分かる。
具体的には「(2^k)段目以降は、それより前の段の繰り返しである」(★)(証明は後述)
このことから、求めるべき答えは ans[n]=2*ans[n-2^k] という再帰式で求まる(ただし2^kはn以下の最大2冪)
この漸化式は「nの最上位bitを消して値を2倍する」というものなので、ans[0]=1とあわせて、結局ans[n]=2^popcnt(n)になることが分かる
★の証明の概略
(1)ある段が全て奇数なら、その直下の段は、両端の1を除いて全て偶数である
(2)左からnマス目の数は、k段下では左からn~(n+k)マス目までのk+1マスにのみ影響する
(3)(1)(2)から帰納的に(2^k-1)段目は全て奇数であることがわかり、主張が示せる

・lucasの定理
主張と証明は下記ページに丸投げ
Lucasの定理とその証明 | 高校数学の美しい物語
以下、ni,miという記号は上記サイトにならう。
定理より
nCmが奇数
⇔「mi=1⇒ni=1」
⇔「mの2進法表記で1である位」は「nの2進法表記で1である位」に含まれる
よって明らかにそのようなmは2^popcnt(n)個

・legendreの定理
自分が実際に解いた方針はこれ
主張と証明は下記ページに丸投げ
ルジャンドルの定理(階乗が持つ素因数のべき数) | 高校数学の美しい物語
先ほど同様にni,miの記号を定める。
k!が2で割れる回数をf(k)とおく。
_{n}C_{m}=\dfrac{n!}{m!(n-m)!}が奇数
⇔f(n)=f(m)+f(m') (m'=n-mとおいた)
ここで一般に整数a,bについて\lfloor\dfrac{a+b}{2}\rfloor\geq\lfloor\dfrac{a}{2}\rfloor+\lfloor\dfrac{b}{2}\rfloorであり、等号成立は「a,bの少なくとも一方が偶数」が必要十分
よってこのことから
f(n)=f(m)+f(m')
\forall i.\lfloor\dfrac{n}{2^i}\rfloor=\lfloor\dfrac{m}{2^i}\rfloor+\lfloor\dfrac{m'}{2^i}\rfloor
⇔∀i.miとm'iの少なくとも一方は0
ここでn=m+m'であったことから、(足し算の際に繰り上がりが起こらないので)「mの2進法表記で1である位」は「nの2進法表記で1である位」に含まれることが分かる
そのようなmは2^popcnt(n)個

(直感的には、「nが2^kの箱に最適に入っている状態を想像する(例えば13なら8,4,1の箱)。nをmとm'に分けたとき、もし大きさ2^iの箱を分解することがあれば、2^iで割った時の値が1小さくなってしまうので困る。つまり、各箱についてmとm'のどちらが取るかを決めるしかない。よって2^popcnt(n)」という感じ)


実装はpopcount関数を使うだけ

main(long n){scanf("%ld",&n);printf("%ld",n+1-(1LL<<__builtin_popcountll(n)));}

79B