SRM708 500の上位陣のコードを見て何でこれで解けるんだろうとしばらく考えてたら分かった気がする。
— 暇人の名は (@kojingharang) 2017年2月11日
特に後半がナゾだったが回文中で固定文字に対応する文字は重複なくN通りあるから足せば良いのかと気付いたところなう。
class PalindromicSubseq { public: int solve(string s) { ll N = s.size(); // dp1[i][j] ... [i, j] 内での回文の個数 vector<vector<modll>> dp1(N, vector<modll>(N)); // dp2[i][j] ... [0, i), (j, N) 内での回文の個数 vector<vector<modll>> dp2(N, vector<modll>(N)); RANGE(L, 0, N+1) REP(i, N-L+1) { int j = i+L-1; if(L==0) { if(0<=j && i<N) dp1[i][j] = 1; // empty } else { modll v = 0; // s[i] を採用しない場合 (s[j]は採用するしないどちらも含む) if(i+1<N) v += dp1[i+1][j]; // s[j] を採用しない場合 (s[i]は採用するしないどちらも含む) if(0<=j-1) v += dp1[i][j-1]; // s[i], s[j] 両方採用しない場合が 2 回数えられている分を減らす if(0<=j-1 && i+1<N) v -= dp1[i+1][j-1]; if(s[i]==s[j]) { // s[i], s[j] を採用できる。採用するとその内側の組み合わせ総数だけ増える if(0<=j-1 && i+1<N) v += dp1[i+1][j-1]; } dp1[i][j] = v; } } for(int L=N;L>=1;L--) REP(i, N-L+1) { int j = i+L-1; if(L==N) { dp2[i][j] = 1; // empty } else { modll v = 0; // 同様 if(0<=i-1) v += dp2[i-1][j]; if(j+1<N) v += dp2[i][j+1]; if(0<=i-1 && j+1<N) { v -= dp2[i-1][j+1]; if(s[i-1]==s[j+1]) { // s[i-1], s[j+1] を採用できる。採用するとその外側の組み合わせ総数だけ増える v += dp2[i-1][j+1]; } } dp2[i][j] = v; } } ll ans = 0; REP(i, N) { modll X = 0; REP(j, N) { if(s[i]==s[j]) { ll I=i, J=j; if(I>J) swap(I, J); // ここでは I, J がどちらも採用され I, J が回文にて対応関係にある。 // 内側は [I+1, J-1] 内の回文の個数 // 外側は [0, I), (J, N) 内の回文の個数 // これらの積が "I, J がどちらも採用され回文にて I, J が対応関係にある" 場合の組み合わせ総数。 X += (I+1<=J-1 ? dp1[I+1][J-1] : modll(1)) * dp2[I][J]; } } ans ^= modll(i+1LL)*X; } return ans; } };