TopCoder - 14379 RPSRobots
WC 讲题的题,肥肠好题目。
vjudge 的翻译 (应该能点)。
看到这个题目感觉十分无助,首先需要转化为更具体的描述。石头剪刀布的模型可以转化为在模意义下做减法,得到的值对应胜负平。但是这似乎没有什么帮助。
然而在长度为 \(3\) 的环上旋转似乎也类似于单位根,先从单位根入手。
我们把 RPS 对应到 \(\omega_3^0, \omega_3^1, \omega_3^2\)。那么两个决策组成的向量点乘(实际上是点除?)的结果对应每一轮的输赢情况。
考虑把 bot A 的决策写成多项式 \(A(x) = \sum_{i = 0}^{k - 1} \omega_3^{a_i}x^i\),再找一个 bot B 的决策 \(B(x) = \sum_{i = 0}^{k - 1} \omega_3^{-b_{k - 1 - i}}x^i\)(首尾翻转并把系数取倒数)。那么 \(A\) 和 \(B\) 循环卷积后得到多项式 \(C(x)\),\(C(x)\) 的每一项都对应一种初始状态下的输赢情况。
回想一下我们平时是如何做 FFT 的,首先把多项式转化为点值表示,然后逐位相乘,最后再还原回多项式,于是完成了一次循环卷积(然而似乎有好多人不知道 FFT 是在做循环卷积,实际上由于单位根下 \(\omega_n^{i+j}=\omega_n^{(i+j)\bmod n}\),所以是循环卷积)。
假如每一项的系数都相等,那么只有 \(x=\omega_k^0\) 的时候点值可能非 \(0\),\(x = \omega_k^i(i\neq0)\) 时一定为 \(0\)。所以 \(C(x)\) 在点值表示下除了第一项都为 \(0\),那么对应 \(A(x)\) 和 \(B(x)\) 在点值表示下非 \(0\) 的位置除了第一项之外不相交。
这里看起来离状压 dp 已经很近了,还剩最后一个问题,\(B(x)\) 需要做一个奇怪的变换,这看起来不好处理,然而注意到由于我们不需要考虑值具体是多少,而只用考虑是否为零,所以实际上不需要处理这种奇怪的变换。
感觉写得有点简略,可以写写柿子自己理解,另,本人很菜所以可能存在笔误,欢迎纠正。
code:
#include<bits/stdc++.h>
// #define N2MENT
#ifdef N2MENT
#define LOG(...) fprintf(stderr, __VA_ARGS__), void()
#else
#define LOG(...) void()
#endif
using namespace std;
using ll = long long;
constexpr inline int bit(int x) {return 1 << x;}
constexpr inline int dig(int x, int d) {return (x >> d) & 1;}
const double pi = acos(-1);
const double eps = 1e-6;
const int maxn = 1e5 + 10;
const int maxk = 19;
const int mod = 1e9 + 7;
complex<double> w3[3], wk[maxk];
int sta[bit(maxk)];
int f[bit(maxk)];
int RPS(char c) {
if(c == 'R') return 0;
if(c == 'P') return 1;
return 2;
}
ll Pow(ll a, ll b = mod - 2, ll mod = mod) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod, b >>= 1;
}
return res;
}
class RPSRobots {
public:
int countGoodSet(int n, vector<string> r, int seed, int pR, int pP, int pS) {
int k = r[0].size();
{//input
long now = seed;
long a = 22222223;
long b = 12345678;
long mod = 1000000000 + 7;
int cur = r.size();
for(int i = cur; i < n; i++)
{
string s = "";
for(int j = 0; j < r[0].size(); j++)
{
if((now % (pR + pP + pS)) < pR)
s += "R";
else if((now % (pR + pP + pS)) < pR + pP)
s += "P";
else
s += "S";
now = (now * a + b) % mod;
}
r.emplace_back(s);
}
}
for(int i = 0; i < 3; i++) w3[i] = complex<double>(cos(2.0 * pi * i / 3), sin(2.0 * pi * i / 3));
for(int i = 0; i < k; i++) wk[i] = complex<double>(cos(2.0 * pi * i / k), sin(2.0 * pi * i / k));
for(string& s : r) {
int S = 0;
for(int i = 1; i < k; i++) {
complex<double> sum(0.0, 0.0);
complex<double> w(1.0, 0.0);
for(int j = 0; j < k; j++, w = w * wk[i]) {
sum += w3[RPS(s[j])] * w;
}
// LOG("%lf+%lfi\n", sum.real(), sum.imag());
if(abs(sum.real()) > eps || abs(sum.imag()) > eps) S |= bit(i - 1);
}
sta[S]++;
}
f[0] = 1;
int ans = 1;
for(int S = 1; S < bit(k - 1); S++) {
int u = S & (-S);
for(int T = S ^ u;; T = (T - 1) & (S ^ u)) {
f[S] = (f[S] + (ll) f[S ^ T ^ u] * sta[T ^ u] % mod) % mod;
if(T == 0) break;
}
ans = (ans + f[S]) % mod;
}
ans = ((ll) ans * Pow(2, sta[0]) % mod - 1 + mod) % mod;
return ans;
}
};
#ifdef N2MENT
//DEBUG
RPSRobots solve;
vector<string> input;
string s;
int main() {
int n, seed, pR, pP, pS;
cin >> n >> seed >> pR >> pP >> pS;
while(cin >> s) {
if(s == "EOF") break;
input.emplace_back(s);
}
cout << solve.countGoodSet(n, input, seed, pR, pP, pS);
}
#endif