i7_yoc's something

数学科…なのか?

JOI2018-2019予選 解法メモ・参加記

A-ソーシャルゲーム(Social Game)
・問題概要

JOI君はソシャゲを始める。
ログインボーナスがA(1<=A<=1000)コイン、7日連続でログインするとB(1<=B<=1000)コインもらえる。
C(1<=C<=10^6)枚のコインが欲しいとき何日ログインすべきか

・解法
 ・累計のコインがC以上になるまで愚直に探索

・実装
 こんな感じ

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
using namespace std;
typedef long long Int;

int main(){
  int a,b,c;
  cin>>a>>b>>c;
  int cnt=0;
  while(c>0){
    c-=a;
    cnt++;
    if(cnt%7==0) c-=b;
  }
  cout<<cnt<<endl;
}

B-すごろくと駒(Sugoroku and Pieces)
・問題概要

 ・すごろくがありスタートとゴールの間に1~2019の番号が振られている
 ・1個先に駒がいないorゴールに着かないなら毎回指定された駒を1個右へ

・解法
 ・実際にそれをプログラム上でやってみる
 ・i番目の駒がどこにあるかの配列と、マスiに駒がいるかどうかのbool型配列を用意して実装

・実装
 こんな感じ

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
using namespace std;
typedef long long Int;
bool used[2020]={0};
int pos[2020]={0};
int main(){
  int n;
  cin>>n;
  for(int i=1;i<=n;i++){
    cin>>pos[i];
    used[pos[i]]=1;
  }
  int m;
  cin>>m;
  for(int i=0;i<m;i++){
    int a;
    cin>>a;
    if(used[pos[a]+1]==0&&pos[a]+1!=2020){
      used[pos[a]]=0;
      used[pos[a]+1]=1;
      pos[a]+=1;
    }
  }
  for(int i=1;i<=n;i++) cout<<pos[i]<<endl;
}

C-マルバツスタンプ
・問題概要

 ・JOI君は○スタンプ、×スタンプ、○×スタンプをいくつか持っている
 ・○×スタンプは○×と×○のどっちの押し方もできる
 ・JOI君がスタンプを押した残骸をもらうので、JOI君が持っている○×の個数の最大は?

・解法
 ・要素数分、はじめから見ていく
 ・i番目!=i+1番目ならそこは○×を使って押せます
 ・この場合i+1番目は見なくて良いのでi+2番目の探索に移ります

・実装
こんな感じ

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
using namespace std;
typedef long long Int;
bool used[2020]={0};
int pos[2020]={0};
int main(){
  int n;
  string s;
  cin>>n;
  cin>>s;
  int ans=0;
  for(int i=0;i<n-1;i++){
    if((s[i]=='O'&&s[i+1]=='X')||(s[i]=='X'&&s[i+1]=='O')){
      ans++;
      i++;
    }
  }
  cout<<ans<<endl;
}

ここまで10分で埋めました。例年の過去問だと20分くらい書けてるので速いほうですね、嬉しい

D-日本沈没(Japan Sinks)
・問題概要

 ・日本をn個の区画に分ける。それぞれに高さA[i]がある。
 ・海面上昇で相対的に日本が沈む。ばいばい。
 ・だんだん高さが低い部分から海底になっていくが、そのとき連続していない一続きの陸地がc個あるとき島の数をcとする
 ・日本が完全消滅するまでに島が一番たくさんできるのはいつ?

・解法
 ・何も考えずに部分点1を見る。愚直にシュミレーションして間に合う。
  ・具体的には、日本の最高点に波が来るまで波の高さを1mずつ試す。
  ・毎回全体を見渡して島の数を数えていけばO(NAmax)<=4000000回の計算なので間に合う
 ・部分点2は高さの制約がないも同然
  ・そこで座標圧縮という技を使う
  ・具体例をみた方がわかりやすいかも

f:id:i7_yoc:20181209234607j:plain
 
  ・写真の場合、波の高さが2だろうが57だろうが99だろうが、区画1と3は浮いてて区画2は沈んでる
  ・→0、1、100、5000兆だけ見ればいい こうすれば5000兆回試さなくて済む
  ・全部の高さをvectorに入れてソートする、またはpriority_queueを使うなどしてこれを実装すれば7+8=15点

 ・5,6の全探索を埋めて、満点解法が生えた
f:id:i7_yoc:20181209235236j:plain
  ・この場合、5が沈むので島の数は増える
  ・しかし実際は1~3や7は5が沈んでも関係ない→その隣の状態だけを考えればいい
  ・海底:0,大陸の左端:1,大陸の右端:2,孤島:3,大陸内部:4とし、その状態がどのように変化するかを考えればO(N^2)くらいでできた
  ・これをACするのに2時間かけたが、これをACできたのは大きい

・実装
 満点解法だけ載せます。割とごちゃごちゃしているのでわかりにくいかも

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
using namespace std;
typedef unsigned long long Int;
typedef pair<Int,Int> P;
Int a[100005]={0};
vector<Int> v;
vector<P> island;

int main(){
  Int n,maxa=0,zcnt=0;
  cin>>n;
  for(Int i=0;i<n;i++){
    cin>>a[i];
    maxa=max(maxa,a[i]);
    if(a[i]==0) zcnt++;
    v.push_back(a[i]);
    island.push_back(P(a[i],i));
  }
  v.push_back(0);
  sort(v.begin(),v.end());
  sort(island.begin(),island.end());
  Int p=zcnt;
  Int ans=0;
  int info[200005]={0};
  Int is=0;
  for(Int j=0;j<n;j++){
    if(a[j]<=0&&is>0){
      ans++;
      is=0;
      if(j>=1){
        if(info[j-1]==1) info[j-1]=3;
        else info[j-1]=2;
      }
    }
    else {
      if(is==0&&a[j]<=0) info[j]=0;
      else if(is==0) info[j]=1;
      else info[j]=4;
      is=a[j];
    }
  }
  if(a[n-1]<=0) info[n-1]=0;
  else if(a[n-2]<0&&a[n-1]>0) info[n-1]=3;
  else info[n-1]=2;
  if(is>0) ans++;

  Int ans2=ans;
  for(Int i=0;i<v.size();i++){
    Int np=0;
    np=(lower_bound(island.begin(),island.end(),P(v[i]+(Int)1,(Int)0))-island.begin());
    for(int j=p;j<np;j++){
      Int pos=island[j].second;
      if(info[pos]==4){
        ans++;
        if(info[pos-1]==1) info[pos-1]=3;
        else info[pos-1]=2;
        if(info[pos+1]==2) info[pos+1]=3;
        else info[pos+1]=1;
      }
      else if(info[pos]==2){
        if(info[pos-1]==1) info[pos-1]=3;
        else info[pos-1]=2;
      }
      else if(info[pos]==3){
        ans--;
        info[pos]=0;
      }
      else if(info[pos]==1){
        if(info[pos+1]==2) info[pos+1]=3;
        else info[pos+1]=1;
      }
      info[pos]=0;
    }
    if(ans2<ans) ans2=ans;
    p=np;
  }
  cout<<ans2<<endl;
}

E-イルミネーション(Illumination)
・問題概要

 ・JOI氏は木をN本はやしている。電飾してEn-JOI-ableな冬を過ごしたい。
 ・まぶしすぎるのは嫌なのでm個の区間[l,r]では1本の木にしか電飾しない
 ・木ごとに美しさがあるのでそれを最大化したい

・解説(になっていない)
 ・結論から言うと部分点1しかとれませんでした
 ・全探索をします
 ・具体的には・・・すべての場所で電飾のあるなしを試します。計算量はO(2^N)
 ・10分でこれを書いた以外何も分かりませんでした。終わった後にDPするらしいと言われ納得したけど実装ができない

・実装
 bit全探索の書き方が知りたい人向けに一応・・・
 ちなみにi>>jはiのビットをjだけシフトさせたと言うことです。例えば57>>2は57=111001(2)より、2つ右にずらすと001110になります。

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
using namespace std;
typedef long long Int;
typedef pair<Int,Int> P;
Int a[200005]={0};
vector<P> v;
int main(){
  Int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>a[i];
  for(int i=0;i<m;i++){
    Int l,r;
    cin>>l>>r;
    v.push_back(P(l,r));
  }
  Int ans=0;
  for(Int i=1;i<(1<<(n+1));i++){
    bool f2=true;
    for(Int j=0;j<m;j++){
      Int l=v[j].first,r=v[j].second;
      bool f=false;
      for(int k=l;k<=r;k++){
        if(((i>>k)&1)==1){
          if(!f) f=true;
          else{
            f2=false;
            break;
          }
        }
      }
    }
    Int kans=0;
    if(f2){
      for(int j=1;j<=n;j++) if(((i>>j)&1)==1) kans+=a[j];
      ans=max(kans,ans);
    }
  }
  cout<<ans<<endl;
}

F-座席(Seats)
・問題概要
 >>
・国がN個あり、i番目とi+1番目は隣国
 ・IOIに各国からAi人が出る
 ・1列に並べるが、不正対策のため隣国や同国の選手を隣にしないように並べたい
 ・パターン数のmod10007を求めよ