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
是一个全局变量,如果最后选用这种药物,那么在返回上一层递归时,这种药物依然是被选中的状态。而最后不选用的写法,恰好起到了一个“清零”的作用。
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吧……只需要在最后判断是否有只有一个节点作为强连通分量的,由于不能自己把消息传给自己,这样是非法的。
#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可以知道,一是对于一些基本的模板,以及它们的性质要熟悉,二来是要注意换根的时候,要加上原来的标记(感性理解一下吧)。
由于第二次DFS由上向下计算,计算dist[v]时,dist[u]已经代表所有节点到u的“不高兴度”了。 ↩︎