测试总结:温州中学 2011年11月月赛 Day1

T1 食物中毒

测试总结:温州中学 2011年11月月赛 Day1_第1张图片
参考代码

const int MAXN = 60;

int n,m,mi,ki;
bool state;
long long require,selected;
long long medicine[MAXN];

void readdata()
{
	for (int i = 1;i <= m;++i)
	{
		scanf("%d",&mi);
		require |= (long long)1 << (mi - 1);
	}
	for (int i = 1;i <= n;++i)
	{
		scanf("%d",&ki);
		for (int j = 1;j <= ki;++j)
		{
			scanf("%d",&mi);
			medicine[i] ^= (long long)1 << (mi - 1);
		}
	}
}

void init()
{
	state = false;
	require = 0;
	memset(medicine,0,sizeof(medicine));
}

void dfs(int itemID)
{
	if ((selected | require) == selected)
	{
		state = true;
		return;
	}
	if (itemID == n + 1) return;
	long long tmp = selected;
	selected ^= medicine[itemID];
	dfs(itemID + 1);
	selected = tmp;
	dfs(itemID + 1);
}

int main()
{
	while (scanf("%d%d",&n,&m) != EOF)
	{
		init();
		readdata();
		dfs(1);
		if (state) puts("Possible");
		else puts("Impossible");
	}
	return 0;
}

分析

  • 选取的所有药物中,必须出现所有需要的化学物质,也可以出现不需要的化学物质。

看到题目中的描述:

“两个相同的化学物质在一起,它们的药性会同时消失变为一种无毒物质。”

可以想到开一个辅助数组,记录每种药物中每种化学物质的出现次数,再判断这种化学物质是否存在。这是处理单个药物的方法。后面在选取药物的时候,也可以运用同样的方法。

由于每种药物只有一个,即每种药物的决策只有选与不选两种,可以直接枚举所有方案数,共 2 20 = 1048576 2^{20} = 1048576 220=1048576种方案。

当然,由于每种药物的决策只有选与不选两种,我们还可以利用位运算来加快计算速度。

  • require |= (long long)1 << (mi - 1);(置1,代表含有这种化学物质)
  • medicine[i] ^= (long long)1 << (mi - 1);(单身狗操作)
  • selected ^= medicine[itemID];(选用当前这种药物)
  • if ((selected | require) == selected)(判断当前选出的所有药物是否已经凑齐所需的化学物质)

是不是很方便?

有一些细节需要注意

long long tmp = selected;
selected ^= medicine[itemID];
dfs(itemID + 1);
selected = tmp;
dfs(itemID + 1);
/*
为什么不这样写:
dfs(itemID + 1);
selected ^= medicine[itemID];
dfs(itemID + 1);
*/

为什么先选用这种药物,然后再不选用呢?

考虑到selected是一个全局变量,如果最后选用这种药物,那么在返回上一层递归时,这种药物依然是被选中的状态。而最后不选用的写法,恰好起到了一个“清零”的作用。


T2 消息传递

测试总结:温州中学 2011年11月月赛 Day1_第2张图片
参考代码

const int MAXN = 100005;

struct edge
{
	int to,nxt;
}info[MAXN << 1];

int n,m,e,ui,vi,top,Index,cid;
int head[MAXN],dfn[MAXN],low[MAXN],inStack[MAXN],Stack[MAXN],color[MAXN],cnt[MAXN];

template<typename T> void qread(T &sum)
{
	sum = 0;
	register char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9')
	{
		sum = (sum << 1) + (sum << 3) + ch - '0';
		ch = getchar();
	}
}

template<typename T> void qwrite(const T x)
{
	if (x < 0) {putchar('-'); qwrite(-x);}
	else
	{
		if (x >= 10) qwrite(x / 10);
		putchar(x % 10 + '0');
	}
}

inline void addedge(int from,int to)
{
	info[++e].to = to;
	info[e].nxt = head[from];
	head[from] = e;
}

void tarjan(int u)
{
	Index++;
	dfn[u] = low[u] = Index;
	Stack[++top] = u;
	inStack[u] = true;
	for (int i = head[u];i;i = info[i].nxt)
	{
		int v = info[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u],low[v]);
		}
		else if (inStack[v]) low[u] = min(low[u],dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		color[u] = ++cid;
		cnt[cid]++;
		inStack[u] = false;
		while (Stack[top] != u)
		{
			color[Stack[top]] = cid;
			cnt[cid]++;
			inStack[Stack[top]] = false;
			top--;
		}
		top--;
	}
}

void init()
{
	scanf("%d%d",&n,&m);
	for (int i = 1;i <= m;++i)
	{
		qread(ui);
		qread(vi);
		addedge(ui,vi);
	}
}

void work()
{
	for (int i = 1;i <= n;++i) if (!dfn[i]) tarjan(i);
	for (int i = 1;i <= n;++i)
	{
		if (cnt[color[i]] > 1) puts("T");
		else puts("F");
	}
}

分析

裸的 Tarjan ⁡ \operatorname{Tarjan} Tarjan吧……只需要在最后判断是否有只有一个节点作为强连通分量的,由于不能自己把消息传给自己,这样是非法的。


T3 周年纪念日

测试总结:温州中学 2011年11月月赛 Day1_第3张图片测试总结:温州中学 2011年11月月赛 Day1_第4张图片
参考代码

#define int long long

const int MAXN = 100005;

struct edge
{
	int to,nxt,wgt;
} info[MAXN << 1];

struct node
{
	int from,to,wgt;
	friend bool operator <(const node &na,const node &nb)
	{
		return na.wgt < nb.wgt;
	}
} Edge[MAXN << 1];

int n,m,e,ui,vi,wi,cost,maxd,minID = INT_MAX;
long long mind = LLONG_MAX;
int head[MAXN],ufs[MAXN],sumPopulation[MAXN],population[MAXN];
long long dist[MAXN];

template<typename T> void qread(T &sum)
{
	sum = 0;
	register char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9')
	{
		sum = (sum << 1) + (sum << 3) + ch - '0';
		ch = getchar();
	}
}

inline void addedge(int from,int to,int wgt)
{
	info[++e].to = to;
	info[e].wgt = wgt;
	info[e].nxt = head[from];
	head[from] = e;
}

inline int find(int u)
{
	return ufs[u] == u ? u : ufs[u] = find(ufs[u]);
}

void preDfs(int u,int f)
{
	for (int i = head[u]; i; i = info[i].nxt)
	{
		int v = info[i].to;
		if (v == f) continue;
		preDfs(v,u);
		sumPopulation[u] += sumPopulation[v];
		dist[u] += dist[v] + sumPopulation[v] * info[i].wgt;
	}
}

void postDfs(int u,int f)
{
	for (int i = head[u]; i; i = info[i].nxt)
	{
		int v = info[i].to;
		if (v == f) continue;
		dist[v] = dist[u] - sumPopulation[v] * info[i].wgt + (sumPopulation[1] - sumPopulation[v]) * info[i].wgt;
		postDfs(v,u);
	}
}

void init()
{
	scanf("%lld%lld",&n,&m);
	for (int i = 1; i <= n; ++i) ufs[i] = i;
	for (int i = 1; i <= n; ++i)
	{
		qread(population[i]);
		sumPopulation[i] = population[i];
	}
	for (int i = 1; i <= m; ++i)
	{
		qread(Edge[i].from);
		qread(Edge[i].to);
		qread(Edge[i].wgt);
	}
	sort(Edge + 1,Edge + m + 1);
}

void work()
{
	int used = 0;
	for (int i = 1; i <= m; ++i)
	{
		if (used == n - 1) break;
		int u = find(Edge[i].from);
		int v = find(Edge[i].to);
		if (u != v)
		{
			ufs[u] = v;
			cost += Edge[i].wgt;
			maxd = max(maxd,Edge[i].wgt);
			addedge(Edge[i].from,Edge[i].to,Edge[i].wgt);
			addedge(Edge[i].to,Edge[i].from,Edge[i].wgt);
			used++;
		}
	}
	preDfs(1,0);
	postDfs(1,0);
	for (int i = 1; i <= n; ++i)
	{
		if (mind > dist[i])
		{
			minID = i;
			mind = dist[i];
		}
	}
	printf("%lld %lld\n%lld %lld",cost,maxd,minID,mind);
}

#undef int

分析

既然修路的费用就是路的长度了,那么求最小费用就是求一个MST;还要让MST中最大边权值最小,后者就是Kruskal的特性。

那么现在只需要在MST上找一点,使得其余点到这一点的距离(带了权值Pi,下同)最小。

考虑二次扫描换根法,使用两遍DFS,第一次由下到上计算出:

  • sumPopulaition[i]:表示以i为根的子树中,包括节点i的总人数。
  • dist[i]:表示以i为根的子树中,其所有儿子节点到根的距离。

第二次由上到下计算出:

  • dist[i]:表示除了节点i的所有节点到节点i的距离。

有一些细节需要注意

  • dist[u] +=dist[v]+ sumPopulation[v] * info[i].wgt;

如果不加上dist[v],那么sumPopulation[v] * info[i].wgt只代表以v为根的子树中,所有人口经过道路i的“不高心度”。这没有把v的所有儿子节点中的人口走到节点v的“不高心度”计算在内。

  • dist[v] = dist[u] - sumPopulation[v] * info[i].wgt + (sumPopulation[1] - sumPopulation[v]) * info[i].wgt;

这句话看起来有些难以理解,其实它本来应该是这样的:

dist[v] = dist[v] + dist[u] - (dist[v] + sumPopulation[v] * info[i].wgt) + (sumPopulation[1] - sumPopulation[v]) * info[i].wgt;

这里和上边是一样的解释:

dist[v]是本来节点v的所有儿子节点到v的不高兴度;
dist[u] - (dist[v] + sumPopulation[v] * info[i].wgt)是除了v和v的所有儿子节点外,所有点到v的父节点的“不高兴度”1
(sumPopulation[1] - sumPopulation[v]) * info[i].wgt;是其他节点通过边i产生的“不高兴度”。

可以发现,dist[v]被消掉了。于是就产生了源代码中的转移方程。


总结

从T1可以知道,当一个事件的决策只有选与不选两种时,可以利用位运算来简化代码实现、优化计算速度。

从T2可以知道,上课要认真听 q w q qwq qwq

从T3可以知道,一是对于一些基本的模板,以及它们的性质要熟悉,二来是要注意换根的时候,要加上原来的标记(感性理解一下吧)。


  1. 由于第二次DFS由上向下计算,计算dist[v]时,dist[u]已经代表所有节点到u的“不高兴度”了。 ↩︎

你可能感兴趣的:(测试总结)