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 
posted @ 2024-02-03 16:52  N2MENT  阅读(76)  评论(0)    收藏  举报