3.28考试总结
吐槽
\(A\color{red}{nson}\)这么神仙一个人,为什么题面写成这种鬼样子啊!!!
题面写成这样就算了,为什么不给个样例解释啊!
果然神仙们写题解都很简略啊
A和C先咕咕咕了,题解是官方题解
A
题面:给你一棵树,你想对这棵树进行点分治。构建点分树后,每个点都有一个点分树上的深度。问所有合法的 点分树上的深度数组数目。\(n\le 5000\)
题解:谁能告诉我深度数组是什么东西啊!!!
考虑\(dp\)。
设\(f[i][j]\)表示在以\(i\)为根的子树中,\(i\)的深度为\(j\)的方案数,当我们合并两棵子树\(x,y\)时,由于子树间
的顺序关系可改变,我们发现:对于一对数\(i,j\),对任意\(d\le i+j\)有\(f[x][i]*f[y][j]*\binom{a+b}{a}\) 的贡献。其中\(a+b=d-1,a\le i,b\le j\)。前缀和优化一下即可。
B
题面:有一张\(n\)个点\(m\)条边的无向图,有\(k\)个加油站,经过加油站可以把油加满。有\(s\)个终点,问从每一个加油站出发,油箱容量至少要多大才能到达某个终点.
\(n,m,k,s\le 100000\)
题解:
官方题解我标红了(可能稍微改了一下)
\(\color{red}{将加油站间的距离看作边,我们需要求出所有加油站的最小生成树}\)
大概是包含所有加油站的最小生成树?
\(\color{red}{直接连边是O(n^2)的}\)
我也不知道为什么
\(\color{red}{设near[x]表示离x最近的加油站。对于一条边(u,v,w),若near[u]不等于near[v],则连(near[u],near[v],dis[u]+dis[v]+w)}\)
\(\color{red}{剩下的问题有很多解决方案}\)
\(\color{red}{一种在线算法如下,构建Kruskal重构树。}\)
\(\color{red}{设c[x]表示子树中所有点距终点距离的最小值,显然随着深度减小,点权(即最小生成树上的边权)变大,c[x]变小。}\)
\(\color{red}{答案是两者中的较大值(的最小值~(这是我加上去的))。倍增即可。}\)
具体的步骤:
首先连边,把所有加油站的\(dis\)初始化为\(0\),全部丢到优先队列(或者队列什么的随便),跑最短路
初始化\(near[k[i]]=i\),表示离\(k[i]\)号点最近的是\(i\)号加油站
如果这个点到某个加油站的最短路是由另一个点转移过来的,则\(near[v]=near[u]\)
然后建图,看上面红字(重构树用的)
然后对把每一个终点丢进去跑最短路,初始化同上
然后设一个\(val[i]=dis[k[i]]\)表示是从第\(i\)个加油站到任意一个终点的最短路
然后建\(Kruskal\)重构树
建树具体看代码,都是常规操作,注意顺便把\(c[x]\)算出来
然后查询,也是看代码
说实话是因为我也不知道为什么要这样,尤其是往上跳的时候为什么不跳
如果直接能跳就往上跳会获得40pts的好成绩
留坑待填
#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
x = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 200005
struct Edge
{
int fr, to, val;
bool operator < (const Edge& p) const { return val < p.val; }
}eg[maxn << 1], Eg[maxn << 1];
int head[maxn], edgenum, Enum;
inline void add(int fr, int to, int val)
{
eg[++edgenum] = { head[fr],to,val };
head[fr] = edgenum;
}
int dis[maxn], vis[maxn], near[maxn];
#define pa pair<int,int>
#define mp make_pair
priority_queue<pa, vector<pa>, greater<pa> >q;
void dijskra(int n, int* x)
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; ++i) near[x[i]] = i, q.push(mp(dis[x[i]] = 0, x[i]));
while (!q.empty())
{
int tp = q.top().second; q.pop();
if (vis[tp]) continue; vis[tp] = 1;
for (int i = head[tp]; i; i = eg[i].fr)
if (dis[eg[i].to] > dis[tp] + eg[i].val)
near[eg[i].to] = near[tp], q.push(mp(dis[eg[i].to] = dis[tp] + eg[i].val, eg[i].to));
}
}
int fa[maxn], val[maxn], f[maxn][17], c[maxn];
int findf(int x) { return x == fa[x] ? x : fa[x] = findf(fa[x]); }
void Kruskal(int& n)
{
sort(Eg + 1, Eg + Enum + 1);
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1, tx, ty; i <= Enum; ++i)
{
tx = findf(Eg[i].fr), ty = findf(Eg[i].to);
if (tx != ty)
{
c[++n] = min(val[tx], val[ty]); val[n] = Eg[i].val;
f[tx][0] = f[ty][0] = fa[tx] = fa[ty] = fa[n] = n;
}
}
for (int i = 1; i < 17; i++)
for (int j = 1; j <= n; j++)
f[j][i] = f[f[j][i - 1]][i - 1];
}
int K[maxn], S[maxn], fr[maxn], to[maxn], eval[maxn];
inline int query(int x)
{
int ans = val[x];
for (int i = 16; i >= 0; --i)
if (f[x][i])
{
if (c[f[x][i]] < val[f[x][i]]) ans = min(ans, val[f[x][i]]);
else ans = min(ans, c[f[x][i]]), x = f[x][i];
}
return ans;
}
int main()
{
//freopen("B.in", "r", stdin), freopen("B.out", "w", stdout);
int n, m, k, s;
read(n), read(m), read(k), read(s);
for (int i = 1; i <= k; ++i) read(K[i]);
for (int i = 1; i <= s; ++i) read(S[i]);
for (int i = 1; i <= m; ++i)
read(fr[i]), read(to[i]), read(eval[i]), add(fr[i], to[i], eval[i]), add(to[i], fr[i], eval[i]);
dijskra(k, K);
for (int i = 1; i <= m; ++i)
if (near[fr[i]] != near[to[i]])
Eg[++Enum] = { near[fr[i]],near[to[i]],dis[fr[i]] + dis[to[i]] + eval[i] };
dijskra(s, S);
for (int i = 1; i <= k; ++i) c[i] = dis[K[i]];
Kruskal(k);
for (int i = 1; i + i <= k + 1; ++i) printf("%d\n", query(i));
return 0;
}
然后还有\(5\color{red}{20Enterprise}\)的方法,个人认为好理解一些
首先两遍最短路,预处理出到加油站和到终点的\(near\)和\(dis\),然后建一个新图
新图这样建:
原始边是\((x,y,w)\)
到起点为\(0\),终点为\(1\)
他写的是\(near[x][0]=y\)表示离\(x\)点最近的加油站为点\(y\)
大概原理就是缩点成只有加油站和终点然后直接跑
{
if(near[x][0]!=near[y][0]) add(near[x][0],near[y][0].dis[x][0]+dis[y][0]+eg[i].val);
add(near[x][0],near[y][1],dis[x][0]+dis[y][1]+eg[i].val);
add(near[x][1],near[y][0],dis[x][1]+dis[y][0]+eg[i].val);
//然后再把上面两(三)条边的反边建了
//其实不判应该也没什么关系
}
然后再一个最短路,把终点全部丢进去,然后直接用\(spfa\)转移,路径上取\(max\)
//吐槽一下这个毒瘤码风
//ed是终点,e[i].quan是边权,其他的应该猜的出来
for(int i=1; i<=s; ++i)
q.push(ed[i]),juli[ed[i]]=0,in[ed[i]]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
in[now]=0;
for(int i=head[now]; i; i=e[i].next)
{
int to=e[i].to;
if(juli[to]>max(juli[now],e[i].quan))
{
juli[to]=max(juli[now],e[i].quan);
if(!in[to])
q.push(to),in[to]=1;
}
}
}
C
题面:有若干个不同的符文槽,每个符文槽上可以无顺序镶嵌若干符文石。威力值定义为所有符文槽中的符文石的权值按位或的值。你希望威力值恰好为 \(t\)。现在,你有\(n\)个符文石,每个符文石可以镶嵌在任一个符文槽上或直接丢弃。假设符文槽数目可以为\(0\)到\(m\)中的任意值,求符合条件的方案数。
我也不知道是每一个槽加起来再把和或起来,还是直接或起来啊!
虽然第一种可能性应该大一些
\(n\le 5000,m\le 10^9,k=2^g(0\le g\le 20)\)
题解:
答案显然是一个关于\(m\)的多项式。
这东西其实是可以直接求出每一项系数的。先求出\(A[i]\)表示或起来至少
为\(x\)的方案数,答案是这东西高维差分得到的。因为最终只需要求\(t\),可以暴力求差分前后每项的贡献(一个常数),然后直接插这个答案即可。这样就只需要插一次,常数比较优秀,否则应该会被卡掉一些分。
There is a negligible beginning in all great action and thought.

浙公网安备 33010602011771号