123789456ye

已AFO

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\),可以暴力求差分前后每项的贡献(一个常数),然后直接插这个答案即可。这样就只需要插一次,常数比较优秀,否则应该会被卡掉一些分。

posted @ 2020-03-28 21:37  123789456ye  阅读(6)  评论(0)    收藏  举报