2012-0031
从 Trac 迁移的文章
这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。
原文章内容如下:
题意是有一个n个点m条边的图,敌人会随机选择一个点攻击,将与这个点相连的边都消灭,这个点的权值变成0。然后我们可以选择p个点保护,使得这些点如果被敌人攻击不会产生任何损失。
每个点都有一个权值,现在希望求在被攻击的最坏情况下,能够连到点S的点的权值和最大。
很显然,每个点如果被攻击,都会因为边的消失产生一个固定损失——因为这个点连出去的边消失使得与S相连的点数目减少导致权值和变小。
那么如果我们能够知道每个点被攻击造成的损失,就应该选择损失最大的p个来保护。
接下来,就是要求每个点的损失。
显然,根据连通性,一个点A被攻击之后会使得某一个点B无法与S连通,则这个点A必然是整个图的一个割点,且B在与S不同的一侧。
我们利用tarjan算法可以用o(n+m)的复杂度求出所有割点。其中,如果我们从S开始DFS,可以形成一棵DFS树,显然,如果A是一个割点,每一个由A点消失导致与S分开连通块,必然都是A的某一棵子树。因此我们可以在DFS时,用一个数组维护每个子树的权值和,然后在tarjan结束后,在每个点处判它的每个子树,是不是会因为这个点消失后与S不连通,如果是,就在这个点的损失值里加上这个子树的权值,其中损失值初值为这个点的权值。
最后只要将第p+1大的损失值输出就可以了。
然后有几点注意事项:
(1)、图中可能存在原本就与S不连通的区域,因此在DFS后,我们只去搜索被DFS扫描到的点,其它点都是不连通的,损失值应该设为0
(2)、如果p》=n,不会有任何损失值产生。
程序:
#include <cstdio>
#include <cstring>
int n,m,numk,sp,tot,t,o,p;
long long ans;
int x[20001],f[20001];
long long sum[20001],lose[20001],a[20001];
int low[20001],num[20001];
int y[200001],z[200001];
void add(int o,int p)
{
tot++;
y[tot]=p;
z[tot]=x[o];
x[o]=tot;
}
void dfs(int i)
{
sum[i]=a[i];
t++;
low[i]=num[i]=t;
for(int next=x[i];next;next=z[next])
if(y[next]!=f[i])
{
if(num[y[next]]>n)
{
f[y[next]]=i;
dfs(y[next]);
sum[i]=sum[i]+sum[y[next]];
if(low[i]>low[y[next]]) low[i]=low[y[next]];
} else
if(low[i]>num[y[next]]) low[i]=num[y[next]];
}
}
void kp(int s,int t)
{
int i,j;
long long m,o;
i=s;j=t;
m=lose[(s+t)/2];
while(i<=j)
{
while(lose[i]>m) i++;
while(lose[j]<m) j--;
if(i<=j)
{
o=lose[i];lose[i]=lose[j];lose[j]=o;
i++;j--;
}
}
if(i<t) kp(i,t);
if(s<j) kp(s,j);
}
int main()
{
while(scanf("%d %d %d",&n,&m,&numk)!=EOF)
{
memset(x,0,sizeof(x));
memset(f,0,sizeof(f));
tot=0;
for(int i=1;i<=n;i++) scanf("%lld",a+i);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&o,&p);
add(o,p);
add(p,o);
}
scanf("%d",&sp);
memset(low,0x70,sizeof(low));
memset(num,0x70,sizeof(num));
memset(lose,0,sizeof(lose));
memset(sum,0,sizeof(sum));
t=0;
f[sp]=0;
dfs(sp);
ans=sum[sp];
for(int i=1;i<=n;i++)
if(num[i]<=t)
{
lose[i]=a[i];
for(int next=x[i];next;next=z[next])
if((f[y[next]]==i)&&(num[i]<=low[y[next]]))
lose[i]+=sum[y[next]];
} else lose[i]=0;
kp(1,n);
if(numk<n) ans-=lose[numk+1];
printf("%lld\n",ans);
}
return 0;
}
by hzwlzrj
题意是有一个n个点m条边的图,敌人会随机选择一个点攻击,将与这个点相连的边都消灭,这个点的权值变成0。然后我们可以选择p个点保护,使得这些点如果被敌人攻击不会产生任何损失。
每个点都有一个权值,现在希望求在被攻击的最坏情况下,能够连到点S的点的权值和最大。
很显然,每个点如果被攻击,都会因为边的消失产生一个固定损失——因为这个点连出去的边消失使得与S相连的点数目减少导致权值和变小。
那么如果我们能够知道每个点被攻击造成的损失,就应该选择损失最大的p个来保护。
接下来,就是要求每个点的损失。
显然,根据连通性,一个点A被攻击之后会使得某一个点B无法与S连通,则这个点A必然是整个图的一个割点,且B在与S不同的一侧。
我们利用tarjan算法可以用o(n+m)的复杂度求出所有割点。其中,如果我们从S开始DFS,可以形成一棵DFS树,显然,如果A是一个割点,每一个由A点消失导致与S分开连通块,必然都是A的某一棵子树。因此我们可以在DFS时,用一个数组维护每个子树的权值和,然后在tarjan结束后,在每个点处判它的每个子树,是不是会因为这个点消失后与S不连通,如果是,就在这个点的损失值里加上这个子树的权值,其中损失值初值为这个点的权值。
最后只要将第p+1大的损失值输出就可以了。
然后有几点注意事项:
(1)、图中可能存在原本就与S不连通的区域,因此在DFS后,我们只去搜索被DFS扫描到的点,其它点都是不连通的,损失值应该设为0
(2)、如果p》=n,不会有任何损失值产生。
程序:
#include
#include
int n,m,numk,sp,tot,t,o,p;
long long ans;
int x[20001],f[20001];
long long sum[20001],lose[20001],a[20001];
int low[20001],num[20001];
int y[200001],z[200001];
void add(int o,int p)
{
tot++;
y[tot]=p;
z[tot]=x[o];
x[o]=tot;
}
void dfs(int i)
{
sum[i]=a[i];
t++;
low[i]=num[i]=t;
for(int next=x[i];next;next=z[next])
if(y[next]!=f[i])
{
if(num[y[next]]>n)
{
f[y[next]]=i;
dfs(y[next]);
sum[i]=sum[i]+sum[y[next]];
if(low[i]>low[y[next]]) low[i]=low[y[next]];
} else
if(low[i]>num[y[next]]) low[i]=num[y[next]];
}
}
void kp(int s,int t)
{
int i,j;
long long m,o;
i=s;j=t;
m=lose[(s+t)/2];
while(i<=j)
{
while(lose[i]>m) i++;
while(lose[j] if(i<=j) { o=lose[i];lose[i]=lose[j];lose[j]=o; i++;j--; } } if(i if(s } int main() { while(scanf("%d %d %d",&n,&m,&numk)!=EOF) { memset(x,0,sizeof(x)); memset(f,0,sizeof(f)); tot=0; for(int i=1;i<=n;i++) scanf("%lld",a+i); for(int i=1;i<=m;i++) { scanf("%d %d",&o,&p); add(o,p); add(p,o); } scanf("%d",&sp); memset(low,0x70,sizeof(low)); memset(num,0x70,sizeof(num)); memset(lose,0,sizeof(lose)); memset(sum,0,sizeof(sum)); t=0; f[sp]=0; dfs(sp); ans=sum[sp]; for(int i=1;i<=n;i++) if(num[i]<=t) { lose[i]=a[i]; for(int next=x[i];next;next=z[next]) if((f[y[next]]==i)&&(num[i]<=low[y[next]])) lose[i]+=sum[y[next]]; } else lose[i]=0; kp(1,n); if(numk printf("%lld\n",ans); } return 0; } by hzwlzrj