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
题意
思路
很容易知道树形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;
}
}