\(\cal T_1\) 回路
\(\mathbb{Description}\)
给定一张 \(n\) 个点的无向图,定义经过一个点 \(u\) 的非平凡回路为一条从 \(u\) 出发回到 \(u\) 的路径,并且至少包含一个简单环。你需要对于每个点,求出经过它的最小非平凡回路的长度 \(L\). 这个问题很困难,因此你只需要求出 \(\lceil L\rceil\) 即可。特别的,如果不存在经过它的非平凡回路,输出 \(-1\).
\(n\le 5000\).
\(\mathbb{Solution}\)
一些闲话:我真的是哔了狗了。
这题真的完完全全想偏了:一来就往 \(\rm dfs\) 树上靠,大体想法是找出每个点所在的最小环,然后再跑一个 \(\rm bfs\) 解决问题。对于最小环,就通过返祖边修改此边覆盖的所有点的最小环(是的,我它吗还写了个树剖套 \(\rm zkw\) 的两 \(\log\) 伞兵算法)。对于多源点 \(1\) 边权最短路,应该是 \(\mathcal O(m)\) 的,因为每个点至多入队两次(初始作为源点入队,被更新入队,是的,所以我的队列用的是优先队列,所以还要带 \(\log\))。然后就写写写,最后发现 "环" 不止由一条返祖边与树边组成,我就傻了。
实际上这题的思路非常简单:枚举每个点作为起点,直接开始 \(\rm bfs\),假设当前点为 \(u\),如果发现 \(v\) 之间被遍历过且 \(u\) 不是从 \(v\) 拓展而来,此时的 \(d_u\) 就是答案。复杂度 \(\mathcal O(n^2)\).
\(\mathbb{Code}\)
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10,x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <queue>
# include <vector>
# include <cstring>
using namespace std;
const int maxn = 5005;
queue <int> q;
char s[maxn];
vector <int> e[maxn];
int n,dis[maxn];
int main() {
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
n=read(9);
for(int i=2;i<=n;++i) {
scanf("%s",s+1);
for(int j=1;j<=i;++j) if(s[j]=='1')
e[i].emplace_back(j), e[j].emplace_back(i);
}
int ans;
for(int i=1;i<=n;++i) {
memset(dis,0,sizeof(int)*(n+2));
while(!q.empty()) q.pop();
q.push(i), dis[i]=1, ans=-1;
while(!q.empty()) {
int u=q.front(); q.pop();
for(const auto& v:e[u])
if(dis[v] && dis[v]+1==dis[u]);
else if(dis[v]) { ans=dis[u]; break; }
else dis[v]=dis[u]+1, q.push(v);
if(~ans) break;
}
print(ans,'\n');
}
return 0;
}
\(\cal T_2\)
\(\mathbb{Description}\)
\(\mathbb{Solution}\)
\(\mathbb{Code}\)
\(\cal T_3\) 追逐
\(\mathbb{Description}\)
\(\rm Alice\) 和 \(\rm Bob\) 在一棵 \(n\) 个点的树上追逐,树上每条边的长度都是 \(1\)。\(\rm Alice\) 先选定自己的起始位置,然后 \(\rm Bob\) 选定自己的起始位置,随后 \(\rm Alice\) 逃跑,\(\rm Bob\) 追 \(\rm Alice\)。\(\rm Alice\) 每步可以走至多 \(d_a\) 的长度,\(\rm Bob\) 每步可以走至多 \(d_b\) 的长度。\(\rm Bob\) 追上 \(\rm Alice\) 当且仅当某次 \(\rm Bob\) 操作之后,\(\rm Bob\) 和 \(\rm Alice\) 处于同一个节点上(选位置的那一个轮次不算)。
但是由于一些不可控力因素,第 \(i\) 条边有 \(p_i\) 的概率会断开。你想要求出,\(\rm Bob\) 能够成功追上 \(\rm Alice\) 的概率是多少。由于大家都不喜欢小数,你只需要输出答案对 \(998244353\) 取模的结果即可。注意 \(\rm Alice\) 和 \(\rm Bob\) 选点的时候是所有边已经按照自己的概率断开的时候。
\(n\le 2\cdot 10^6,1\le d_a,d_b\le 10^9\).
\(\mathbb{Solution}\)
首先这题有一个先导结论:\(\rm Bob\) 能追上 \(\rm Alice\) 当且仅当 \(d_a\le 2d_b\) 或树上直径 \(\le 2d_b\).
对于树上直径 \(\le 2d_b\) 的情况,\(\rm Bob\) 只需要选择某条直径的中点即可,此时无论 \(\rm Alice\) 跳到哪个点,\(\rm Bob\) 都能直接跳过去,因为树上每个点到他的距离均 \(\le d_b\);反之,就意味着存在直径 \(>2d_b\),如果还满足 \(d_a\le 2d_b\),\(\rm Bob\) 只需要最初选点时距离 \(\rm Alice\) 为 \(d_b\)(这是一定可行的),然后一直维持 \(\ge d_b\) 的距离,总能将 \(\rm Alice\) 逼入绝境,因为 \(\rm Alice\) 与 \(\rm Bob\) 相对而跳一定是个死。而如果 \(d_a>2d_b\),\(\rm Alice\) 就可以把 \(\rm Bob\) 钓来钓去(初始时选在直径上,之后就一直在直径上跳),当二者距离 \(\le d_b\) 时,对着他跳走即可。草怎么这么像渣女和病娇的故事好带感斯哈斯哈。
\(\text{Subtask 1}\):\(n\le 2000\)
有了上面的结论,我们只需要算出整棵树的直径 \(\le 2d_b\) 的概率即可,令 \(dp_{u,i}\) 为节点 \(u\) 的子树最深深度为 \(i\) 且直径不超过 \(2d_b\) 的概率,转移有
是 \(\min\{i-1,2d_b-i\}\) 而不是 \(\min\{i,2d_b-i\}\) 的原因是避免算重。前缀和优化一下就是 \(\mathcal O(n^2)\) 的了。
\(\text{Subtask 2}\):树为一条链
利用初始时只有 \(f_{u,0}=1\) 的性质,我们可以简化上面的 \(\mathtt{dp}\) 式:
这实际上就是区间乘 + 单点插入,用线段树可以简单维护。
\(\mathbb{Code}\)
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x : x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <vector>
# include <iostream>
using namespace std;
typedef pair <int,int> par;
const int ONE = 1e7;
const int maxn = 2005;
const int mod = 998244353;
inline int inc(int x,int y) { return x+y>=mod?x+y-mod:x+y; }
inline int dec(int x,int y) { return x-y<0?x-y+mod:x-y; }
inline int inv(int x,int y=mod-2) {
int r=1;
for(; y; y>>=1, x=1ll*x*x%mod)
if(y&1) r=1ll*r*x%mod;
return r;
} const int Inv = inv(ONE);
int n,d_a,d_b,dp[maxn][maxn],lim;
vector <par> e[maxn];
void dfs(int u,int fa) {
int tmp[maxn]; dp[u][0]=1;
for(int i=0;i<=lim;++i) tmp[i]=1;
for(const auto& v:e[u]) if(v.first^fa) {
dfs(v.first,u); int to=v.first, p=v.second, coe;
for(int i=0;i<=lim;++i) {
dp[u][i] = inc(
1ll*p*dp[u][i]%mod*dp[to][lim]%mod,
1ll*dec(1,p)*dp[to][min(i-1,lim-i-1)]%mod*dp[u][i]%mod
);
if(i) {
if(i==1) coe = dp[to][0];
else coe = dec(dp[to][i-1],dp[to][i-2]);
dp[u][i] = inc(
dp[u][i],
1ll*coe*tmp[min(i-1,lim-i)]%mod*dec(1,p)%mod
);
}
}
tmp[0]=dp[u][0];
for(int i=1;i<=lim;++i) tmp[i] = inc(tmp[i-1],dp[u][i]);
}
for(int i=1;i<=lim;++i) dp[u][i] = inc(dp[u][i-1],dp[u][i]);
}
int main() {
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n=read(9), d_a=read(9), d_b=read(9); lim = (d_b<<1);
for(int i=1;i<n;++i) {
int u=read(9), v=read(9), w=1ll*read(9)*Inv%mod;
e[u].emplace_back(make_pair(v,w));
e[v].emplace_back(make_pair(u,w));
}
if(d_a<=lim) return puts("1"), (0-0);
dfs(1,0); print(dp[1][lim],'\n');
return 0;
}