tekiheiの日記

競技プログラミングについて書きます

CodeForces 1286A Garland (R1700)

codeforces.com

問題

長さ Nの数列 pが与えられます。これは長さ Nの順列の一部が欠けたもので、欠けたところは p_i=0になっています。  pが順列になるように復元したときの、 pの複雑さの最小値を求めてください。ここで pの複雑さとは、隣接要素のペアであって、偶奇が異なるようなものの個数とします。

制約
  •  1 \le N \le 100
  •  0 \le p_i \le N\ (1 \le i \le N)
考察

偶奇だけが重要なので mod\ 2で考えます。まず、 p 2で割った余りで置き換えておきます( p_i=0だったところは p_i=-1とします)。左から順番に復元していくことにします。すると、それまでに使った 0と1の個数と、最後の要素の偶奇が同じであれば、後のことを考えるとコストが小さいほうがよいので、次のようなDPが考えられます。

$$ dp[i][j][k]:=左からi個復元して、それまでで0をj個使っていて、最後の要素の偶奇がkのときのコストの最小値 $$ 使った 1の個数は i-jなので状態として持たなくて良いです。

 dp[i][j][k]からの遷移は

  •  0を置く場合 $$ chmin(dp[i+1][j+1][0],dp[i][j][k]+k) $$

  •  1を置く場合
    $$ chmin(dp[i+1][j][1],dp[i][j][k]+(k\ xor\ 1)) $$

です。 0 1が置ける個数はそれぞれ \lfloor N/2 \rfloor \lceil N/2 \rceilなので、これを超えないようにしなければなりません。

#include<bits/stdc++.h>
using namespace std;

const int INF=0x3f3f3f3f;
int dp[110][110][2];

int main()
{
   int N; cin>>N;
   vector<int> p(N);
   for(int i=0;i<N;i++) cin>>p[i];

   if(N==1){ cout<<0<<endl; return 0; }

   for(int i=0;i<N;i++){
      if(p[i]==0) p[i]=-1;
      else p[i]=p[i]%2;
   }

   memset(dp,INF,sizeof(dp));
   if(p[0]==0 or p[0]==-1) dp[1][1][0]=0;
   if(p[0]==1 or p[0]==-1) dp[1][0][1]=0;

   for(int i=1;i<N;i++) for(int j=0;j<=i;j++) for(int k=0;k<2;k++) if(dp[i][j][k]<INF){
      if((p[i]==0 or p[i]==-1) and j<N/2){
         dp[i+1][j+1][0]=min(dp[i+1][j+1][0],dp[i][j][k]+(k^0));
      }
      if((p[i]==1 or p[i]==-1) and i-j<(N+1)/2){
         dp[i+1][j+0][1]=min(dp[i+1][j+0][1],dp[i][j][k]+(k^1));
      }
   }
   cout<<min(dp[N][N/2][0],dp[N][N/2][1])<<endl;
   return 0;
}

すでに数字が置かれているところも置かれていないとしています(置く数は決まっている)。こうすると最初に 0をたくさん使ってしまって、すでに決まっているところで置けないみたいになることがありますが、そのようなところからは遷移しないようにしているので大丈夫です。

感想

すでに置かれているところも新たに置くことにすると実装が軽くなりました。Editorialには貪欲法が書かれていましたがよく分かりませんでした。