SRM508 div2

easy
実はこのコードは間違い。-100 <= X <= 100, -100 <= R <= 100ならとりうる点として、{150, 200}とかもあり得るんじゃない??

	int countProbablePlaces(vector <int> X, vector <int> Y, vector <int> R) {
    int sz = X.size();
    int cnt = 0;
    for(int i = -100; i <= 100; i++) {
      for(int j = -100; j <= 100; j++) {
        bool flag = true;
        for(int k = 0; k < sz; k++) {
          if(abs(X[k]-i)+abs(Y[k]-j) > R[k]) {
            flag = false; break;
          }
        }
        if(flag) cnt++;
      }
    }
    return cnt;
	}


この手のミスはケアレスミスではないから注意!

    int sz = X.size();
    int cnt = 0;
    //ここを訂正する
    for(int i = -200; i <= 200; i++) {
      for(int j = -200; j <= 200; j++) {
        bool flag = true;
        for(int k = 0; k < sz; k++) {
          if(abs(X[k]-i)+abs(Y[k]-j) > R[k]) {
            flag = false; break;
          }
        }
        if(flag) cnt++;
      }
    }
    return cnt;
	}

medium
素数ということに気を取られがちだが、要するになんかの数で割って、Mを余り1の個所に移動させる最小の手順を求めればよい。
56 --> 14 --> 15(15%14==1)
56/14 = 4 = 2*2より、DIVIDEの操作は2回、SHIFTの操作は1回で3回となる。
SHIFTは右にも左にもシフトできるので、
M=7のとき、
1 7 15 //left shift:6回 right shift:8回でleft shiftした方が早く目的の値に到達できる。

問題の意味を読み取るのに時間がかかったので反省。

#define INF (1<<30)
  
class DivideAndShift {
	public:
	int getLeast(int N, int M) {
    int ans = INF;
    //i = 1から始まっている
    for(int i = 1; i <= N; i++) {
      if(N%i == 0) {
        int num = i;
        int tmp = N/i;
        int mod = M%tmp;
        int shift = min( abs(mod-1), tmp+1-mod);
        //M=iのとき分割した後の個数が1になるので、shiftの回数は必ず0になる。直前のコードの例外を補う。
        if(tmp == 1) shift = 0;
        int cnt = 0;
        int j = 2;
        //DIVIDEの回数を調べる(素因数分解した後の掛け算の個数)
        while(num != 1) {
          if(num%j == 0) {
            cnt++; num /=j;
          } else {
            j++;
          }
        }
        ans = min(cnt+shift, ans);
      }
    }
    return ans;         
	}


difficult
どう見てもDPで解くしかなさそうな問題.
dp[N+1][R+1]として、iまでが範囲のときの総数を直接求めようとすると詰まる.
なぜかというと、n+k == n|k でnが違えば結果が違ってくるから.漸化式にできない.
だから、dp[N+1][R+1]で総和がexactly iのときのn個選んだときの場合の数を DPにすればよい。

//これはだめな例
  int countSequences(int N, int R) {
    int dp[N+1][R+2];
    memset(dp, 0, sizeof dp);
    for(int i = 0; i <= R; i++) {
      dp[1][i] = 1;
    }
    for(int i = 1; i < N; i++) {
      //これではR+1以降でsumされないので、だめ。例ではうまいこと2^n-1になっているので通ってしまう.
      for(int j = R; j >= 0; j--) {
	for(int k = 0; k <= R; k++) {
	  if(j + k == (j|k) ) {
	    int index = (j+k > R) ? R+1 : j+k;
	    dp[i+1][index] += dp[i][j];
	  }
	}
      }
    }
				     				 
    int ans = 0;
    //R+1もたすことに注意
    for(int i = 0; i <= R+1; i++) {
      ans += dp[N][i];
    }
    return ans;
  }
//こっちが正しい
  int countSequences(int N, int R) {
    const int MAX = (1<<14); //例えば、R=2^10+2:(100_0000_0010)とかのときは最終的な和が2^11-1:(111_1111_1111)になる可能性があるので、配列をここまでのばす。
    int dp[N+1][MAX]; //0~2^13
    memset(dp, 0, sizeof dp);
    for(int i = 0; i <= R; i++) {
      dp[1][i] = 1;
    }
    for(int i = 1; i < N; i++) {
      //n個足したときの和がdp[i][j]となる
      for(int j = MAX; j >= 0; j--) {
        //さらに数を足す.0~Rの範囲なので必ずこの範囲でループさせる.
	for(int k = 0; k <=R ; k++) {
	  if(j + k == (j|k) && dp[i][j] != 0) {
            //dp[i+1][j]とかで作ってしまうと、j=R+..の時に計算できなくなるorまた新たに条件を加えてけいさんしなければならなくなる.
	    dp[i+1][j+k] = (dp[i][j]+dp[i+1][j+k])%MOD;
	  }
	}
      }
    }
				     				 
    int ans = 0;
    //全体の和は1~Rまで、ではないことに注意.
    for(int i = 0; i < MAX; i++) {
      ans = (ans + dp[N][i])%MOD;
    }
    return ans;
  }
//全体の流れとしては、
R=4
(今までの和j:5) + (新たに足す数k:2)  --条件が真--> (j+k:7)
i = ?? //いままで足した回数
j = 5  新たに足す数:k = [0~R]まで試してみる. k =2でヒットしたところ.
R=4だけど、j=5のときにも上の例のように普通になりたつので、気をつける。