zoukankan      html  css  js  c++  java
  • 2016ACM/ICPC亚洲区沈阳站-重现赛

    source

    H - Guessing the Dice Roll

    题意

    n个人分别猜一个由数字1~6组成长度为L的字符串t。主持人也生成一个由数字1~6组成的字符串s,每次等概率从1~6中取一个数字插入到字符串末尾。当某人猜的字符串成为了s的后缀,就胜利,游戏结束。问每个人获胜的概率。

    思路

    显然应该将n个人的字符串建成AC自动机。定义(P_i)为s串转移到AC自动机上i点的任意次的概率总和(注意是任意次,而不是第一次)。那么可以得到状态转移方程为((j eq t)代表j如果是某个t串结尾就不转移了)

    [P_i=sumlimits_{j eq t且j ightarrow i}{frac{1}{6}P_j}(i eq 0) ]

    由于第一次必经过状态0,概率为1,故有

    [P_0=1+sumlimits_{j eq t且j ightarrow 0}{frac{1}{6}P_j} ]

    这样就可以用高斯消元求出每个(P_i)了。由于转移到t串结尾后不再转移,故t串结尾对应状态的概率就是答案。

    #include <bits/stdc++.h>
    
    #define endl '
    '
    #define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
    #define INF 0x3f3f3f3f
    const long double eps = 1e-10;
    
    
    //AC自动机模板,查询文本中模式串的出现次数。
    const int N = 200;
    int tr[N][10]; //N等于所有模式串长度总和
    int e[N];      //结点信息
    int fail[N];   //fail指针
    int si = 0;
    
    namespace AC {
        void init() {
            memset(tr[0], 0,sizeof tr[0]); //清空trie树第一层,代表这颗树需要重新构建。
            si = 0;
        }
    
        void insert(const char s[]) { //插入同时初始化trie树,提高初始化效率
            int cur = 0;
            fail[cur] = 0;
            e[cur] = 0;
            for(int i = 0; s[i]; i++) {
                if(tr[cur][s[i] - '0']) cur = tr[cur][s[i] - '0'];
                else {
                    tr[cur][s[i] - '0'] = ++si;
                    cur = si;
                    memset(tr[si], 0, sizeof tr[si]);
                    fail[cur] = 0;
                    e[cur] = 0;
                }
            }
            e[cur]++; //更新结点信息(这里是出现次数)
        }
    
        void build() {
            queue<int> q;
            for(int i = 0; i < 10; i++) 
                if(tr[0][i]) q.push(tr[0][i]);
    
            //tr[fail[cur]]代表cur的一个后缀
            while(!q.empty()) {
                int cur = q.front();
                q.pop();
                for(int i = 1; i <= 6; i++) {
                    if(tr[cur][i]) {
                        fail[tr[cur][i]] = tr[fail[cur]][i]; //不用判断tr[fail[cur]][i]是否存在,因为不存在的tr[fail[cur]][i]已经建立好了转跳。
                        q.push(tr[cur][i]);
                    } else {
                        tr[cur][i] = tr[fail[cur]][i]; //扩展trie树,面对不存在的状态,直接转跳到另一个后缀。这样直接无脑在trie上走就可以实现自动失配转跳。
                        //有点类似路径压缩
                    }
                }
            } 
        }
    
        int query(char s[]) { //返回有多少模式串在s中出现过
            int cnt = 0;
            int cur = 0;
            for(int i = 0; s[i]; i++) {
                cur = tr[cur][s[i] - '0'];
                for(int j = cur; j && e[j] != -1; j = fail[j]) { //查询每个后缀是否匹配到了模式串
                    cnt += e[j];
                    e[j] = -1; //找到了就可以删掉防止重复查询
                }
            }
            return cnt;
        }
    }
    
    string s;
    
    long double A[N][N];
    
    int gauss()
    {
        int c, r;//c 是枚举列 r 是枚举行
        for( c = 0, r = 0; c < si+1; ++ c)//枚举列
        {
            int t = r;
            for(int i = r; i < si+1; ++ i)//枚举行
              if(fabs(A[i][c]) - fabs(A[t][c]) > eps)
                 t = i;
                 
              if(fabs(A[t][c]) < eps) continue;
              
              for(int i = c; i <= si+1; ++ i)//t 和 r 行每一列交换
              swap(A[t][i],A[r][i]);
             
              for(int i = si+1; i >= c; -- i) A[r][i] /= A[r][c];
              //将该行首项元素变为 1
              for(int i = r + 1; i < si+1; ++ i)//枚举行
                if(fabs(A[i][c]) > eps)//如果该行的首不为0
                  for(int j = si+1; j >= c; -- j)
                    A[i][j]  -= A[r][j] * A[i][c];
            r ++;
        }
        for(int i = si+1; i >= 0; -- i)
          for(int j = i + 1; j < si+1; ++ j)
            A[i][si+1] -= A[i][j] * A[j][si+1];
        return 0;
    }
    
    int main() {
        int t;
        cin >> t;
        while(t--) {
            int n, l;
            AC::init();
            
            cin >> n >> l;
            for(int i = 0; i < n; i++) {
                s.clear();
                for(int j = 0; j < l; j++) {
                    int d;
                    cin >> d;
                    s.push_back(d + '0');
                }
                AC::insert(s.c_str());
            }
            AC::build();
            for(int i = 0; i <= si; i++) {
                for(int j = 0; j <= si + 1; j++) {
                    A[i][j] = i == j ? -1 : 0;
                }
            }
            A[0][si + 1] = -1;
            for(int i = 0; i <= si; i++) {
                if(e[i]) continue;
                for(int j = 1; j <= 6; j++) {
                   A[tr[i][j]][i] += 1 / 6.0;
                }
            }
            gauss();
            int cnt = 0;
            for(int i = 1; i <= si; ++ i) {
                if(e[i]) cnt++; 
            }
            for(int i = 1; i <= si; ++ i) {
                if(!e[i]) continue;
                cnt--;
                cout << seteps(6) << A[i][si + 1] << " 
    "[!cnt];
            }
        } 
    }
    

    I - The Elder

    题意

    context

    思路

    很容易知道树形dp

    [dp[i]=minlimits_{j是i的祖先}((d_i-d_j)^2+dp[j]+P) ]

    (d_i)代表点(i)到根结点的距离。显然(n^2)的复杂度不能过,需要斜率优化。将上面式化成

    [dp[j]+d_j^2=2d_id_j+(dp[i]-d_i^2-P) ]

    将上式看成斜率为(2d_i)的直线,过点((d_j, dp[j]+d_j^2))。那么截距越小,dp[i]越小。接下来就是斜率dp的常规操作了。
    注意队列在回溯时还原的写法,由于队列内的值没有被抹去,所以可以改变下标而不必开辟新空间将弹出的值存起来。

    #include <bits/stdc++.h>
    
    #define endl '
    '
    #define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
    typedef long long ll;
    
    using namespace std;
    #define INF 0x3f3f3f3f
    
    const int N = 1e5 + 10;
    const int M = 1e6 + 10;
    const double eps = 1e-5;
    
    typedef pair<int, int> PII;
    
    ll dp[N];
    vector<PII> np[N];
    ll d[N];
    int q[M];
    int h, t;
    int n, P;
    void dfs(int p, int fa) {
        for(auto nt : np[p]) {
            if(nt.first == fa) continue;
            d[nt.first] = d[p] + nt.second;
            dfs(nt.first, p);
        }
    }
    
    ll sqr(ll x) {
        return x * x;
    }
    
    ll dx(int a, int b) {
        return d[a] - d[b];
    }
    
    ll dy(int a, int b) {
        return dp[a] - dp[b] + sqr(d[a]) - sqr(d[b]);
    }
    
    void solve(int p, int fa) {
        for(auto nt : np[p]) {
            if(nt.first == fa) continue;
            int tt = t, hh = h;
            int pre;
            while(t - h >= 2 && dy(q[h + 1], q[h]) < 2 * d[nt.first] * dx(q[h + 1], q[h])) {
                h++;
            }
            int tar = q[h];
            dp[nt.first] = sqr(d[nt.first] - d[tar]) + dp[tar] + P;
            while(t - h >= 2 && dy(nt.first, q[t - 1]) * dx(q[t - 1], q[t - 2]) < dy(q[t - 1], q[t - 2]) * dx(nt.first, q[t - 1])) {
                t--;
            }
            pre = q[t];
            q[t++] = nt.first;
            solve(nt.first, p);
            q[--t] = pre;
            t = tt;
            h = hh;
        }
    }
    
    int main() {
        IOS;
        int T;
        cin >> T;
        while(T--) {
            cin >> n >> P;
            h = t = 0;
            for(int i = 1; i <= n; i++) {
                dp[i] = d[i] = 0;
                np[i].clear();
            }
            dp[1] = -P;
            for(int i = 1; i < n; i++) {
                int u, v, w;
                cin >> u >> v >> w;
                np[u].push_back({v, w});
                np[v].push_back({u, w});
            }
            dfs(1, 0);
            q[t++] = 1;
            solve(1, 0);
            ll ans = 0;
            for(int i = 1; i <= n; i++) ans = max(ans, dp[i]);
            cout << ans << endl;
        }
    }
    
  • 相关阅读:
    2019-1-25-win10-uwp-禁用-ScrollViewer-交互
    2019-1-25-win10-uwp-禁用-ScrollViewer-交互
    2018-8-13-WPF-使用-Edge-浏览器
    2018-8-13-WPF-使用-Edge-浏览器
    2018-10-23-使用-Pandoc-把-Markdown-转-Docx
    2018-10-23-使用-Pandoc-把-Markdown-转-Docx
    Java实现 LeetCode 486 预测赢家
    PDO::getAttribute
    PDO::beginTransaction
    PDO::exec
  • 原文地址:https://www.cnblogs.com/limil/p/14745928.html
Copyright © 2011-2022 走看看