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\neq t\)代表j如果是某个t串结尾就不转移了)

\[P_i=\sum\limits_{j\neq t且j\rightarrow i}{\frac{1}{6}P_j}(i \neq 0) \]

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

\[P_0=1+\sum\limits_{j\neq t且j\rightarrow 0}{\frac{1}{6}P_j} \]

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

#include <bits/stdc++.h>

#define endl '\n'
#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] << " \n"[!cnt];
        }
    } 
}

I - The Elder

题意

context

思路

很容易知道树形dp

\[dp[i]=\min\limits_{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 '\n'
#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;
    }
}
posted @ 2021-05-08 17:40  limil  阅读(113)  评论(0编辑  收藏  举报