2022年11月24日木曜日

配列の要素の和

配列の和を求める

ABC272-A

問題概要

配列aが与えられたとき、aの要素の総和を求めよ。

解答

解答1

Arrayクラスにsumメソッドが用意されているので、それを使えばよい。

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
puts a.sum
    

上が本番で提出したコード。

解答2

Arrayクラスのinjectメソッドを使う。

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
puts a.inject {|result, item| result + item }
    

自分で、要素を一個ずつ足す処理を書いていってもいい。

解答3

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
sum=0
a.size.times do |i|
    sum+=a[i]
end
puts sum
    

解答4

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
sum=0
a.each do |item|
    sum+=item
end
puts sum
    

実行時間は、

実行時間(ms)
解答169
解答259
解答359
解答458

となった。

Cでやったのは以下。実行時間は 1 ms。

int isum(int* a, int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N 100000002
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    char* s3=(char*)malloc(sizeof(char)*N);
    fgets(s3,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s3,a,n,N);
    printf("%d\n",isum(a,n));
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
int isum(int* a, int n){
    int sum=0;
    for(int i=0;i<n;i++)sum+= *(a+i);
    return sum;
}
    

配列の一部の和(インデックスを指定)

ABC290-A

問題概要

配列aの、b[1]..b[m]番目の和を求めよ。

解答

n,m=gets.chomp.split(" ").map(&:to_i)
a=gets.chomp.split(" ").map(&:to_i)
b=gets.chomp.split(" ").map(&:to_i)
ans=0
b.each do |it|
    ans+=a[it-1]
end
puts ans
    

上が本番で提出したコード。以下はCで書いたもの。

int selected_isum(int* a, int* idxs,int n,int m);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N 100000002
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int j=0;
    while(*(s1+j)!=32)j++;
    j++;
    int m=readint(s1+j);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*N);
    fgets(s2,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s2,a,n,N);
    free(s2);
    char* s3=(char*)malloc(sizeof(char)*N);
    fgets(s3,N,stdin);
    int * b=(int*)malloc(sizeof(int)*m);
    input_iarray(s3,b,m,N);
    free(s3);
    printf("%d\n",selected_isum(a,b,n,m));
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
int selected_isum(int* a, int* idxs,int n,int m){
    int sum=0;
    for(int i=0;i<m;i++){
        int idx=*(idxs+i)-1;
        sum+= *(a+idx);
    }
    return sum;
}
    

群ごとの和

ABC307-A

問題概要

与えられた配列を7個ごとの群に区切り、各群の数の和を順に出力せよ。

解答

nn=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
n=nn*7
r=n%7
if r>0
    b_size=(n/7+1)
else
    b_size=(n/7)
end
b=Array.new(b_size,0)
(n/7).times do |i|
    7.times do |j|
        b[i]+=a[i*7+j]
    end
end
if r>0
    r.times do |j|
        b[i+1]+=a[(n/7)*7+j]
    end
end
puts b.join(" ")
    

最初、nは与えられる配列の要素数だと思って書き進めたのだが、そうではなく、群の数だった。更に、7で割って余りが生じるかどうかの場合分けも不要であった。

上が本番で提出したコード。以下はCで書いたもの。

void print_ivector(int* a,int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N 100000002
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*N);
    fgets(s2,N,stdin);
    int* a=(int*)malloc(sizeof(int)*n*7);
    input_iarray(s2,a,n*7,N);
    free(s2);
    int* weekly=(int*)malloc(sizeof(int)*n);
    for(int i=0;i<n;i++)*(weekly+i)=0;
    int j=0;
    int wn=0;
    while(j<7*n){
        *(weekly+wn)+= *(a+7*wn+j%7);
        if (j%7==6)wn++;
        j++;
    }
    print_ivector(weekly,n);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

条件を満たす要素の和

ABC328-B

問題概要

整数からなる配列が与えられる。値が一定値以下である要素の和を出力せよ。

解答

n,x=gets.chomp.split(&quot; &quot;).map(&amp;:to_i)
a=gets.chomp.split(&quot; &quot;).map(&amp;:to_i)
ans=0
(0...n).each do |i|
    if a[i]&lt;=x
        ans+=a[i]
    end
end
puts ans
    

上が本番で提出したコード。以下はCで書いたもの。

int if_smaller(int* np,int x);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N 100000002
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int j=0;
    while(*(s1+j)!=32)j++;
    j++;
    int x=readint(s1+j);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*N);
    fgets(s2,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s2,a,n,N);
    free(s2);
    int ans=0;
    for(int i=0;i<n;i++)ans+= (if_smaller(a+i,x));
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
int if_smaller(int* np,int x){
    if (*np<=x)return *np;
    else return 0;
}
    

2022年11月13日日曜日

要素の数を数える問題・正の字カウント・ハッシュ

数を数える

今回は、具体的なABCの問題から入るのではなく、一般的な場合から。

与えられたデータの中に、区別できるものがそれぞれ何個ずつあるかを数える。。

まずは、要素の種類が分かっている場合

例えば、o,xだけとか、"Yes", "No"だけとか、一桁の数字だけとか。

もっと多くて、都道府県とか、アメリカ合衆国の州とかいう場合も考えられる。

こういう時には、あらかじめハッシュテーブルを用意しておいて、ハッシュのキーと同じものが出てきたら、そのキーのの値を一つずつ増やしていけばよい。

設例1

データ oxooxoxxxxoo が標準入力から与えられるとする。この中に、o と x はそれぞれ何個ずつあるか。

解答

h={o:0,x:1}
a=gets.chomp.split("").to_a
a.each do |it|
    h[it.to_sym]+=1
end
puts "o:#{h[:o]}"
puts "x:#{h[:x]}"    
    

実行結果は、

o:3
x:3
    

次に、出現する要素があらかじめわかっていない場合どうするかを見ていく。

あらかじめわかっていない要素の数を数える問題

[a,a,a,b,b,c]のような、特異な要素がそれぞれ複数個ありうる配列が与えられて、その特異な要素ごとにその数を数える、というタイプの問題である。実用度はかなり高い。手作業でやる時には、「順番に見ていって、新しい要素が出てきたらその名前を書いて、その下に正の字の最初の一画を書き、二回以上出現した要素については、既に書いた名前の下の正の字の画数を増やしていく」、ということをするのだが、それをコンピューターでやろうとするとどうするか。

データ

μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος
οὐλομένην, ἣ μυρί᾽ Ἀχαιοῖς ἄλγε᾽ ἔθηκε,
πολλὰς δ᾽ ἰφθίμους ψυχὰς Ἄϊδι προΐαψεν
ἡρώων, αὐτοὺς δὲ ἑλώρια τεῦχε κύνεσσιν
5οἰωνοῖσί τε πᾶσι, Διὸς δ᾽ ἐτελείετο βουλή,
ἐξ οὗ δὴ τὰ πρῶτα διαστήτην ἐρίσαντε
Ἀτρεΐδης τε ἄναξ ἀνδρῶν καὶ δῖος Ἀχιλλεύς.
τίς τ᾽ ἄρ σφωε θεῶν ἔριδι ξυνέηκε μάχεσθαι;
の中に出てくる文字を文字ごとに集計せよ。(アクセント記号、気息記号が異なれば別の文字として扱う。)

解答

一つの案は、配列を使うやり方で、

s=<<'EOS'
μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος
οὐλομένην, ἣ μυρί᾽ Ἀχαιοῖς ἄλγε᾽ ἔθηκε,
πολλὰς δ᾽ ἰφθίμους ψυχὰς Ἄϊδι προΐαψεν
ἡρώων, αὐτοὺς δὲ ἑλώρια τεῦχε κύνεσσιν
5οἰωνοῖσί τε πᾶσι, Διὸς δ᾽ ἐτελείετο βουλή,
ἐξ οὗ δὴ τὰ πρῶτα διαστήτην ἐρίσαντε
Ἀτρεΐδης τε ἄναξ ἀνδρῶν καὶ δῖος Ἀχιλλεύς.
τίς τ᾽ ἄρ σφωε θεῶν ἔριδι ξυνέηκε μάχεσθαι;
EOS
list=[]
number=[]
(0...s.size).each do |i|
    if s[i]=="\n" || s[i]==" "
        next
    elsif list.include?(s[i])
        number[list.index(s[i])]+=1
    else
        list.push(s[i])
        number.push(1)
    end
end
list.size.times do |i|
    puts "#{list[i]} : #{number[i]}"
end
    

実行結果は、

μ : 5
ῆ : 2
ν : 16
ι : 14
ἄ : 4
ε : 22
δ : 12
θ : 5
ὰ : 4
Π : 1
η : 7
λ : 11
ϊ : 2
ά : 2
ω : 4
Ἀ : 4
χ : 6
ο : 14
ς : 11
ὐ : 2
έ : 2
, : 5
ἣ : 1
υ : 5
ρ : 10
ί : 6
᾽ : 5
α : 10
ῖ : 3
γ : 1
ἔ : 2
κ : 4
π : 4
ἰ : 2
φ : 2
ψ : 2
Ἄ : 1
ΐ : 2
ἡ : 1
ώ : 2
τ : 14
ὺ : 1
ὲ : 1
ἑ : 1
ῦ : 1
ύ : 2
σ : 8
5 : 1
ᾶ : 1
Δ : 1
ὸ : 1
ἐ : 3
β : 1
ή : 2
ξ : 3
ὗ : 1
ὴ : 1
ῶ : 3
ἀ : 1
ὶ : 1
. : 1
; : 1
のようになった。

もう一つのやり方として、rubyにはハッシュという便利なクラスが用意されているので、それを使うもの。実際に問題を解く際にはたいていこちらでやっているので、これについてはそちらの実例を参照。


数を数える

ABC240-B

問題概要

長さ N の正整数列 aには何種類の整数が現れるか。

解答

n=gets.to_i
a=gets.split.map(&:to_i)
b=[]
c=[]
n.times do |i|
    if b.include?(a[i])
        c[b.index(a[i])]+=1
    else
        b.push(a[i])
        c[b.index(a[i])]=1
    end
end
puts b.size
    

上が本番で提出したコード。Cで最初にやったのが以下。(REになった。)

void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N 2e9
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*N);
    fgets(s2,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s2,a,n,N);
    free(s2);
    int max=0;
    for(int i=0;i<n;i++){
        if (*(a+i)>max)max=*(a+i);
    }
    int * nums=(int*)malloc(sizeof(int)*(max+1));
    for(int i=0;i<=max;i++)*(nums+i)=0;
    for(int i=0;i<n;i++){
        int num=*(a+i);
        *(nums+num)+=1;
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if (*(nums+i))ans++;
    }
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
    

サンプル3件ではACになるが、他のテストケースでは全てREになった。

Paiza.ioにテストケースのデータを入れてやってみると、こちらでもエラーになる。一旦コード全体をコメントアウトし、少しずつコメントアウトを外しながらエラーが起る場所を探してみると、問題の箇所は25行目(*(nums+num)+=1;)のようだ。

だが、メモリは十分に確保しているはずなのに、どうしてエラーになるのか分からず、StackOverflowで質問。

原因は、メモリ制限がPaiza.ioで512MB, AtCoderでも1024MBなのに対し、制約上のa[i]の最大値10^9個のint型メモリを確保しようとすると4000MBになってしまうのが原因のようだ。それで、mallocがNULLを返すが、そこにそのままデータを入れようとしてランタイムエラーになったらしい。(attakeiさん、actorbugさん、コメント・回答有難うございました。)

それで、結局二分木を使って出現した数のリストを作ることに。と言っても、少し前に作ったABC329-Bのコードがほぼそのまま使えたので、新しく書いたところはほとんどない。それが以下。

typedef struct {
    int v;
    struct tn *l;
    struct tn *r;
} tn;
int push_element(tn* f, int* ar);
tn* create_tn(int v);
void insert_tn(int v, tn *node);
int seccond_largest(tn* baum, int prev);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N1 20
#define N 100000002
tn *root=NULL;
int cnt=0, ar_idx=0;
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    char* s3=(char*)malloc(sizeof(char)*N);
    fgets(s3,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s3,a,n,N);
    for(int i=0;i<n;i++){
        insert_tn(*(a+i),root);
    }
    int * b=(int*)malloc(sizeof(int)*cnt);
    push_element(root,b);
    printf("%d\n",cnt);    
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
tn* create_tn(int v){
    tn* neu;
    neu=(tn*)malloc(sizeof(tn));
    if(neu==NULL){
        printf("Memory allocation failure.");
        exit(0);
    }
    neu->l=NULL;
    neu->r=NULL;
    neu->v=v;cnt++;
    return neu;
}
void insert_tn(int v, tn *node){
    if(node==NULL){
        root=create_tn(v);
        return;
    } else if (v == node->v){
        return;
    } else if (v < node->v){
        if (node->l !=NULL){
            insert_tn(v,node->l);
        } else {
            node->l = create_tn(v);
        }
    } else if (node->v < v){
        if (node->r !=NULL){
            insert_tn(v,node->r);
        } else {
            node->r = create_tn(v);
        }
    }
    return;
}
int push_element(tn* f, int* ar){
    if(f->l!=NULL){
        push_element(f->l,ar);
    }
    *(ar+(ar_idx++))=f->v;
    if(f->r!=NULL){
        push_element(f->r,ar);
    }
} 
    

ABC263-A

問題概要

フルハウスかどうか判定(正の字カウント)

ABC263-A

問題概要

5つの数がが与えられる。それらが、それぞれ3個と2個の二種類の数字からなるかどうか判定せよ。

解答

本番では別のやり方でやったのだが、これは正の字カウントでやった方が素直だし早い。

a=gets.chomp.split(" ").map(&:to_i)
h=Hash.new
a.each do |item|
    if h.has_key?(item)
        h[item]+=1
    else
        h[item]=1
    end
end
ss=h.size
if ss!=2
    puts "No"
    exit
end
hv=h.values
if hv[0]==2 && hv[1]==3 || hv[0]==3 && hv[1]==2
    puts "Yes"
else
    puts "No"
end
    

Cで書いたのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 22
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if (s1==NULL){
        printf("memory allocation to s1 failed.\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int* a=(int*)malloc(sizeof(int)*5);
    if (a==NULL){
        printf("memory allocation to a failed.\n");
        exit(1);
    }
    int i=0,j=0;
    for(;i<4;i++){
        *(a+i)=readint(s1+j);
        while(*(s1+j)!=32)j++;
        j++;
    }
    *(a+i)=readint(s1+j);
    int* r=(int*)malloc(sizeof(int)*14);
    if (a==NULL){
        printf("memory allocation to r failed.\n");
        exit(1);
    }
    for(i=0;i<=13;i++)*(r+i)=0;
    for(i=0;i<5;i++){
        int t=*(a+i);
        *(r+t)+=1;
    }
    int cnt=0;
    for(i=1;i<=13;i++){
        if(*(r+i))cnt++;
    }
//    printf("%d\n",cnt);
    if (cnt!=2){
        printf("No\n");
        exit(0);
    }else{
        int n1=0,n2=0;
        for(i=1;i<=13;i++){
            if(*(r+i)){
                if (n1==0)n1=*(r+i);
                else n2=*(r+i);
            }
        }
//        printf("n1=%d, n2=%d\n",n1,n2);
        if ((n1==3 && n2==2) || n2==3 && n1==2){
            printf("Yes\n");
            exit(0);
        }else{
            printf("No\n");
            exit(0);
        }
    }
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

昨日やったABC268-Aのコードがかなり流用できたので、今回はmallocのNULLチェックも入れてみた。


ABC268-A

問題概要

5個の数字が与えられる。この中に数字が何種類あるか答えよ。

解答

a=gets.chomp.split(" ").map(&:to_i)
h=Hash.new
a.each do |item|
    if h.has_key?(item)
        h[item]+=1
    else
        h[item]=1
    end
end
puts h.size
    

空ハッシュを作っておき、配列の要素をはじめから順番に見ていって、もしハッシュのキーにその要素が既にあれば、その値を一増やし、もしなければ新しいキーを追加し、その値を1にしている。問題の答えは、最終的なハッシュのサイズになる。

もっとも、この問題の場合、各キーの個数は必要なく、ユニークな要素が何種類あるか分かればそれでいいから、

a=gets.chomp.split(" ").map(&:to_i)
au=a.uniq!
puts au.size
    

としてもよい。

Cで書いたのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 22
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int* a=(int*)malloc(sizeof(int)*5);
    int i=0,j=0;
    for(;i<4;i++){
        *(a+i)=readint(s1+j);
        while(*(s1+j)!=32)j++;
        j++;
    }
    *(a+i)=readint(s1+j);
    int* r=(int*)malloc(sizeof(int)*101);
    for(i=0;i<=100;i++)*(r+i)=0;
    for(i=0;i<5;i++){
        int t=*(a+i);
        *(r+t)+=1;
    }
    int ans=0;
    for(i=0;i<=100;i++){
        if(*(r+i))ans++;
    }
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

数を数える

ABC287-A

問題概要

ForまたはAgainstからなる文字列がn個与えられる。Forが過半数より多いか否か判定せよ。

解答

n=gets.to_i
count=0
n.times do |i|
    s=gets.chomp
    if s=="For"
        count+=1
    end
end
if count>n/2
    puts "Yes"
else
    puts "No"
end
        

上が本番で提出したコード。Cで書いたのが以下。

int scmp(char *s, char *t);
int is_nonletter(char *c);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define N2 1000
#define N 100000002
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    char* s=(char*)malloc(sizeof(char)*N2);
    for(int i=0;i<n;i++){
        fgets(s+10*i,10,stdin);
    }
/*    for(int i=0;i<n;i++){
        printf("%s\n",s+10*i);
    }*/
    const char* ar[2]={"For","Against"};
//    printf("%s\n",ar[0]);
    int yes=0,no=0;
    for(int i=0;i<n;i++){
        if(scmp(s+10*i,ar[0])==0)yes++;
        else if(scmp(s+10*i,ar[1])==0)no++;
    }
    if (yes>no){
        printf("Yes");
    }else{
        printf("No");
    }
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
int scmp(char *s, char *t){
    int i=0;
    //printf("s=%s,t=%s\n",s,t); 
    int flag=4;
    while(1){
        if (is_nonletter(s+i) && is_nonletter(t+i))flag=0;
        else if (is_nonletter(s+i) && !is_nonletter(t+i))flag=-2;
        else if (!is_nonletter(s+i) && is_nonletter(t+i))flag=2;
        else if(*(s+i)>*(t+i))flag=1;
        else if(*(s+i)<*(t+i))flag=-1;
        else if(*(s+i)==*(t+i))i++;
        else flag=-2;
        if (flag!=4)return flag;
    }
    return flag;
}
int is_nonletter(char *c){
    if (*c<=32 || *c==127)return 1;
    else return  0;
}
        

条件に一致するものの数を数える

ABC287-B

問題概要

数字文字列の配列s(文字数6),t(文字数3)がある。配列sに含まれる文字列のうち、その4~6文字目が配列tに含まれる文字列と一致するものがいくつあるか求めよ。

解答

n,m=gets.chomp.split(" ").map(&:to_i)
s=[]
t=[]
count=0
(0...n).each do |i|
    s[i]=gets.chomp
end
(0...m).each do |i|
    t[i]=gets.chomp
end
(0...n).each do |i|
    (0...m).each do |j|
        if s[i].slice(3,3)==t[j]
            count+=1
            break
        end
    end
end
puts count
    

上が本番で提出したコード。Cで書いたのが以下だが、まずはWAになったコード。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define NS 8000
#define NT 5000
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int j=0;
    while(*(s1+j)!=32)j++;
    j++;
    int m=readint(s1+j);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*NS);
    char* s3=(char*)malloc(sizeof(char)*NT);
    for(int i=0;i<n;i++){
        fgets(s2+i*6,8,stdin);
    }
    for(int i=0;i<m;i++){
        fgets(s3+i*3,5,stdin);
    }
    int ans=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            int flag=1;
            for(int k=0;k<3;k++){
                if (*(s2+i*6+3+k)!=*(s3+j*3+k)){
                    flag=0;
                    break;
                }
                if (flag==0)break;
            }
            if (flag==1)ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

WAになるテストケースを見てみると、tの要素に重複するものがある。つまり、この問題で問われているのは、「Sの要素のうち、tの要素のいずれかと一致するものがいくつあるか。」なので、s[i]がtの要素のうち2つ以上に一致しても、それは一回とか添えなければいけないのである。

すると、まずはtをユニークな配列に作り直さなければならないのか、これをCでやるとなると意外と面倒だなど思いつつ、まずはrubyで書いたときはどうしたか見てみることになる。すると、何か特別な処理をしているようには見えない。これは一体どういうことだ、としばらく悩んだ。(rubyでのコードを書いたのは一年近く前のことなので、その特に何を考えていたかなど覚えていない。)

しばらくして、ようやく、rubyで書いたコードでは、一致するものが見つかった時点でtの要素を回すループを抜けていることに気づく。こうすればよかったのが。意外と単純な解法があった。(なお、rubyではuniqという便利なメソッドがあるので、tを重複要素の無いものにすることも簡単にできる。)それでやり直してACになったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 20
#define NS 8000
#define NT 5000
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int j=0;
    while(*(s1+j)!=32)j++;
    j++;
    int m=readint(s1+j);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*NS);
    char* s3=(char*)malloc(sizeof(char)*NT);
    for(int i=0;i<n;i++){
        fgets(s2+i*6,8,stdin);
    }
    for(int i=0;i<m;i++){
        fgets(s3+i*3,5,stdin);
    }
    int ans=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            int flag=1;
            for(int k=0;k<3;k++){
                if (*(s2+i*6+3+k)!=*(s3+j*3+k)){
                    flag=0;
                    break;
                }
                if (flag==0)break;
            }
            if (flag==1){
                ans++;
                break;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

列ごとに特定の値の要素を数える

ABC274-B

問題概要

要素が#または.である行列が与えられる。各列の#の数を順に出力せよ

解答

n,m=gets.chomp.split(" ").map(&:to_i)
a=[]
ans=Array.new(m,0)
n.times do |i|
    a[i]=gets.chomp.split("").to_a
    m.times do |j|
        if a[i][j]=="#"
            ans[j]+=1
        end
    end
end
puts ans.join(" ")
    

上が本番で提出したコード

他のやり方としては、転置行列を作って、各行の要素数を数えるというものも考えられる。そうすると、Arrayクラスのcountメソッドが使える。

n,m=gets.chomp.split(" ").map(&:to_i)
a=[]
b=Array.new(m){Array.new(n,"")}
n.times do |i|
    a[i]=gets.chomp.split("").to_a
    m.times do |j|
        b[j][i]=a[i][j]
    end
end
m.times do |i|
    print b[i].count("#")
    if i!=m-1
        print(" ")
    end
end
puts ""
    

ただ、最初のコードの実行時間が285msなのに対し、下のコード475msと、実行時間が約1.7倍に増えている。そのうえメモリ使用量も4%くらい増えているから、本番で敢えてこれを使うメリットはないだろう。

Cでやったのが以下。

void print_ivector(int* a,int n);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N 1e6
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    int h=readint(s);
    int i=0;
    while(*(s+i)!=32)i++;
    i++;
    int w=readint(s+i);
    free(s);
    //printf("%d %d\n",h,w);
    char* s2=(char*)malloc(sizeof(char)*N+2);
    for(int j=0;j<h;j++){
        char* t=(char*)malloc(sizeof(char)*(w+2));
        fgets(t,w+2,stdin);
        for(int k=0;k<w;k++)*(s2+j*w+k)=*(t+k);
    }
    /*
    for(int j=0;j<h;j++){
        for(int k=0;k<w;k++){
            printf("%c",*(s2+j*w+k));
        }
        printf("\n");
    }
    */
    int* a=(int*)malloc(sizeof(int)*w);
    for(int j=0;j<w;j++){
        int temp=0;
        for(int k=0;k<h;k++){
            if (*(s2+k*w+j)==35)temp++;
        }
        *(a+j)=temp;
    }
    print_ivector(a,w);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
    }
    

文字列中の文字の出現回数を数える

ABC279-A

問題概要

vとwからなる文字列が与えられる。下に尖った個所が何カ所あるかを求めよ。

解答

s=gets.chomp.split("").to_a
ans=0
s.size.times do |i|
    if s[i]=="v"
        ans+=1
    elsif s[i]=="w"
        ans+=2
    end
end
puts ans
    

上が本番の提出コード。一文字ずつ見ていって、vが出てきたら1、wが出てきたら2を答えに加算している。

Cでやったのが以下。

#include<stdio.h>
#include<stdlib.h>
#define N 102
int main(void){
    int ans=0;  
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    for(int n=0;n<N;n++){
        if (*(s+n)==118){
            ans+=1;
        }
        if (*(s+n)==119){
            ans+=2;
        }
    }
    printf("%d\n",ans);
    return 0;
}

行列中の特定の要素の数を数える。

ABC280-A

問題概要

.または#を要素とする行列が与えられる。#が何個あるか出力せよ。

解答

h,w=gets.chomp.split(" ").map(&:to_i)
ans=0
h.times do |i|
    t=gets.chomp.split("").to_a
    c=0
    w.times do |j|
        if t[j]=="#"
            c+=1
        end
    end
    ans+=c
end
puts ans
    

上が本番の提出コード。Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N 7
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N);
    fgets(s1,N,stdin);
    int i=0;
    int h=readint(s1);
    while(*(s1+i)!=32)i++;
    i++;
    int w=readint(s1+i);
    free(s1);
//    printf("%d %d\n",h,w);
    int ans=0;
    for(i=0;i<h;i++){
        char* s2=(char*)malloc(sizeof(char)*(w+2));
        fgets(s2,(w+2),stdin);
        for(int j=0;j<w;j++){
//            printf("%c",*(s2+j));
            if(*(s2+j)==35)ans++;
        }
//        printf("\n");
    }
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ABC295-C

問題概要

数字を要素とする配列が与えられる。同じ数字のペアは何個作れるか。

解答

「正の字カウント」を使えばよい。

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
h=Hash.new
a.each do |item|
    if h.has_key?(item)
        h[item]+=1
    else
        h[item]=1
    end
end
count=0
h.each_value do |v|
    count+=v/2
end
puts count
    

上が本番の提出コードCでやったのが以下。まずは、以下のコードでTLEになった。

typedef struct {
    int key;
    int v;
    struct tn *l;
    struct tn *r;
} tn;
void print_ivector(int* a,int n);
int tree_to_pa(tn* f, int* a1, int* a2);
tn* create_tn(int key);
void insert_tn(int key, tn *node);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 8
#define N 1e7
tn *root=NULL;
int cnt=0,ar_idx=0;
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if (s1==NULL){
        printf("memory allocation to s1 failed.\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    char* s=(char*)malloc(sizeof(char)*N);
    if (s==NULL){
        printf("memory allocation to s failed.\n");
        exit(1);
    }
    fgets(s,N,stdin);
    int* a=(int*)malloc(sizeof(int)*n);
    if (a==NULL){
        printf("memory allocation to a failed.\n");
        exit(1);
    }
    int i=0,j=0;
    for(;i<n-1;i++){
        *(a+i)=readint(s+j);
        while(*(s+j)!=32)j++;
        j++;
    }
    *(a+i)=readint(s+j);
    free(s);
//    print_ivector(a,n);
    for(int i=0;i<n;i++){
        insert_tn(*(a+i),root);
    }
    free(a);
    int *a1=(int*)malloc(sizeof(int)*cnt);
    if (a1==NULL){
        printf("memory allocation to a1 failed.\n");
        exit(1);
    }
    int *a2=(int*)malloc(sizeof(int)*cnt);
    if (a2==NULL){
        printf("memory allocation to a2 failed.\n");
        exit(1);
    }
    tree_to_pa(root,a1,a2);
    int ans=0;
    for(i=0;i<cnt;i++){
        ans+= *(a2+i)/2;
    }
//    print_ivector(a1,cnt);
//    print_ivector(a2,cnt);
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
tn* create_tn(int key){
    tn* neu;
    neu=(tn*)malloc(sizeof(tn));
    if(neu==NULL){
        printf("Memory allocation failed.");
        exit(1);
    }
    neu->l=NULL;
    neu->r=NULL;
    neu->key=key;cnt++;
    neu->v=1;
    return neu;
}
void insert_tn(int key, tn *node){
    if(node==NULL){
        root=create_tn(key);
        return;
    } else if (key == node->key){
        (node->v)+=1;
        return;
    } else if (key < node->key){
        if (node->l !=NULL){
            insert_tn(key,node->l);
        } else {
            node->l = create_tn(key);
        }
    } else if (node->key < key){
        if (node->r !=NULL){
            insert_tn(key,node->r);
        } else {
            node->r = create_tn(key);
        }
    }
    return;
}
int tree_to_pa(tn* f, int* a1, int* a2){
    if(f->l!=NULL){
        tree_to_pa(f->l,a1,a2);
    }
    *(a1+(ar_idx))=f->key;
    *(a2+(ar_idx))=f->v;ar_idx++;
    if(f->r!=NULL){
        tree_to_pa(f->r,a1,a2);
    }
} 
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

少し前にABC329-Bで二分木を使うコードを書いたので、それを少し修正して、二分木の葉にキーと値の二つを持たせ、数字をキーとするハッシュとして使えるようにした。

AtCoderでの実行結果の詳細を見ると、sortedという名の付いた最後4件のデータでTLEになっている。どうやら、二分木にソート済みのデータを入れると枝が一方向に伸び、探索にO(n)の時間がかかるというあの現象が起きているようだ。


決まった要素の出現回数、+同数の場合の処理

ABC301-A

問題概要

AとTからなる文字列が与えられる。AとTのどちらが多いか。ただし、同数の場合、先にその数に達した方を答えよ。

解答

出現する要素の種類が分かっているので、その要素をキーとするハッシュの値を加算していけばよい。同数の場合については、「文字列の最後の要素でない方」を答えればよい。

n=gets.to_i
s=gets.chomp
count=Hash.new
count["A"]=0
count["T"]=0
n.times do |i|
    count[s[i]]+=1
end
if count["A"]>count["T"]
    puts "A"
elsif count["T"]>count["A"]
    puts "T"
else
    if s[-1]=="T"
        puts "A"
    else
        puts "T"
    end
end
    

上が本番で提出したコード。Cで書いたのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 102
int main(void){
    int n,na=0,nt=0;
    char *sn=(char*)malloc(sizeof(char)*5);
    fgets(sn,5,stdin);
    n=readint(sn);
//    printf("%d\n",n);
    char *s=(char*)malloc(sizeof(char)*N1);
    fgets(s,N1,stdin);
//    printf("%s\n",s);
    for(int i=0;i<n;i++){
        if (*(s+i)==65)na++;
        else if (*(s+i)==84)nt++;
    }
    if (na>nt)printf("A\n");
    else if (na<nt)printf("T\n");
    else if (na==nt){
        if (*(s+n-1)==65)printf("T\n");
        else printf("A\n");
    }
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
//        printf("%d\n",*(s+i));
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ABC308-B

問題概要

文字列を要素とする配列が与えられる。その各要素は品目名であり、品目名と価格を対応させた表が与えられる。ただし価格表に載っていない品目もあり、その価格はデフォルト値となる。価格の合計を出力せよ。

解答

n,m=gets.chomp.split(" ").map(&:to_i)
c=gets.chomp.split(" ")
d=gets.chomp.split(" ")
p=gets.chomp.split(" ").map(&:to_i)
ans=0
n.times do |i|
    if !d.include?(c[i])
        k=p[0]
    else
        k=p[d.find_index(c[i])+1]
    end
    ans+=k
end
puts ans
    

Cでまず書いたのが以下。このコードは、sample1だけWAになった。

typedef struct {
    int id;
    char key[21];
    int v;
    struct tn *l;
    struct tn *r;
} tn;
void print_tn(tn* tns, int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
void print_ivector(int* a,int n);
int tree_to_pa(tn* f, int* a1, int* a2);
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N1 9
#define N2 2102
#define N 100000002
tn* create_tn(int key);
void insert_tn(int key, tn *node);
tn *root=NULL;
int cnt=0,ar_idx=0;
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("Memory allocation to s1 failed.");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int i=0;
    int n=readint(s1);
    while(*(s1+i)!=32)i++;
    i++;
    int m=readint(s1+i);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*(21*n+1));
    if(s2==NULL){
        printf("Memory allocation to s2 failed.");
        exit(1);
    }
    fgets(s2,(21*n+1),stdin);
//    printf("s2=%s\n",s2);
    char* s3=(char*)malloc(sizeof(char)*(21*m+1));
    if(s3==NULL){
        printf("Memory allocation to s3 failed.");
        exit(1);
    }
    fgets(s3,(21*m+1),stdin);
//    printf("s3=%s\n",s3);
    char* s4=(char*)malloc(sizeof(char)*(6*m+1));
    if(s4==NULL){
        printf("Memory allocation to s4 failed.");
        exit(1);
    }
    fgets(s4,6*(m+1)+1,stdin);
//    printf("s4=%s\n",s4);
    int* p=(int*)malloc(sizeof(int)*(m+1));
    if (p==NULL){
        printf("memory allocation to p failed.\n");
        exit(1);
    }
    input_iarray(s4,p,m+1,6*m+1);
//    print_ivector(p,m+1);
    free(s4);
    tn* arr=(tn*)malloc(sizeof(tn)*m);
    if (arr==NULL){
        printf("memory allocation to arr failed.\n");
        exit(1);
    }
    int s3_idx=0;
    i=0;
    for(;i<m;i++){
        char* temp=(char*)malloc(sizeof(char)*22);
        if (temp==NULL){
            printf("memory allocation to temp(%d) failed.\n",i);
            exit(1);
        }
        int k=0;
//        printf("s3+s3_idx=%s\n",s3+s3_idx);
        while(*(s3+s3_idx)!=32 && *(s3+s3_idx)!=0 && *(s3+s3_idx)!='\n'){
            *(temp+k)=*(s3+s3_idx); k++; s3_idx++;
        }
//        printf("temp(%d)=%s\n",i,temp);
        ((arr+cnt)->id)=cnt;
        strcpy((arr+cnt)->key,temp);
        ((arr+cnt)->v)=*(p+cnt+1);
        s3_idx++;
        cnt++; 
    }
//    print_tn(arr,m);
    int total=0;
    int s2_idx=0;
    for(i=0;i<n;i++){
        char* temp2=(char*)malloc(sizeof(char)*22);
        if (temp2==NULL){
            printf("memory allocation to temp2(%d) failed.\n",i);
            exit(1);
        }
        int k=0;
//        printf("s2+s2_idx=%s\n",s2+s2_idx);
        while(*(s2+s2_idx)!=32 && *(s2+s2_idx)!=0 && *(s2+s2_idx)!='\n'){
            *(temp2+k)=*(s2+s2_idx); k++; s2_idx++;
        }
        s2_idx++;
//        printf("temp2(%d)=%s\n",i,temp2);
        int flag=0,price=0;
        for(int j=0;j<m;j++){
            if(strcmp(temp2,(arr+j)->key)==0){
                flag=1;
                price=(arr+j)->v;
                break;
            }
        }
        if(flag==1){
//            printf("%s: price=%d\n",temp2,price);
            total+=price;
        }else{
//            printf("%s: price=%d\n",temp2,*p);
            total+=*p;
        }
    }
    printf("%d\n",total);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx\n");
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
void print_tn(tn* tns, int n){
    for(int i=0;i<n;i++){
        printf("----\n");
        printf("id=%d\n",(tns+i)->id);
        printf("key=%s\n",(tns+i)->key);
        printf("price=%d\n",(tns+i)->v);
    }
}
    

AtCoderで提出するとsample1の一つだけWAになるのだが、sample1のデータを入れて実行すると、正しい答えが出ている。原因が分からないので、StackOverflowで質問した。

actorbugさんから頂いた回答によると、strcmpで比較するために文字列を入れたtemp,temp2の最後に\0を書き込んでいなかったのが原因らしい。(他にも幾つか修正すべき点を指摘していただいた。)

ちなみに、この比較の部分は、最初はCの標準関数のstrcmpを使うのではなく、自作関数で比較しようとしたのだが、構造体のメンバである文字列のアドレスを主とルする方法が分からず、strcmpを使うことにしたのだった。

指摘されたとおりに修正したら、全てACになった。そのコードが以下。

typedef struct {
    int id;
    char key[21];
    int v;
    struct tn *l;
    struct tn *r;
} tn;
void print_tn(tn* tns, int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
void print_ivector(int* a,int n);
int tree_to_pa(tn* f, int* a1, int* a2);
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N1 9
#define N2 2102
#define N 100000002
tn* create_tn(int key);
void insert_tn(int key, tn *node);
tn *root=NULL;
int cnt=0,ar_idx=0;
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("Memory allocation to s1 failed.");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int i=0;
    int n=readint(s1);
    while(*(s1+i)!=32)i++;
    i++;
    int m=readint(s1+i);
//    printf("n=%d m=%d\n",n,m);
    free(s1);
    char* s2=(char*)malloc(sizeof(char)*(21*n+1));
    if(s2==NULL){
        printf("Memory allocation to s2 failed.");
        exit(1);
    }
    fgets(s2,(21*n+1),stdin);
//    printf("s2=%s\n",s2);
    char* s3=(char*)malloc(sizeof(char)*(21*m+1));
    if(s3==NULL){
        printf("Memory allocation to s3 failed.");
        exit(1);
    }
    fgets(s3,(21*m+1),stdin);
//    printf("s3=%s\n",s3);
    char* s4=(char*)malloc(sizeof(char)*(6*(m+1)+1));
    if(s4==NULL){
        printf("Memory allocation to s4 failed.");
        exit(1);
    }
    fgets(s4,6*(m+1)+1,stdin);
//    printf("s4=%s\n",s4);
    int* p=(int*)malloc(sizeof(int)*(m+1));
    if (p==NULL){
        printf("memory allocation to p failed.\n");
        exit(1);
    }
    input_iarray(s4,p,m+1,6*m+1);
//    print_ivector(p,m+1);
    free(s4);
    tn* arr=(tn*)malloc(sizeof(tn)*m);
    if (arr==NULL){
        printf("memory allocation to arr failed.\n");
        exit(1);
    }
    int s3_idx=0;
    i=0;
    for(;i<m;i++){
        char* temp=(char*)malloc(sizeof(char)*22);
        if (temp==NULL){
            printf("memory allocation to temp(%d) failed.\n",i);
            exit(1);
        }
        int k=0;
//        printf("s3+s3_idx=%s\n",s3+s3_idx);
        while(*(s3+s3_idx)!=32 && *(s3+s3_idx)!=0 && *(s3+s3_idx)!='\n'){
            *(temp+k)=*(s3+s3_idx); k++; s3_idx++;
        }
        *(temp+k)=0;
//        printf("temp(%d)=%s\n",i,temp);
        ((arr+cnt)->id)=cnt;
        strcpy((arr+cnt)->key,temp);
        free(temp);
        ((arr+cnt)->v)=*(p+cnt+1);
        s3_idx++;
        cnt++; 
    }
    free(s3);
//    print_tn(arr,m);
    int total=0;
    int s2_idx=0;
    for(i=0;i<n;i++){
        char* temp2=(char*)malloc(sizeof(char)*22);
        if (temp2==NULL){
            printf("memory allocation to temp2(%d) failed.\n",i);
            exit(1);
        }
        int k=0;
//        printf("s2+s2_idx=%s\n",s2+s2_idx);
        while(*(s2+s2_idx)!=32 && *(s2+s2_idx)!=0 && *(s2+s2_idx)!='\n'){
            *(temp2+k)=*(s2+s2_idx); k++; s2_idx++;
        }
        *(temp2+k)=0;
        s2_idx++;
//        printf("temp2(%d)=%s\n",i,temp2);
        int flag=0,price=0;
        for(int j=0;j<m;j++){
            if(strcmp(temp2,(arr+j)->key)==0){
                flag=1;
                price=(arr+j)->v;
                break;
            }
        }
        free(temp2);
        if(flag==1){
//            printf("%s: price=%d\n",temp2,price);
            total+=price;
        }else{
//            printf("%s: price=%d\n",temp2,*p);
            total+=*p;
        }
    }
    free(s2);
    free(arr);
    free(p);
    printf("%d\n",total);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx\n");
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
void print_tn(tn* tns, int n){
    for(int i=0;i<n;i++){
        printf("----\n");
        printf("id=%d\n",(tns+i)->id);
        printf("key=%s\n",(tns+i)->key);
        printf("price=%d\n",(tns+i)->v);
    }
}
    

ハッシュ

ABC319-A

問題概要

文字列と数値の組の表がある。表中の文字列の一つが与えられるので、それに対応する数値を出力せよ。

解答

data=<<EOF
tourist 3858
ksun48 3679
Benq 3658
Um_nik 3648
apiad 3638
Stonefeang 3630
ecnerwala 3613
mnbvmar 3555
newbiedmy 3516
semiexp 3481tourist 3858
EOF
d=data.split("\n")
da=Array.new(d.size){Array.new(2)}
i=0
d.each do |it|
    s,n=it.split(" ")
    da[i][0]=s
    da[i][1]=n.to_i
    i+=1
end
s=gets.chomp
da.each do |dd|
    if dd[0]==s
        puts dd[1]
        exit
    end
end
    

上が本番の提出コード。Cでやったのが以下。

typedef struct {
    char key[21];
    int v;
} tn;
void print_tn(tn* tns, int n);
int readint(char *s);
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N1 10
#define N2 12
#define N 100000002
int cnt=0,ar_idx=0;
int main(void){
    char s1[]="tourist 3858,ksun48 3679,Benq 3658,Um_nik 3648,apiad 3638,Stonefeang 3630,ecnerwala 3613,mnbvmar 3555,newbiedmy 3516,semiexp 3481";
//    printf("%s\n",s1);
    tn* arr=(tn*)malloc(sizeof(tn)*N1);
    if (arr==NULL){
        printf("memory allocation to arr failed.\n");
        exit(1);
    }
    int s1_idx=0,i=0;
    for(i=0;i<N1;i++){
        char* temp=(char*)malloc(sizeof(char)*22);
        if (temp==NULL){
            printf("memory allocation to temp(%d) failed.\n",i);
            exit(1);
        }
        int k=0;
        while(*(s1+s1_idx)!=32){
            *(temp+k)=*(s1+s1_idx); k++; s1_idx++;
        }
        *(temp+k)=0;
        strcpy((arr+i)->key,temp);
        free(temp);
        s1_idx++;
        int point=readint(s1+s1_idx);
        (arr+i)->v=point;
        if (i!=N1-1){
            while(*(s1+s1_idx)>=48 && *(s1+s1_idx)<=57){
                s1_idx++;
            }
            while(*(s1+s1_idx)==44)s1_idx++;
        }
    }
//    print_tn(arr,N1);
    char* s2=(char*)malloc(sizeof(char)*N2);
    if (s2==NULL){
        printf("memory allocation to s2 failed.\n");
        exit(1);
    }
    fgets(s2,N2,stdin);
    char* temp2=(char*)malloc(sizeof(char)*(N2+1));
    if (temp2==NULL){
        printf("memory allocation to temp2 failed.\n");
        exit(1);
    }
    int s2_idx=0,k=0;
    while((*(s2+s2_idx)>=48 && *(s2+s2_idx)<=122)){
        *(temp2+k)=*(s2+s2_idx); s2_idx++; k++;
    }
    *(temp2+k)=0;
//    printf("%s\n",temp2);
    int flag=0;
    for(i=0;i<N1;i++){
        if (strcmp((arr+i)->key,temp2)==0){
            printf("%d",(arr+i)->v);
            flag=1;
            exit(0);
        }
    }
    if (flag==0){
        printf("name couldn't be found.\n");
        exit(1);
    }
    free(arr);
    free(temp2);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void print_tn(tn* tns, int n){
    for(int i=0;i<n;i++){
        printf("----\n");
        printf("key=%s\n",(tns+i)->key);
        printf("point=%d\n",(tns+i)->v);
    }
}
    

ABC325-B

問題概要

拠点iの標準時は世界標準時+w[i]で、x[i]人が所属している。(w[i]は整数。)整数時から始まる1時間に各拠点の標準時における9時から18時が完全に含まれる拠点の所属員を累計するとき、その値の最大値を求めよ。

解答

n=gets.to_i
x=[]
w=[]
n.times do |i|
    x[i],w[i]=gets.chomp.split(" ").map(&:to_i)
end
h=Hash.new
(0..23).each do |i|
    t=0
    n.times do |j|
        st=i+w[j]
        if st>24
            st-=24
        end
        if st>=9 && st<=17
            t+=x[j]
        end
    end
    h[i]=t
end
max=0
h.each {|key, value| 
    if value > max
        max=value
    end
}
puts max
    

この問題では開始時間は整数時の24通りに限られるので、0..23時をそれぞれ開始時間としたときに開始時間の現地標準時が9時から17時になる拠点の所属員数の累計値を、開始時間をキー、対応する累計値を値とするハッシュにまとめ、値の最大値を出力すればよい。

上が本番の提出コード。Cでやったのが以下。

typedef struct {
    int hour;
    int num;
} table;
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 6
#define N2 12
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    int* w=(int*)malloc(sizeof(int)*n);
    if(w==NULL){
        printf("memory allocation to w failed\n");
        exit(1);
    }
    int* x=(int*)malloc(sizeof(int)*n);
    if(x==NULL){
        printf("memory allocation to x failed\n");
        exit(1);
    }
    for(int i=0;i<n;i++){
        char* s2=(char*)malloc(sizeof(char)*N2);
        if(s2==NULL){
            printf("memory allocation to w failed\n");
            exit(1);
        }
        fgets(s2,N2,stdin);
        *(x+i)=readint(s2);
        int j=0;
        while(*(s2+j)!=32)j++;
        j++;
        *(w+i)=readint(s2+j);
//        printf("w[%d]=%d x[%d]=%d\n",i,*(w+i),i,*(x+i));
        free(s2);
    }
    table* t=(table*)malloc(sizeof(table)*24);
    if(t==NULL){
        printf("memory allocation to t failed\n");
        exit(1);
    }
    for(int i=0;i<24;i++){
        (t+i)->hour=i;
    }
    for(int i=0;i<n;i++){
        int start=9;
        int end=17;
        start=((start+*(w+i))%24);
        end=((end+*(w+i))%24);
//        printf("start=%d end=%d\n",start,end);
        if(start<end){
            for(int j=start;j<=end;j++){
                ((t+j)->num) += *(x+i);
            }
        }else{
            for(int j=start;j<=23;j++){
                ((t+j)->num) += *(x+i);
            }
            for(int j=0;j<=end;j++){
                ((t+j)->num) += *(x+i);
            }
        }
    }
    int max=0;
    for(int i=0;i<24;i++){
        //printf("from %d: %d person\n",i,(t+i)->num);
        if ((t+i)->num > max){
            max=(t+i)->num;
        }
    }
    printf("%d\n",max);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ほぼ出来上がったころに気づいたのだが、構造体tableのhourはインデックスと同じなので、構造体を作らなくても単なる配列でもよかった。


複数の最大値

ABC329-D

問題概要

1からnまでの整数のいずれかを要素とする配列(要素数m)が与えられる。i番目までの中で最も多く出現している数(ただし、最も多く出現している数が複数あるときは、そのうち最も小さい数)を出力せよ。

解答

n,mは最大で2*10^5なので、ほぼ二重ループになる下記のコードでTLEになることは分かっていたが、この日は開始から約30分後に参加登録したにもかかわらず、A,B問題はおろかC問題まであっさり解け、それで終了までかなり時間があったため、これもやってみることに。あくまでも、小規模なデータならこれで解けるということで書いたコードである。

n,m=gets.chomp.split(" ").map(&:to_i)
a=gets.chomp.split(" ").map(&:to_i)
c=Hash.new
(0...m).each do |i|
    if c.has_key?(a[i])
        c[a[i]]+=1
    else
        c[a[i]]=1
    end
    max=0
    idxs=[]
    c.each do |k,v|
        if v>max
            idxs.clear
            idxs.push(k)
            max=v
        elsif v==max
            idxs.push(k)
        end
    end
    puts idxs.min
end
    

i番目まで数えた時の各要素の出現回数を、要素の値をキー、出現回数を値とするハッシュで保持、最大回数出現する要素を配列に記録し、その配列の最小値を出力している。

2022年11月10日木曜日

出力形式に関する問題

小数の出力形式

ABC274-A

問題概要

A/Bを小数第4位で四捨五入した値を末尾ゼロを省略せずに表記して出力せよ。

解答

a,b=gets.chomp.split(" ").map(&:to_i)
printf("%.3f\n", (b.to_f/a).round(3))
    

aかbのどちらかを小数に変換して割り算し、そのオブジェクトからroundメソッド呼び出して引数に表示したい小数点以下の桁数を渡す。

あとはprintfが0埋めをやってくれる。

どうということのない問題なのだが、実際には改めて調べなおしてやることになった。

上が本番の提出コード。Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 7
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int a=readint(s1);
    int j=0;
    while(*(s1+j)!=32)j++;
    j++;
    int b=readint(s1+j);
    free(s1);
//    printf("a=%d, b=%d\n",a,b);
    int quot10000=b*10000/a;
//    printf("quot10000=%d\n",quot10000);
    int arr[5];
    for(int i=0,divisor=10000;i<5,divisor>0;divisor/=10,i++){
        arr[i]=quot10000/divisor;
        quot10000-=arr[i]*divisor;
//        printf("arr[%d]=%d\n",i,arr[i]);
    }
    char str[6];
    str[0]=arr[0]+48;
    str[1]=46;
    str[2]=arr[1]+48;    
    str[3]=arr[2]+48;
    if (arr[4]<5){
        str[4]=arr[3]+48;
    }else{
        str[4]=arr[3]+1+48;
    }
    str[5]=0;
    printf("%s\n",str);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

16進数に変換し、先頭に0埋め

ABC271-A

問題概要

10進法で表された整数nが与えられる。(1≦n≦255) nを二桁の16進数として出力せよ。ただし、二桁未満になる時は、先頭に0を埋める。

解答

n=gets.to_i
s=[]
q,x=n.divmod(16)
h={0=>"0",1=>"1",2=>"2",3=>"3",4=>"4",5=>"5",6=>"6",7=>"7",8=>"8",9=>"9",10=>"A",11=>"B",12=>"C",13=>"D",14=>"E",15=>"F"}
s.push(h[q])
s.push(h[x])
puts s.join("")
    

これが本番の提出コード。

0から15の数字をキー、"0"から"F"までの文字を値とするハッシュを作り、与えられた数字を16で割った商をキーとする値を16の位に、余りをキーとする値を1の位に入れている。

もっとも、rubyにはもともと10進数を16進数に変換する機能が備わっているので、以下のようにそちらを利用してもよい。

n=gets.to_i
nn=sprintf("%02X",n)
puts nn
    

本番でこれを使わなかったのは、これだと先頭に0xが入ると思い込んでいたからなのだが、やってみたら0x無しで出力できた。

Cでやったのが以下。

char convert_to_hexadeca_1(int n);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 5
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
//    printf("n=%d\n",n);
    int n1=n/16;
    int n2=n%16;
//    printf("s0=%d, s1=%d\n",n1,n2);
    char str[3];
    str[0]=convert_to_hexadeca_1(n1);
    str[1]=convert_to_hexadeca_1(n2);
    str[2]=0;
    printf("%s\n",str);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
char convert_to_hexadeca_1(int n){
    if (n>=16){
        printf("n must be less than 16.\n");
        exit(1);
    }
    if (n<0){
        printf("n must not be negative.\n");
        exit(1);
    }
    char c;
    if (n>=0 && n<10){
        c=n+48;
    }else if (n>9 && n<16){
        c=n-10+65;
    }
    return c;
}
    

時刻の表示

ABC258-A

問題概要

21時からk分後の時刻をhh:mmの形で出力せよ。

解答

n=gets.to_i
m=s=0
m,s=n.divmod(60)
m+=21
printf("%02d:%02d\n",m,s)
    

上が本番の提出コード。Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 5
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if (s1==NULL){
        printf("memory allocation to s1 failed.\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int k=readint(s1);
//    printf("k=%d\n",k);
    int hour=21+k/60;
    int minute=k%60;
//    printf("hour=%d, minute=%d\n",hour,minute);
    char str[6];
    str[0]=hour/10+48;
    str[1]=hour%10+48;
    str[2]=58;
    str[3]=minute/10+48;
    str[4]=minute%10+48;
    str[5]=0;
    printf("%s\n",str);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

先頭0埋め

ABC254-A

問題概要

3桁以上の整数が与えられる。その下2桁を、xxの形で出力せよ。

解答

n=gets.to_i
t1=(n%10).to_s
n/=10
ans=(n%10).to_s
ans+=t1
puts ans    
    

上が本番の提出コード。

1の位の数と10の位の数をいったん文字列にしてから連結している。

後になって考えると、次のようにした方が簡単だったのだが。

n=gets.to_i
printf("%02d\n",n%100)
    

Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 5
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if (s1==NULL){
        printf("memory allocation to s1 failed.\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
//    printf("n=%d\n",n);
    int n10=(n/10)%10;
    int n1=n%10;
//    printf("n10=%d, n1=%d\n",n10,n1);
    char str[3];
    str[0]=n10+48;
    str[1]=n1+48;
    str[2]=0;
    printf("%s\n",str);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}    
    

切り捨て

ABC314-A

問題概要

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679の小数第n位以下を切り捨てて出力せよ。

解答

最初、問題を、「小数第n+1位で四捨五入せよ」というものだと勘違いしてしまって書いたのが次のコード

n=gets.to_i
s="3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
ans=""
t=s[n+2].to_i
if t>=5
    ss=s[n+1].to_i
    ss+=1
    if ss==10
        s[n+1]="0"
        sss=s[n].to_i+1
        if sss==10
            s[n]="0"
            s[n-1]=(s[n-1].to_i+1).to_s
        else
            s[n].sss.to_s
        end
    else
        s[n+1]=ss.to_s
    end
end
(2+n).times do |i|
    ans[i]=s[i]
end
puts ans
    

9が最大で何個まで続いているか分からなければまた別の処理が必要になるが、この問題の文字列では9は2つまでしか連続していないから、これで十分。なぜかWAがいくつか出るので、いったん放棄していたのだが、B問題をやったあと改めて見直してみて問題の勘違いに気付いた。それで書き直したのが以下。

n=gets.to_i
s="3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
ans=""
t=s[n+2].to_i
(2+n).times do |i|
    ans[i]=s[i]
end
puts ans
    

これでACになった。有り得ないような勘違いだったのだが、まさか単なる切り捨てのような簡単すぎる問題は出ないだろうという思い込みで問題文を読み違えてしまったようだ。

Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 5
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if (s1==NULL){
        printf("memory allocation to s1 failed.\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
//    printf("n=%d\n",n);
    char pi[]="3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679";
    char str[103];
    str[0]=pi[0];
    str[1]=pi[1];
    for(int i=1;i<=n;i++){
        str[i+1]=pi[i+1];
    }
    str[n+2]=0;
    printf("%s\n",str);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

2022年11月9日水曜日

累乗、階乗、C,Pの計算

2^nの計算

ABC256-A

問題概要

2^nを出力せよ。

解答

n=gets.to_i
puts 2.pow(n).to_i
    

としてもいいし、底が2のときたけ使える方法だが、

n=gets.to_i
puts 1<<n
    

としてもいい。

このときは本番不参加。Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 4
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int t=1,p=2;
    while(n>0){
        if(n%2)t*=p;
        n>>=1;
        p*=p;
    }
    printf("%d\n",t);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}    

累乗

ABC283-A

問題概要

aのb乗を出力せよ。

解答

a,b=gets.chomp.split(" ").map(&:to_i)
puts a**b
    

上が本番で提出したコード。Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N 5
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    int a=readint(s);
    int i=0;
    while(*(s+i)!=32)i++;
    i++;
    int b=readint(s+i);
    free(s);
    int t=1;
    for(int j=0;j<b;j++){
        t*=a;
    }
    printf("%d\n",t);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

累乗を求める部分を関数として独立して使えるよ絵にしたのが以下。

#include<stdio.h>
#include<stdlib.h>
#define N 5
int readint(char *s);
int int_pow(int x,int y);
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    int a=readint(s);
    int i=0;
    while(*(s+i)!=32)i++;
    i++;
    int b=readint(s+i);
    free(s);
    printf("%d\n",int_pow(a,b));
    return 0;
}
int 
int_pow(int x,int n){
    int ret=1; 
    while(n>0){
        if((n&1)==1)ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

指数関数の不等式を解く

ABC248-B

問題概要

a*k^n≧bを満たす最小の非負整数nを求めよ。(a,b,kは自然数。k≧2)

解答

a,b,k=gets.chomp.split(" ").to_a.map(&:to_i)
i=0
loop do
    if a>=b
        puts i
        exit
    end
    a*=k
    i+=1
end
    

これが本番で提出したコード。

このようにループを回して不等式を満たす最小の整数iを求めたわけだが、直接、上の不等式を解くという方法もある。それでやってみたのが以下のコードなのだが、一つだけWAになった。(Sample:AC x 3,All:AC x 9 WA x 1)

a,b,k=gets.chomp.split(" ").to_a.map(&:to_i)
puts Math::log((b/a.to_f),k).ceil
    

浮動小数点数の誤差の影響かと思うが、確実な理由は分からない。

--------------------

(追記)StackOverflowで質問したところ、やはり浮動小数の誤差が原因だった。


n!を求める

ABC273-A

問題概要

f(0)=1, f(k)=k*f(k-1)である。(k>0) nが与えられたとき、f(n)を求めよ。ただし、n≦10

再帰関数を使ってn!を求める問題である。

解答

    def f(n)
    if n==1 || n==0
        ret=1
    else
        ret=n*f(n-1)
    end
    return ret
end
n=gets.to_i
puts f(n)
    

上は、本番で提出したコード

問題文でもnが10以下という限定が付いているが、この方法でn!を求めると、すぐにnが巨大な数になってしまう。

実用的には、あらかじめn!を小さい順に計算してpushした配列を作っておいて、それを参照するのがよいか。

こんな感じで。

$fac=[]
$fac[0]=1
(1..100).each do |i|
    $fac[i]=$fac[i-1]*i
end
n=gets.to_i
puts $fac[n]
    

Cでやったのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N 4
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    int n=readint(s);
    free(s);
    int t=1;
    for(int j=1;j<=n;j++){
        t*=j;
    }
    printf("%d\n",t);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

パスカルの三角形を描く

ABC254-B

問題概要

次の定義を使ってパスカルの三角形を描け。

  1. j=0またはj=iのとき、a[i,j]=1
  2. 上記以外の時、a[i,j]=a[i-1,j]+a[i-1]

n=gets.to_i
a=[]
n.times do |i|
    a.push(Array.new(i+1,1))
    if i!=0
        (i+1).times do |j|
            if j!=0 && j!=i
                a[i][j]=a[i-1][j-1]+a[i-1][j]
            end
        end
    end
        puts a[i].join(" ")
end
    

問題文の指示通りに実装すればよい。

それ自体としては面白みのない問題だが、これを使ってCの計算をすることができる。

C[n,r]を、C[n,r]=n!/r!・(n-r)!という定義に従って計算しようとすると、途中で出てくる数が大きくなりすぎてしまう難点があるのだが、上のやり方でパスカルの三角形を作れば、a[i-1][j]がそのままC[i,r]になる。

それを使ってCの計算をやってみた。

    
n=100
$a=[]
n.times do |i|
    $a.push(Array.new(i+1,1))
    if i!=0
        (i+1).times do |j|
            if j!=0 && j!=i
                $a[i][j]=$a[i-1][j-1]+$a[i-1][j]
            end
        end
    end
end
def C(n,r)
    if r>n/2
        r=n-r
    end
    $a[n][r]
end
11.times do |i|
    puts "C[10][#{i}]=#{C(10,i)}"
end
    

実行結果は、

C[10][0]=1
C[10][1]=10
C[10][2]=45
C[10][3]=120
C[10][4]=210
C[10][5]=252
C[10][6]=210
C[10][7]=120
C[10][8]=45
C[10][9]=10
C[10][10]=1

Cで書いた、元の問題の解答が以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 4
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    free(s1);
    int sz=n*(n+1)/2;
    int* p=(int*)malloc(sizeof(int)*sz);
    *p=1;
    *(p+1)=1;
    *(p+2)=1;
    int start=3;
    for(int i=2;i<n;i++){
        *(p+start)=1;
        *(p+start+i)=1;
        for(int j=1;j<i;j++){
            *(p+start+j)=*(p+start-i+j-1)+*(p+start-i+j);
//            printf("%d+%d=%d\n",*(p+start-i+j-1),*(p+start-i+j),*(p+start+j));
        }
        start+=(i+1);
    }
    start=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<i+1;j++){
            printf("%d",*(p+start+j));
            if(j==i){
                printf("\n");
            }else{
                printf("%c",32);
            }
        }
        start+=(i+1);
    }
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

上に手を加えてnCrを求めるものを書いたのが以下。

int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N 30
int main(void){
    int* p=(int*)malloc(sizeof(int)*N*(N+1)/2);
    *p=1;
    *(p+1)=1;
    *(p+2)=1;
    int start=3;
    for(int i=2;i<N;i++){
        *(p+start)=1;
        *(p+start+i)=1;
        for(int j=1;j<i;j++){
            *(p+start+j)=*(p+start-i+j-1)+*(p+start-i+j);
        }
        start+=(i+1);
    }
    for(int i=1;i<N;i++){
        int s=i*(i+1)/2;
        for(int j=0;j<i+1;j++){
            printf("C[%d,%d]=%d\n",i,j,*(p+s+j));
        }
    }
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ABC303-B

問題概要

要素数nの整数列がm個与えられる。その中に、一度も隣接していない数字の組はいくつあるか

解答

以下のコードは、一部RE、一部WAになった。

def C(n,r)
    if r==0 || r==n
        return 1
    elsif r==1 || r==n-1
        return n
    else
        return C(n-1,r)+C(n-1,r-1)
    end
end
require 'set'
n,m=gets.chomp.split(" ").map(&:to_i)
a=[]
m.times do |i|
    a[i]=gets.chomp.split(" ").map(&:to_i)
end
b=Array.new(n){Array.new(n,false)}
m.times do |i|
    (n-1).times do |j|
        b[a[i][j]-1][a[i][j+1]-1]=true
        b[a[i][j+1]-1][a[i][j]-1]=true
    end
end
s=Set.new
m.times do |i|
    n.times do |j|
        if b[i][j]==true
            if j<i
                s.add([j,i])
            else
                s.add([i,j])
            end
        end
    end
end
puts C(n,2)-s.size
    

隣接している組の数を数え(たつもりだった)、それを、n人から2人を選ぶ場合の数C[n,2]から引いている。

後から見直してみると上のコードは隣接しているかの判定とは全然関係の無いことをやっているので、やり直したのが以下、これでACになった。

def C(n,r)
    if n<=0 || r<0 || n<r
        puts "wrong argument"
    elsif r==0 || r==n
        return 1
    elsif r==1 || r==n-1
        return n
    else
        return C(n-1,r)+C(n-1,r-1)
    end
end
require 'set'
n,m=gets.chomp.split(" ").map(&:to_i)
ar=[]
m.times do |i|
    ar[i]=gets.chomp.split(" ").map(&:to_i)
end
s=Set.new
m.times do |i|
    (1...n).each do |j|
        a=ar[i][j-1]
        b=ar[i][j]
        if a>b
            a,b=b,a
        end
        s.add([a,b])
    end
end
puts C(n,2)-s.size
=begin
puts C(n,2)
s.each do |st|
    print "(#{st[0]},#{st[1]}), "
end
=end
    

Cでやったのが以下。

int Combi(int n,int r);
int readint(char *s);
#include<stdio.h>
#include<stdlib.h>
#define N1 7
#define N 143
#define N2 5051
int main(void){
    int n,m;
    int i=0;
    char *s=(char*)malloc(sizeof(char)*N1);
    if (s==NULL){
        printf("memory allocation to s failed.\n");
        exit(1);
    }
    fgets(s,N1,stdin);
    n=readint(s);
    while(*(s+i)!=32)i++;
    i++;
    m=readint(s+i);
//    printf("%d %d\n",n,m);
    free(s);
    int* a=(int*)malloc(sizeof(int)*n*m);
    if (a==NULL){
        printf("memory allocation to a failed.\n");
        exit(1);
    }
    for(int j=0;j<m;j++){
        char *s2=(char*)malloc(sizeof(char)*N);
        fgets(s2,N,stdin);
        int l=0,k=0;
        while(k<n){
            int temp=readint(s2+l);
            *(a+j*n+k)=temp; k++;
            while(*(s2+l)>=48 && *(s2+l)<=57)l++;
            l++;
        }
    }
    /*
    for(int j=0;j<m;j++){
        for(int k=0;k<n;k++){
            printf("%d ",*(a+j*n+k));
        }
        printf("\n");
    }
    for(int j=0;j<=10;j++){
        printf("Combi(%d,%d)=%d\n",10,j,Combi(10,j));
    }
    */
    int cnt=0;
    int *b=(int*)malloc(sizeof(int)*N2);
    if (b==NULL){
        printf("memory allocation to b failed.\n");
        exit(1);
    }
    for(int j=0;j<N;j++){
        *(b+j)=0;
    }
    for(int j=0;j<m;j++){
        for(int k=1;k<n;k++){
            int t1=*(a+j*n+k-1);
            int t2=*(a+j*n+k);
            if(t1>t2){
                int temp=t1;
                t1=t2;
                t2=temp;
            }
            int t3=t1*100+t2;
//            printf("(%d, %d)\n",t1,t2);
            *(b+t3)=1;
        }
    }
    for(int j=0;j<N2;j++){
        if(*(b+j)==1)cnt++;
    }
//    printf("Combi(%d,%d)=%d\n",n,2,Combi(n,2));
//    printf("cnt=%d\n",cnt);
    printf("%d\n",Combi(n,2)-cnt);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
int Combi(int n,int r){
    if (n<=0 || r<0 || n<r){
        printf("Wrong argument.\n");
        exit(1);
    }
    if (r==0 || r==n)return 1;
    else if (r==1 || r==n-1)return n;
    else return Combi(n-1,r)+Combi(n-1,r-1);
}
    

基本的にはrubyでやったときと同じやり方で、C(n,2)から隣接している組の数を引いているのだが、問題は、Cでセットを実装する力はまだないということである。しかし、問題においてはnが50とかなり小さいので、隣接する二つの数を、小さい方を上2桁、大きい方を下2桁とする4桁の整数と一対一対応させることができ、二つの数の組を一つの整数に置き換えて著すことができる。そうやって隣接する組の数を数えた。


累乗計算

ABC320-A

問題概要

A^B + B^A を計算せよ。

解答

a,b=gets.chomp.split(" ").map(&:to_i)
puts a**b + b**a
    

Cでやったのが以下。

#include<stdio.h>
#include<stdlib.h>
#define N 5
int readint(char *s);
int int_pow(int x,int y);
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    if(s==NULL){
        printf("memory allocation to s failed\n");
        exit(1);
    }
    fgets(s,N,stdin);
    int a=readint(s);
    int i=0;
    while(*(s+i)!=32)i++;
    i++;
    int b=readint(s+i);
    free(s);
    printf("%d\n",int_pow(a,b)+int_pow(b,a));
    return 0;
}
int 
int_pow(int x,int n){
    int ret=1; 
    while(n>0){
        if((n&1)==1)ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ABC324-B

問題概要

与えられた整数nが、n=2^x * 3^y と表せるか。

解答

問題は上記のように表現されているが、x,yを具体的に求める必要はなく、要は2と3以外の素因数を持たなければよい。つまり、2と3で割れるだけ割った後、1になっていればよい。

n=gets.to_i
while n%2==0
    n/=2
end
while n%3==0
    n/=3
end
if n==1
    puts "Yes"
else
    puts "No"
end
    

Cでやったのが以下。

#include
#include
#define N 21
long long readll(char *s);
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    if(s==NULL){
        printf("memory allocation to s failed\n");
        exit(1);
    }
    fgets(s,N,stdin);
    long long a=readll(s);
    free(s);
    while(a%2==0){
        a=a>>1;
    }
    while(a%3==0){
        a/=3;
    }
    if(a==1){
        printf("Yes\n");
    }else{
        printf("No\n");
    }
    return 0;
}
long long readll(char *s){
    long long ret=0;
    int i=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
    

ABC327-B

問題概要

与えられた数bが、整数aにより、b=a^a と表せるか。

解答

まずは初めに書いたコード。TLEになった。

b=gets.to_i
if b==1
    puts 1
    exit
else
    (2..(Math::sqrt(b).to_i)).each do |a|
        if a**a==b
            puts a
            exit
        end
    end
end
puts -1
    

まあこれはTLEになるのも当然。B問題だから計算量のことは意識せずに書いたのだが、bは最大で10^18なので、その2乗根まで1つずつ試すと10^9回である。もっとも、この問題の場合、bが4でなければaはbの3乗根以下、27でもなければ4乗根以下なので、その辺までaの上限を下げてしまえば、簡単に回避できそうだ。

そうしてもよかったのだが、上のコードを書きながら、別のやり方もあるなと考えていたので、そちらで書いたのが以下。

    class Pair
    attr_accessor :a, :b
    def initialize(h, t)
        @a, @b = h, t
    end
end
b=gets.to_i
list=[]
a=1
loop do
    t=a**a
    if t&gt;1e18
        break
    else
        list.push(Pair.new(a,t))
        a+=1
    end
end
list.each do |ls|
    if ls.b==b
    puts ls.a
    exit
    end
end
puts -1
    

bは制約により1以上10^18以下だが、何しろbはaの二乗なのでaが大きくなるとすぐにこの上限に達する。なにしろ、a=10ですでにb=10^10なのだ。だから、aを1からはじめて1ずつインクリメントしながらa^aのリストを作っていって、それが10^18を超えたら停止し、リストの中にbが含まれているか調べればよい。出来たリストの中身を確認したところ、aは最大で15だった。


Cでやって、まず書いたのが以下のコード。これはREになった。

typedef struct {
    long long n;
    long long n2;
} table;
long long readll(char *s);
long long ll_pow(long long x, long long n);
#include<stdio.h>
#include<stdlib.h>
#define N1 21
#define N2 16
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    long long  b=readll(s1);
    free(s1);
    table* tb=(table*)malloc(sizeof(table)*N2);
    if(tb==NULL){
        printf("memory allocation to tb failed\n");
        exit(1);
    }
    for(int j=0;j<N2;j++){
        (tb+j)->n=0;
        (tb+j)->n2=0;
    }
    long long t1=1;
    long long t2=1;
    int i=0;
    while(1){
        t2=ll_pow(t1,t1);
        if (t2==0)break; 
        (tb+i)->n=t1;
        (tb+i)->n2=t2;
        t1+=1; i+=1;
    }
/*    for(int i=0;i<N2;i++){
        printf("%d :(%lld, %lld)\n",i+1,(tb+i)->n,(tb+i)->n2);
    }
*/
    int found=0;
    for(int j=0;j<N2;j++){
        if (b==(tb+j)->n2){
            found=1;
            break;
        }
    }
    free(tb);
    if(found==1){
        printf("Yes\n");
    }else{
        printf("No\n");
    }
    return 0;
}
long long readll(char *s){
    long long ret=0;
    int i=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
long long ll_pow(long long x, long long n){
    long long ret=1; 
    while(n>0){
        if(ret>1e18 || x>1e18){
            return 0;
        }
        if((n&1)==1)ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}
    

先にrubyでやったときは、型の最大値を超えてしまうことを心配する必要はなかったが、Cではlong long型の最大値である2^64を超えないようにする必要がある。

もっともこれは、先にrubyでやったときにaが15を超えるとa^aが10^18を超えることを確認しているので、15^15のところまででやめるようにすれば解決可能ではある。しかし、先に型の制約がない言語で一回試しているという前提がない条件で、Cでのコードの中だけで、「long long型の最大値を超えないようにしつつ、bが10^18である場合まですべて漏れなく試す。」という処理を書きたかった。

もちろん、先にrubyで試していなくでも、log10(15^15)=15*log10(15)=15*(log10(3)+1og10(5))=15*(log10(3)+1og10(10/2))=15*(log10(3)+1-1og10(2))=15*(0.4771+1-0.3010)≓17.7, log10(16^16)=log10(2^64)=64*log10(2)=64*0.3010≓19.2となって、15と16の間が境界であることは手計算で確認できる。10^10は明らかに10^18より小さく、20*20=2^20*10^20は明らかに10^18より大きいから、その中間あたりから試していけば、15と16の間が境界というのもすぐわかりはする。ただ、こういう人間による手計算の前処理を使うのもちょっと邪道な気がする。それに、log10(15)やlog10(16)は上のように高校生のころから記憶している数値を使ってすぐに計算できるが、これがlog10(17)やlog10(13)も調べなければならないとなると常用対数表が必要になるし、そういったコードの外の処理を伴わずに、全てをコードの中だけで判定したかったのである。aの上限N2を20としたのは、上述のように20^20が10^18より大きいことは対数計算をするまでもなく明白なので、これぐらいは人間の判断を介在させてもいいだろうと妥協した。

そこで、計算途中で本問のbの制約上の最大値である10^18を超えたら止める処理を累乗を計算する関数であるll_powの中に入れておいたが、なにしろ10^18は2^64にかなり近い数なので(上述のように、log10(2^64)≓19.2)、結局long long型の最大値を超えてしまうのではないかという不安は感じていたが、昨日は頭が疲れていたこともあり、これで提出してしまって、REになったのである。

それで、Stack StackOverflowで質問してしまって終わりにしたのだが、actorbugさんから回答を頂いたところ、やはり計算途中で桁あふれを起こしてしまい、それが原因のようだ。

a=16のとき、ll_pow関数内でi回ループを回した後のxの変化を見てみると

i n x
0 16 16
1 8  16*16=2^8
2 4  (2^8)*(2^8)=2^16
3 2  (2^16)*(2^16)=2^32
4 1  (2^32)*(2^32)=2^64  
    
となり、最後にret=1*2^64=2^64となるが、ここで4回目のループ終了時のxとretに10^18を超える値を代入してしまっている。そして、こうなることは上のコード内のll_pow関数内の処理では止められない。(こんなことは質問する前に自分で気付くべきだった。)

それで、今度はretかxが10^9を超えた時点で止めるようにコードを修正してみたのだが、そうするとnが14と15のときも0を返してまう。

こちらもll_pow関数内でi回ループを回した後のxとretの変化を見てみると、まずa=14のとき、

i   n   x ret
0   14  14  1
1   7   14^2   1
2   1   14^8    14^4   
となって、log10(14^8)=8*log10(14)=8*(log10(2)+log10(7))=8*(0.3010+0.8451)=8*1.1461≓9.2となってxが10^9を超え、ここで終わってしまう。

まずa=15のとき、

i   n   x ret
0   15  15  1
1   7   15^2   15
2   3   15^4    15^2   
3   1   15^8    15^4   
となって、log10(15^8)=8*log10(15)=8*(log10(3)+log10(5))=8*(0.4771+0.6990)=8*1.1761≓9.4となってxが10^9を超え、ここで終わる。

この二つの数の場合には、xが10^10以下まで許容するようにすればうまくいくのだが、そのように人間がいちいちあらかじめ計算して調整することが、果たしてプログラミングのやり方として好ましいものかどうか。

それで、結局、ll_pow関数の中の上限チェックの部分は削除し、aを15まで試して止めることにした。これでACになった。それが以下。なお、先のコードは解答の形式も違っていたので、そこも修正してある。

typedef struct {
    long long n;
    long long n2;
} table;
long long readll(char *s);
long long ll_pow(long long x, long long n);
#include<stdio.h>
#include<stdlib.h>
#define N1 21
#define N2 15
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    long long  b=readll(s1);
    free(s1);
    table* tb=(table*)malloc(sizeof(table)*N2);
    if(tb==NULL){
        printf("memory allocation to tb failed\n");
        exit(1);
    }
    for(int j=0;j<N2;j++){
        (tb+j)->n=0;
        (tb+j)->n2=0;
    }
    long long t1=1;
    long long t2=1;
    int i=0;
    for(int j=0;j<N2;j++){
        t2=ll_pow(t1,t1);
        (tb+j)->n=t1;
        (tb+j)->n2=t2;
        t1+=1;
    }
/*    for(int i=0;i<N2;i++){
        printf("%d :(%lld, %lld)\n",i+1,(tb+i)->n,(tb+i)->n2);
    }
*/
    int found=-1;
    for(int j=0;j<N2;j++){
        if (b==(tb+j)->n2){
            found=(tb+j)->n;
            break;
        }
    }
    free(tb);
    printf("%d\n",found);
    return 0;
}
long long readll(char *s){
    long long ret=0;
    int i=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
long long ll_pow(long long x, long long n){
    long long ret=1; 
    while(n>0){
        if((n&1)==1)ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}
    

2022年11月1日火曜日

n進法

ABC285-C

問題概要

A,B,C,...,X,Y,Z,AA,AB,AC,...ZX,ZY,ZZ,AAA,AAB...という文字列の列と1,2,3,...,24,25,26,27,28,29...,700,701,702,703,704,...という数字を対応させるとき、与えられた文字列と対応する数字を出力せよ。

解答

例えば、与えられた文字列がCBAなら、その前には、長さ1の文字列が26^1個、長さ2の文字列が26^2個、あって、その次に、長さ3の文字列が、AAAからBZZまで2*26^2個、CAAからCAZまで1*26^1個、その次の文字がCBAなので、26^1+26^2+2*26^2+1*26^1+1が対応いる数字となる。

一般化すると、与えられた文字列がn文字からなっていれば、まずその前に、長さ1の文字列が26^1個、...、長さn-1の文字列が26^(n-1)個あるので、初期値0にこれを加算していく。次に、長さnの文字列が与えられた文字列の前に何個あるか考える。i番目(0オリジン)の文字のアスキーコードから65を引いた値をm[i]とすると、m[0]*26^(n-1)+m[1]*26^(n-2)+...+m[n-2]*26^1+m[n-1]個である。この部分、具体的には、先頭桁から、これまでの和に26を乗じ、その桁の文字のアスキーコード-65を加えることを繰り返して行った。最後に1を加えると、与えられた文字列の番号になる。

以下が、本番で提出したコード。

s=gets.chomp
ans=0
n=s.size
(1...n).each do |i|
    t=26.pow(i)
    ans+=t
end
tt=0
n.times do |i|
    tt*=26
    t=s[i].ord-65
    tt+=t
end
ans+=tt
puts ans+1
    

中学受験算数で群数列をやりなれていると、こういう問題は割合と簡単に思える。(実際、今回はC問題の方がB問題よりも簡単だった。)

上が本番で提出したコード。Cで書いたのが以下。

long long ll_pow(long long x, long long n);
#include<stdio.h>
#include<stdlib.h>
#define N 14
int main(void){
    char* s=(char*)malloc(sizeof(char)*N);
    fgets(s,N,stdin);
    long long ans=0,t=0;
    int length=0;
    while(s[length]>=65 && s[length]<=90)length++;
//    printf("length=%d\n",length);
    ans+=((26*(1-ll_pow(26,length-1))/(1-26)));
//    printf("sum1=%d\n",ans);
    for(int i=0;i<length;i++){
        t*=26;
        t+=(s[i]-65);
    }
    t++;
    ans+=t;
    printf("%lld\n",ans);
    free(s);
/*    for(int i=1;i<12;i++){
        printf("%lld\n",ll_pow(26,i));
    }*/
    return 0;
}
long long ll_pow(long long x, long long n){
    long long ret=1; 
    while(n>0){
        if((n&1)==1)ret*=x;
        x*=x;
        n>>=1;
    }
    return ret;
}
    

やりかたは基本的にrubyでやった時と同じで、与えられた文字列がn文字からなる場合、n-1個までの文字からなる文字列の個数の個数の和( 26+26^2+26^3+...+26^(n-1) )を求める部分と、与えられたn個の文字からなる文字列を26進法で表された数値とすると、それが10進法で何になるか(厳密に言うと、ここで最初の数は0なので、「何番目か」という問いに答えるには1を足す必要がある)を求める部分とからなる。この前半部分について、今回は少し趣向を変えて、等比数列の和を求める公式を使って求めた。

あと、今回必要になったのは、最初の文字列の読み込みをする際に何文字分のメモリを確保すればいいかを知るために、26+26^2+26^3+...+26^(n-1) < 10^16 を満たす最大のnを求めること。制約が、文字列の文字数ではなく、1個からn個までからなる文字列の累計の個数として与えられているためだ。上の不等式の左辺は26*(1-26^(n-1))/(1-26)=26*(26^(n-1)-1)/25。よって、26*(26^(n-1)-1)/25 < 10^16 とし、両辺の10を底とする対数を取ると、log10(26)+log10(26^(n-1)-1)-log10(25) < 16。ここで、(26^(n-1)-1)は26^(n-1)にほぼ等しいとみなして計算すると、log10(26)+(n-1)*log10(26)-2*log10(5) < 16。

log10(26)=1.415 (なお、これは、常用対数表でlog10(2.6)=0.415 となっているのを読んで、log10(26)=log10(2.6*10)=log10(2.6)+log10(10)=1.415) として求められる。log10(5)=0.699 を代入して、n*1.415-2*0.699 < 16。n < 12.1。となって、nの文字数は最大で12と分かる。(後になって気づいたが、これは自分で計算しなくても、入出力例の最後に書いてあった。)


2進数

ABC306-B

問題概要

a[0],a[1],...a[n-2],a[n-1]が、それぞれx^0,x^1,...,x^(n-2),x^(n-1)の係数である。x=2として、この数を10進法で出力せよ。(n=64, a[0],a[1],...a[n-2],a[n-1]は0または1)

解答

a=gets.chomp.split(" ").map(&:to_i)
s=a.reverse.join("")
puts s.to_i(2)
    

与えられた配列は順に2進法での大きい桁の方からの数になるので、文字列を逆順にしてから2進法→10進法の変換を行うと、求める値が得られる。

上が本番で提出したコードだが、xが2以外の一般の数でも使えるようにしたのが、次のコード。

a=gets.chomp.split(" ").map(&:to_i)
n=0
t=1
base=2
a.size.times do |i|
    n+=a[i]*t
    t*=base
end
puts n
    

Cでやった一つ目のコードが以下。

void print_ivector(int* a,int n);
#include<stdio.h>
#include<stdlib.h>
#define N 130
#define N2 64 
int main(void){
    char *s=(char*)malloc(sizeof(char)*N);
    int* a=(int*)malloc(sizeof(int)*N2);
    fgets(s,N,stdin);
    int i=0,j=0;
    while(*(s+i)!=0 && *(s+i)!='\n'){
        if(*(s+i)==48 || *(s+i)==49){
            *(a+j)=*(s+i)-48;i++;j++;
        }else if (*(s+i)==32)i++;
    }
//    print_ivector(a,N2);
    unsigned long long num=0,t=1;
    for(int i=0;i<N2;i++){
        num+=a[i]*t;
        t*=2;
    }
    printf("%llu\n",num);
    return 0;
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

a[0],a[1],...a[n-2],a[n-1]が全て1だった場合、この数は 2^64-1 になる。符号なし64ビット整数の最大値だ。十進法では、log10(2^64)=64*log10(2)=64*0.301=19.262 だから、およそ 10^19.262=(10^0.262)*10^19=1.836*10^19となる。ちなみに、2^64-1の十進法での正確な値は 18,446,744,073,709,551,615 である。というわけで、ここは unsigned long long を使うしかない。

もう一つが以下。

#include<stdio.h>
#include<stdlib.h>
#define N 130
#define N2 64 
int main(void){
    char *s=(char*)malloc(sizeof(char)*N);
    int* a=(int*)malloc(sizeof(int)*N2);
    fgets(s,N,stdin);
    int i=0,j=0;
    while(*(s+i)!=0 && *(s+i)!='\n'){
        if(*(s+i)==48 || *(s+i)==49){
            *(a+j)=*(s+i)-48;i++;j++;
        }else if (*(s+i)==32)i++;
    }
    unsigned long long num=0;
    for(int i=N2-1;i>=0;i--){
        num*=2;
        num+=a[i];
    }
    printf("%llu\n",num);
    return 0;
}
    

こちらの方が僅かだが時間は余計にかかるようだ。

2022年10月30日日曜日

配列の判定・操作(1)

環状配列上で隣接する数か

ABC240-A

問題概要

1から10までの数がこの順で並ぶ環状配列において、aとbが隣接しているか判定せよ。(a<b)

解答

a,b=gets.chomp.split(" ").map(&:to_i)
if a==1 and b==10 then
    puts "Yes"
elsif
    (b+10-a)%10==1
    puts "Yes"
else
    puts "No"
end
    

上が本番で提出したコード。しかし、なぜ単純にb-aとするのではなく、(b+10-a)%10==1としたのか、今となってはわからない。下がCで書いたコードだが、こちらは単にb-aとして問題なく動いている。

#include<stdio.h>
int main(void){
    int a,b;
    scanf("%d %d\n",&a,&b);
    if ((a==1 && b==10) || b-a==1){
        printf("Yes\n");
    }else{
        printf("No\n");
    }
    return 0;
}
    

連鎖移動

ABC241-A

問題概要

0..9の数字を要素とする配列aがある。はじめのインデックスは0。「現在のインデックスに対応する値を新しいインデックスとする。」ことを3回繰り返す。最終時点での値を求めよ。

解答

a=gets.chomp.split(" ").map(&:to_i)
idx=0
3.times do
    idx=a[idx]
end
puts idx
    

上が本番で提出したコード。

Cでやったのが以下。

#include<stdio.h>
#include<stdlib.h>
#define N 10
#define T 3
int main(void){
    int* a=(int*)malloc(sizeof(int)*N);
    for(int i=0;i<N;i++){
        scanf("%d",a+i);
    }
    int idx=0;
    for(int i=0;i<T;i++){
        idx=*(a+idx);
    }
    printf("%d\n",idx);
    return 0;
}
    

ABC241-B

問題概要

自然数を要素とする長さnの配列aがある。i=1..mにわたり、i回目に値b[i]を配列aから取り除く。m回目の操作までの間に、取り除くべき数値が配列aからすでに無くなっているか判定せよ。(なくなっていればNo, なくなっていなければYesを出力)

解答

def bsearch(a,x)
    n=a.size
    l=0
    r=n
    while r-l>0.9 do
        m=(r+l)/2
#        puts "l=#{l} r=#{r}"
        if a[m]<x then
            l=m+1
        else
            if a[m]==x && (m==0 || a[m-1]<x) then
                return m
            else
                r=m
            end
        end
    end
    return -1
end
n,m=gets.chomp.split(" ").map(&:to_i)
a=gets.chomp.split(" ").map(&:to_i)
b=gets.chomp.split(" ").map(&:to_i)
a.sort!
m.times do |i|
    t=bsearch(a,b[i])
    if t==-1
        puts "No"
        exit
    else
        a.delete_at(t)
    end
end
puts "Yes"
    

m回にわたり、配列aにb[i]が含まれているかどうかを二分探索で調べ、含まれていればその値をaから取り除くことを繰り返している。

上が本番での提出コード。Cで書いたのが以下だが、まずTLE等になった次のコードを掲げる。

typedef struct node{
    struct node *prev;
    struct node *next;
    int v;
} node;
void print_ivector(int* a,int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#define N1 11
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int i=0;
    while(*(s1+i)>=48 && *(s1+i)<=57)i++;
    i++;
    int m=readint(s1+i);
    char* s2=(char*)malloc(sizeof(char)*(11*n+1));
    if(s2==NULL){
        printf("memory allocation to s2 failed\n");
        exit(1);
    }
    fgets(s2,(11*n+1),stdin);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL){
        printf("memory allocation to a failed\n");
        exit(1);
    }
    input_iarray(s2,a,n,(11*n+1));
    free(s2);
    char* s3=(char*)malloc(sizeof(char)*(11*m+1));
    if(s3==NULL){
        printf("memory allocation to s3 failed\n");
        exit(1);
    }
    fgets(s3,(11*m+1),stdin);
    int* b=(int*)malloc(sizeof(int)*m);
    if(b==NULL){
        printf("memory allocation to b failed\n");
        exit(1);
    }
    input_iarray(s3,b,m,(11*m+1));
    free(s3);
    node* nds=(node*)malloc(sizeof(node)*n);
    if(nds==NULL){
        printf("memory allocation to nds failed\n");
        exit(1);
    }
    node *first=nds;
    node *end=(nds+n-1);
    for(int j=0;j<n;j++){
        if(j==0 && j==n-1){
            (nds+j)->v=*a;
        }else if(j==0){
            (nds+j)->prev=NULL;
            (nds+j)->next=(nds+1);
            (nds+j)->v=*a;
        } else if(j==n-1){
            (nds+j)->prev=(nds+n-2);
            (nds+j)->next=NULL;
            (nds+j)->v=*(a+n-1);
        } else {
            (nds+j)->prev=(nds+j-1);
            (nds+j)->next=(nds+j+1);
            (nds+j)->v=*(a+n-1);
        }
    }
/*
    printf("n=%d m=%d\n",n,m);
    print_ivector(a,n);
    print_ivector(b,m);
    struct node* k=first;
    do {
        printf("%d ",k->v);
    }while((k->next)!=NULL);
    printf("\n");
*/
    for(int j=0;j<m;j++){
        int found=0;
        while(1) {
            node *nd=first;
            if ((nd->v)==*(b+j)){
                found=1;
                if(nd->prev==NULL && nd->next==NULL){
                    nd=NULL;
                }
                else if(nd->prev==NULL){
                    first=(nd->next);
                    (nd->next)->prev=NULL;
                }else if(nd->next==NULL){
                    end=(nd->prev);
                    (nd->prev)->next=NULL;
                }else{
                    (nd->prev)->next=(nd->next);
                    (nd->next)->prev=(nd->prev);
                }
                break;
            }
            if(nd==end){
                break;
            }else{
                nd=(nd->next);
            }
        }
        if(found==0){
            printf("No\n");
            exit(0);
        }
    }
    printf("Yes\n");
    
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

上のコードは一部のテストケースではACになるものの、他はTLEになったりWAになったりする。TLEと言っても、この問題の制約ではaもbも要素数は最大で1000個だから、計算量の問題ではない。どうも、whileループが無限ループになっているようだ。そこで、Stackoverflowで質問した。

actorbugさんから回答を頂いたが、やはり、whileループが無限ループになっていて、node *nd=first;という一文をwhileループの中においてしまっていたため、毎回先頭に戻ることになってしまっていたのだった。

これに加えて、他にも見つけた誤りを修正したのが次のコード。これは、一つだけREになった。

typedef struct node{
    struct node *prev;
    struct node *next;
    int v;
} node;
void print_ivector(int* a,int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#define N1 11
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int i=0;
    while(*(s1+i)>=48 && *(s1+i)<=57)i++;
    i++;
    int m=readint(s1+i);
    char* s2=(char*)malloc(sizeof(char)*(11*n+1));
    if(s2==NULL){
        printf("memory allocation to s2 failed\n");
        exit(1);
    }
    fgets(s2,(11*n+1),stdin);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL){
        printf("memory allocation to a failed\n");
        exit(1);
    }
    input_iarray(s2,a,n,(11*n+1));
    free(s2);
    char* s3=(char*)malloc(sizeof(char)*(11*m+1));
    if(s3==NULL){
        printf("memory allocation to s3 failed\n");
        exit(1);
    }
    fgets(s3,(11*m+1),stdin);
    int* b=(int*)malloc(sizeof(int)*m);
    if(b==NULL){
        printf("memory allocation to b failed\n");
        exit(1);
    }
    input_iarray(s3,b,m,(11*m+1));
    free(s3);
    node* nds=(node*)malloc(sizeof(node)*n);
    if(nds==NULL){
        printf("memory allocation to nds failed\n");
        exit(1);
    }
    node *first=nds;
    node *end=(nds+n-1);
    for(int j=0;j<n;j++){
        if(j==0 && j==n-1){
            (nds+j)->v=*a;
        }else if(j==0){
            (nds+j)->prev=NULL;
            (nds+j)->next=(nds+1);
            (nds+j)->v=*a;
        } else if(j==n-1){
            (nds+j)->prev=(nds+n-2);
            (nds+j)->next=NULL;
            (nds+j)->v=*(a+n-1);
        } else {
            (nds+j)->prev=(nds+j-1);
            (nds+j)->next=(nds+j+1);
            (nds+j)->v=*(a+j);
        }
    }
/*
    printf("n=%d m=%d\n",n,m);
    print_ivector(a,n);
    print_ivector(b,m);
    struct node* k=first;
    do {
        printf("%d ",k->v);
    }while((k->next)!=NULL);
    printf("\n");
*/
    for(int j=0;j<m;j++){
        int found=0;
        node *nd=first;
        while(1) {
            if ((nd->v)==*(b+j)){
                found=1;
                if(nd->prev==NULL && nd->next==NULL){
                    nd=NULL;
                }
                else if(nd->prev==NULL){
                    first=(nd->next);
                    (nd->next)->prev=NULL;
                }else if(nd->next==NULL){
                    end=(nd->prev);
                    (nd->prev)->next=NULL;
                }else{
                    (nd->prev)->next=(nd->next);
                    (nd->next)->prev=(nd->prev);
                }
                break;
            }
            if(nd==end){
                break;
            }else{
                nd=(nd->next);
            }
        }
        if(found==0){
            printf("No\n");
            exit(0);
        }
    }
    printf("Yes\n");
    
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

その、REになるテストケースを見ると、

1 1
1
1
という内容だ。

Paiza.ioにこのデータを入れてみると、Yesという正解になるのだが、AtCoderのコードテストで試してみると、終了コード139と、やはりエラーになるようだ。n=1,m=1というのは入力例2と同じ。違うのは、入力例2ではaとbのそれぞれの唯一の要素は異なる値だが、こちらではそれが同じ値だということ。

nd->prev==NULL && nd->next==NULL の場合に

        if(j==m-1){
            printf("Yes\n");
            exit(0);
        }else{
            printf("No\n");
            exit(0);
        }
    
という微修正を加えてみたが、結果は同じ。一度諦めてから翌日もう一度コードを見直してみて、最初に配列から連結リストにデータを入れなおした際、データ数が1個の場合にその前後をNULLにする処理をしていなかったことに気づく。しかし、これでよく nd->next==NULL を停止条件にしている入力確認のためのコードでエラーにならなかったなと思う。この確認はPaizaでしていて、コード全体もPaizaでは成功しているから、Paizaでは自動的に何も入れてないところはNULLになっているということか。

最終的にACになったコードは以下。

typedef struct node{
    struct node *prev;
    struct node *next;
    int v;
} node;
void print_ivector(int* a,int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#define N1 11
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int i=0;
    while(*(s1+i)>=48 && *(s1+i)<=57)i++;
    i++;
    int m=readint(s1+i);
    char* s2=(char*)malloc(sizeof(char)*(11*n+1));
    if(s2==NULL){
        printf("memory allocation to s2 failed\n");
        exit(1);
    }
    fgets(s2,(11*n+1),stdin);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL){
        printf("memory allocation to a failed\n");
        exit(1);
    }
    input_iarray(s2,a,n,(11*n+1));
    free(s2);
    char* s3=(char*)malloc(sizeof(char)*(11*m+1));
    if(s3==NULL){
        printf("memory allocation to s3 failed\n");
        exit(1);
    }
    fgets(s3,(11*m+1),stdin);
    int* b=(int*)malloc(sizeof(int)*m);
    if(b==NULL){
        printf("memory allocation to b failed\n");
        exit(1);
    }
    input_iarray(s3,b,m,(11*m+1));
    free(s3);
    node* nds=(node*)malloc(sizeof(node)*n);
    if(nds==NULL){
        printf("memory allocation to nds failed\n");
        exit(1);
    }
    node *first=nds;
    node *end=(nds+n-1);
    for(int j=0;j<n;j++){
        if(j==0 && j==n-1){
            nds->prev=NULL;
            nds->next=NULL;
            (nds+j)->v=*a;
        }else if(j==0){
            (nds+j)->prev=NULL;
            (nds+j)->next=(nds+1);
            (nds+j)->v=*a;
        } else if(j==n-1){
            (nds+j)->prev=(nds+n-2);
            (nds+j)->next=NULL;
            (nds+j)->v=*(a+n-1);
        } else {
            (nds+j)->prev=(nds+j-1);
            (nds+j)->next=(nds+j+1);
            (nds+j)->v=*(a+j);
        }
    }
/*
    printf("n=%d m=%d\n",n,m);
    print_ivector(a,n);
    print_ivector(b,m);
    struct node* k=first;
    do {
        printf("%d ",k->v);
    }while((k->next)!=NULL);
    printf("\n");
*/
    for(int j=0;j<m;j++){
        int found=0;
        node *nd=first;
        while(1) {
            if ((nd->v)==*(b+j)){
                found=1;
                if(nd->prev==NULL && nd->next==NULL){
                    if(j==m-1){
                        printf("Yes\n");
                        exit(0);
                    }else{
                        printf("No\n");
                        exit(0);
                    }
                }
                else if(nd->prev==NULL){
                    first=(nd->next);
                    (nd->next)->prev=NULL;
                }else if(nd->next==NULL){
                    end=(nd->prev);
                    (nd->prev)->next=NULL;
                }else{
                    (nd->prev)->next=(nd->next);
                    (nd->next)->prev=(nd->prev);
                }
                break;
            }
            if(nd==end){
                break;
            }else{
                nd=(nd->next);
            }
        }
        if(found==0){
            printf("No\n");
            exit(0);
        }
    }
    printf("Yes\n");
    
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

配列に含まれない最小の非負整数

ABC245-B

問題概要

非負整数を要素とする長さnの配列aが与えられる。aに含まれない最小の非負整数を求めよ。

解答

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
a.sort!
b=(0..n).to_a
c=b-a
puts c.min
    

上が本番で提出したコード。見直してみると、aをソートしている意味がないし、bは0からaの全範囲の一つ上までを含む配列を作ればよいので、制約によりa[i]の最大値が2000であることを考え、b=(0..2000+1).to_aとするか、或いはb=(0..a.max+1).to_a としてもよかった。

やり直したのが以下のコード。配列の引き算を使っていない分、こちらのほうがいいかと思う。

n=gets.to_i
a=gets.chomp.split(" ").map(&:to_i)
ref=Array.new(2002,false)
n.times do |i|
    ref[a[i]]=true
end
(0..2001).each do |i|
    if ref[i]==false
        puts i
        exit
    end
end
    

Cで書いたのが以下。

void qsort_1(int* a, int l, int r);
void print_ivector(int* a,int n);
void input_iarray(char* s,int* a,int n,int imax);
int readint(char *s);
#define N 10002
#define N1 6
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    char* s3=(char*)malloc(sizeof(char)*N);
    fgets(s3,N,stdin);
    int * a=(int*)malloc(sizeof(int)*n);
    input_iarray(s3,a,n,N);
//    print_ivector(a,n);
    qsort_1(a,0,n-1);
//    print_ivector(a,n);
    int ans=0;
    int i=0;
    do {
        if(a[i]==ans){
            ans++;
            if(i==(n-1))break;
            else{
                i++;
                while(i<n-1 && a[i]<ans)i++;
                }
        }
        else break;
    }while(i<n);
    printf("%d\n",ans);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void qsort_1(int* a, int l, int r){
    int left, right, d, temp;
    if (l>=r)return;
    d=*(a+l);
    for(left=l,right=r;left<right;){
        while(left<=right && a[left]<=d)left++;
        while(left<=right && a[right]>d)right--;
        if(left<right){
            temp=a[left];
            a[left]=a[right];
            a[right]=temp;
        }
    }
    temp=a[l];
    a[l]=a[right];
    a[right]=temp;
    qsort_1(a,l,right-1);
    qsort_1(a,right+1,r);
}
void input_iarray(char* s,int* a,int n,int imax){
    int aidx=0,i=0; 
    while(*(s+i)!=0 && i<imax){
        if (*(s+i)<48 || *(s+i)>57){i++;}
        else{
            int bidx=i;
            while(*(s+i)>=48 && *(s+i)<=57){
                i++;
            }
            int t=0;
            for(int j=bidx;j<i;j++){
                t*=10;
                t+=*(s+j)-48;
            }
        *(a+aidx++)=t;
        }
    }
    if (aidx!=n)printf("n!=aidx aidx=%d\n",aidx);
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}
    

まず配列をソートし(今回は実際に必要なソート。クイックソートを(ほぼ本の引き写しだけだけど)初めて実装した)、答えの候補を最小の0から始め、1ずつインクリメントしながら、それが配列の中に含まれなくなる所を探している。配列の最小値と答えの候補が一致した時には答えの候補の値をインクリメントするとともに配列のインデックスもインクリメントするが、配列には同じ値が複数並んでいる可能性があるので、もしインクリメント後のインデックスに対応する値が答えの候補よりも小さければ、配列の値が答えの候補の値以上になるまでインデックスをインクリメントしている。


分類無し

ABC247-B

問題概要

それぞれ2個の文字列からなるn組の文字列の組が与えられる。各組の第一要素と第二要素のいずれか一方のみを選ぶとき選ばれた全てが、他の各組の第1または第2要素と一致しないようにすることができるか。

解答

n=gets.to_i
a=[]
n.times do |i|
    a[i]=gets.chomp.split(" ")
end
flag=true
n.times do |i|
    b=a.dup
    b.delete_at(i)
    b.flatten!
    if b.include?(a[i][0]) && b.include?(a[i][1])
        flag=false
    end
end
if flag==true
    puts "Yes"
else
    puts "No"
end
    

上が本番で提出したコードなのだが、自分で半年余りの後に読み返してみて、何をやっているのかしばらく分からなかった。

これは、各組について、その第一要素と第二要素のいずれかが、他の組の中で出現する文字列と一致していなければ、その組は他の組と被らない文字列を持つことができることを使っている。そこで、各組ごとに、全文字列からその組の文字列二つを除いた要素数2n-2の配列を作り、その組の第一文字列か第二文字列のどちらかが、その要素数2n-2の配列に含まれていなければその組は大丈夫として(具体的には、第一文字列も第二文字列も要素数2n-2の配列に含まれていればフラグをfalseにして)、全ての組が大丈夫であれば(flagがfalseになっていなければ)、「作ることができる」と判定しているのである。(自分が書いたコードなのに、人が書いたコードの解説をしている気分だ。)

Cで書いたのが以下。

typedef struct nom {
    char first[11];
    char last[11];
}nom;
int scmp1(char* s, char* t, int n);
int readint(char *s);
#define N1 5
#define N2 23
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    nom* nms=(nom*)malloc(sizeof(nom)*n); 
    if(nms==NULL){
        printf("memory allocation to nms failed\n");
        exit(1);
    }
    for(int j=0;j<n;j++){
        char* s2=(char*)malloc(sizeof(char)*N2);
        if(s2==NULL){
            printf("memory allocation to s2(%d) failed\n",j);
            exit(1);
        }    
        fgets(s2,N2,stdin);
        int k=0,l=0;
        while(*(s2+k)!=32){
            (nms+j)->first[k]=*(s2+k);
            k++;
        }
        (nms+j)->first[k]=0;
        k++;
        while(*(s2+k)>=97 && *(s2+k)<=122){
            (nms+j)->last[l]=*(s2+k);
            k++;l++;
        }
        (nms+j)->last[l]=0;
    }
/*
    for(int j=0;j<n;j++){
        printf("---nms(%d)---\n",j);
        printf("%s\n",(nms+j)->first);
        printf("%s\n",(nms+j)->last);
    }
*/
    int flag_first=1,flag_last=1;
    for(int j=0;j<n;j++){
        for(int h=0;h<n;h++){
            if(h==j)continue;
            if(scmp1((nms+j)->first,(nms+h)->first,10)==0){
                flag_first=0;
            }
            if(scmp1((nms+j)->first,(nms+h)->last,10)==0){
                flag_first=0;
            }
            if((scmp1((nms+j)->last,(nms+h)->first,10)==0)){
                flag_last=0;
            }
            if((scmp1((nms+j)->last,(nms+h)->last,10)==0)){
                flag_last=0;
            }
        }
        if(flag_first==0 && flag_last==0){
            printf("No\n");
            exit(0);
        }
    }
    printf("Yes\n");
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
int scmp1(char* s, char* t, int n){
    int i=0;
    while(1){
        if (*(s+i)==0 && *(t+i)==0) return 0;
        else if (*(t+i)!=*(s+i)) return (*(t+i)-*(s+i));
        else i++;
        if(i>=n){
//            printf("s=%s, t=%s, i=%d\n",s,t,i);
//            printf("Comparing failed.\n");
        }
    }
}
    

ただ、上のコードはACにはなったものの、想定外の動作が残る部分があった。scmp1関数の中の if(i>=n) 条件文の中身をコメントアウトなしで実行すると、例えば次の例(テストケース名は02_except_one_00)

    100
    avmlajlxrf zttmjemxex
    vauvmzqhzs uzgvclqnem
    lcljwpbfsg xnbsoouwsn
    zifcqxivtq gjcupzgkyz
    cinpwuotzy yxojlpipjn
    tdnemryudq skxibydrct
    tdjdtgtipl eurwjwxvyv
    abzttwzxhb amycbijxta
    vwzjmmlaew yxojlpipjn
    xteucdfimo syiwqxsyvy
    vowbgfxsqv sjcegwybmg
    urdgjzthal cckapzefmh
    jlqemwztfg yzlusdaezt
    ejwwcutzsa atxkasugou
    fvvvsecqvg csloaexqar
    xdudibkgmt dmzuyjjkfu
    jnmfxnucto anlkpmegdk
    hlwnwahibo wdwgecsbqe
    xoeqypczez txgzzkdeco
    vrwhijewvl wytsxzogzb
    flocshjexi vrounuangs
    giiikhuvlw eapudjjlxz
    asvwjrhpck oyyrexwiri
    cqsqohapin ddpdojipiq
    hgtmfsuedp tkjqmgetkl
    emrjmdsahn urmlzavbyc
    odultqflzd obnwbmbrbv
    jhfxadevqt cinpwuotzy
    dyvarcvxms tduoqcljzw
    pcumzuyzgs xbfsoxvtlx
    slzgvyptkq begswrioai
    sikliwzvqd ivkzfnjnqs
    fxfomljyeo rohrqgsbrr
    mwtptktucb gabwlmmezw
    swydbovjqx ybfbjxmlkw
    qhpbuccyhu dsaxxwfler
    guqxzzgrlr ftimlzorzv
    bqklofbwzb pyemgoqbqp
    xnzsfrgfqd fctpafbged
    safprjkxav iducfibsms
    bgaczxkjjo qdubpubmqy
    gjfgwuapdu mxngjzuyzs
    gfmrbrczyv wkybysnbzh
    cpqnmbijhc tisbxwevsl
    yyepttinlp qxdlcyaryl
    rtfvtlsaed gxnmgfdlaz
    cztjbttosy xvgevjycvc
    niqyddonks pcntudhjex
    eprhqytehu gxoechnwzq
    uewhzvtawt hdkzmpoxdd
    qrtpqcgzid jmdnedpvde
    cbobbqkhvo fecwfsshrs
    lxlwxjdune nbfsjuwwfl
    nfolbzlgjz ayiiidkxpv
    secemmlcyo binjogcrin
    evzuvmvvxi wsxrlspmwe
    edufzrlfzz uhzqiupfoc
    cjehxnsfew lfxcujwugl
    vfvxpvhkzp zwhyamzuqc
    bmhndupair jmijqprnqv
    rrxvdkpxxe comgvwojrd
    shhltqafiy axpqnlimij
    qwjlqpuqyr rwrkgculnr
    isiadtwyew gmgrjchxeq
    asydjlrgnu rsqxsdaeke
    byeogugzvd oimjblsaqp
    etfzxishqr zicggawrny
    vrkvpixskp pootisasgb
    jyquzkympl kpgrarjurw
    irokbsgmzn axnmijtbuo
    jcicrhohws glmxshpofv
    mlcltqliqd edgalwqyrz
    qabdaqkuvk gpwzxamavm
    rjuybctves qoltuirrbt
    ceehfrnnlt wzqpfbegeb
    bygsepvzvs wivbkxcwwr
    bztgqkaszz kbddlinrdl
    bgcwkfhjfd anvaxdphvm
    hmkrfrrykj tposlkgyfu
    cvpzdchrcf jrkecgpdod
    ziyqvltwjr jjywyobhow
    mspuosmxsw plkwluwjpg
    lnxxowqjmu eggxviqrxd
    dgwlamwtdf svrlcuukan
    edrefbtrce khqwssekyx
    uhcvrasuxf vtvrbfeswq
    jupyipafzd ufewjurxbj
    bltjuxbtkv zmfhstxyzx
    zgcxjwddcs xafyqtugis
    glbrpmckrt xqxemkieyn
    epbfldpccl idkkwdfvrc
    rlcsstqdlx rvdzydkjnp
    rhknmgmqdq xyumddhczm
    ignokmfecc tizaaxfkgh
    zlinrzhweq zzdpcqbjvc
    emehoqjlcq gmvctsblgw
    yargqiezlu oyifpuekox
    ntveljeitx coyexswfvj
    nbejnmvkdn amgirkcihn
    lxlaawoatn qjofmdhvwt
で、
    s=yxojlpipjn, t=yxojlpipjn, i=10
    Comparing failed.
    s=cinpwuotzy, t=cinpwuotzy, i=10
    Comparing failed.
という出力がされてしまうのである。

文字列の文字数が10文字、末尾のヌル文字も含めて11文字あるとき、i=0からはじめたカウンターが11回廻ってi==n(=10)になったときには if (*(s+i)==0 && *(t+i)==0) return 0; の文が実行され、その後に来ている if(i>=n) という条件分岐の所に行くことはないはずなのに、なぜだ、と悩んだ。

scmp1関数の実装がおかしいのかと考えて、

    char str1[4]="abc";
    char str2[4]="abc";
    scmp1(str1,str2,4);
というコードを試しに入れてみたが、こちらでは上の現象は起きない。

原因が分からず、結局 Stackoverflow で質問した。

回答を頂いて分かったことは、例えば上のテストケース02_except_one_00のような文字数10文字で同じ文字列があるときには、i=9(10回目のループ)で最後の文字の比較が行われ、それが同じ文字なので else i++; が実行されてi=10となり、その直後のif(i>=n)以下が実行されてしまうということである。つまり、この場合、scmp1関数の第三引数として渡すべきは10ではなく11だったということになる。文字数が最大10文字だから、11文字目のヌル文字の比較が行われるのはi=0から始めてi=10のとき、だから i==10 となるかで上限チェックをすればいいと思い込んでいたのが間違いだったのである。

それに、よく見れば、abcとabcを比較した時にはscmp1関数の第三引数に文字数+1の4を渡している。だから、それと同じならば、文字数が10の時にはscmp1関数の第三引数は11でなければならないはずだったのだ。分かってみれば当たり前のことなのだが、自分では気づかないものだと実感する。

以下が、そこを修正したコード。先に掲げたコードは、ACにはなったが厳密にはバグのあるコードということになる。

typedef struct nom {
    char first[11];
    char last[11];
}nom;
int scmp1(char* s, char* t, int n);
int readint(char *s);
#define N1 5
#define N2 23
#define N3 11
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    nom* nms=(nom*)malloc(sizeof(nom)*n); 
    if(nms==NULL){
        printf("memory allocation to nms failed\n");
        exit(1);
    }
    for(int j=0;j<n;j++){
        char* s2=(char*)malloc(sizeof(char)*N2);
        if(s2==NULL){
            printf("memory allocation to s2(%d) failed\n",j);
            exit(1);
        }    
        fgets(s2,N2,stdin);
        int k=0,l=0;
        while(*(s2+k)!=32){
            (nms+j)->first[k]=*(s2+k);
            k++;
        }
        (nms+j)->first[k]=0;
        k++;
        while(*(s2+k)>=97 && *(s2+k)<=122){
            (nms+j)->last[l]=*(s2+k);
            k++;l++;
        }
        (nms+j)->last[l]=0;
    }
/*
    for(int j=0;j<n;j++){
        printf("---nms(%d)---\n",j);
        printf("%s\n",(nms+j)->first);
        printf("%s\n",(nms+j)->last);
    }
*/
    int flag_first=1,flag_last=1;
    for(int j=0;j<n;j++){
        for(int h=0;h<n;h++){
            if(h==j)continue;
            if(scmp1((nms+j)->first,(nms+h)->first,N3)==0){
                flag_first=0;
            }
            if(scmp1((nms+j)->first,(nms+h)->last,N3)==0){
                flag_first=0;
            }
            if((scmp1((nms+j)->last,(nms+h)->first,N3)==0)){
                flag_last=0;
            }
            if((scmp1((nms+j)->last,(nms+h)->last,N3)==0)){
                flag_last=0;
            }
        }
        if(flag_first==0 && flag_last==0){
            printf("No\n");
            exit(0);
        }
    }
    printf("Yes\n");
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
int scmp1(char* s, char* t, int n){
    int i=0;
    while(1){
        if (*(s+i)==0 && *(t+i)==0) return 0;
        else if (*(t+i)!=*(s+i)) return (*(t+i)-*(s+i));
        else i++;
        if(i>=n){
            printf("s=%s, t=%s, i=%d\n",s,t,i);
            printf("Comparing failed.\n");
        }
    }
}
    

配列の生成

ABC247-C

問題概要

列 S を次のように定義します。

  1. s[1]は 1 つの 1 からなる長さ 1 の列である。
  2. S[n](n は 2 以上の整数) は S[n−1],n,S[n−1]をこの順につなげた列である。
たとえば S[2]はS[1],2,S[1]をこの順につなげた列なので 1,2,1 である。 S[3]はS[2],3,S[2]をこの順につなげた列なので 1,2,1,3,1,2,1 である。 N が与えられるので、列 S[n]をすべて出力してください。

解答

問題の定義の通りに処理する。具体的には、nが1ならば1を出力。nが2以上ならば、初期値としてa=[1]を作り、あとは2からnに至るまで、a[n]=a[n-1](連結)[n](連結)a[n-1]という漸化式で新しい配列を作り、それを出力していく。見直してみると、nが1の時の処理を分ける必要はなかったが、まあいい。

n=gets.to_i
a=[]
if n==1
    puts 1
    exit
else
    a=[1]
    (2..n).each do |i|
        a=a+[i]+a
    end
end
puts a.join(" ")
    

上が本番で提出したコード。C問題としては珍しく簡単だった。

さて、これをCでやるとなると、まず、全部で何項の数列になるかを計算しなければならない。a[n]の項数をc[n]とすると、c[1]=1, c[n+1]=1+2*c[n] だから、c[n+1]+1=2*(c[n]+1), c[1]+1=2 となり、c[n]+1=2*2^(n-1)=2^n で、c[n]=2^n-1 となる。n=1,2,3,4 の場合について、入力例からこれが正しいことが確認できる。

それで書いたのが以下。

void print_ivector(int* a,int n);
int readint(char *s);
#define N1 4
#include<stdio.h>
#include<stdlib.h>
int main(void){
    char* s1=(char*)malloc(sizeof(char)*N1);
    if(s1==NULL){
        printf("memory allocation to s1 failed\n");
        exit(1);
    }
    fgets(s1,N1,stdin);
    int n=readint(s1);
    int num,oldnum;
    int *new;
    for(int i=1;i<=n;i++){
        num=((1<<i)-1);
        if(i==1){
            int* temp=(int*)malloc(sizeof(int)*num);
            if(temp==NULL){
                printf("memory allocation to temp(i=1) failed\n");
                exit(1);
            }
            new=temp;
            *new=1;
            oldnum=1;
        }else{
//            printf("num=%d\n",num);
//            printf("oldnum=%d\n",oldnum);
            int* temp=(int*)realloc(new,sizeof(int)*num);
            if(temp==NULL){
                printf("memory allocation to temp(i=%d) failed\n",i);
                free(new);
                exit(1);
            }
            new=temp;
            for(int j=0;j<oldnum;j++){
//                printf("j=%d\n",j);
                *(new+num/2+1+j)=*(new+j);
            }
            *(new+num/2)=i;
            oldnum=num;
        }
    }
    print_ivector(new,num);
    return 0;
}
int readint(char *s){
    int i=0,ret=0;
    while(*(s+i)>=48 && *(s+i)<=57){
        ret*=10;
        ret+=*(s+i)-48;
        i+=1;
    }
    return ret;
}
void print_ivector(int* a,int n){
    for(int i=0;i<n-1;i++){
        printf("%d ",*(a+i));
    }
    printf("%d\n",*(a+n-1));
}