您的位置:首页 > 其它

[NOIp复习计划]:图的连通

2017-07-19 22:49 176 查看
[bzoj 2427][HAOI2010]软件安装

考虑到有环,就tarjan一发,把价值和空间合并一下,然后会发现,缩点之后是个森林,就可以跑树形dp了

设f[i][j]表示第i个节点,空间为j的时候的最大价值,然后类似背包的方式dp一发就行了。

f[i][j]=f[to[i]][k]+f[i][j−k]

注意边界,然后就RE到死了 a了。

对了vector.size()返回的不是int,而是size_type,它被定义成unsigned,所以 < size(),改写成< =size()-1,就有可能出事。。。

#include<bits/stdc++.h>
using namespace std;
const
12b10
int MAXN = 120;
const int MAXM = 560;
int f[MAXN][MAXM],n,m,w[MAXN],v[MAXN];
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
typedef vector<int> P;
vector<P> mp;
vector<P> mp2;
int cnt,clo,dfn[MAXN],low[MAXN],belong[MAXN],top,sta[MAXN];
bool ins[MAXN];
int v2[MAXN],w2[MAXN],in[MAXN];
void dfs(int x){
int i;
dfn[x]=low[x]=++clo;
sta[++top]=x;ins[x]=1;
rep(i,0,((int)mp[x].size()-1)){
if(!dfn[mp[x][i]]){
dfs(mp[x][i]);
low[x]=min(low[x],low[mp[x][i]]);
}else if(ins[mp[x][i]]){
low[x]=min(low[x],dfn[mp[x][i]]);
}
}
if(dfn[x]==low[x]){
cnt++;
while(top!=0&&sta[top]!=x){
belong[sta[top]]=cnt;
ins[sta[top--]]=0;
}
belong[sta[top]]=cnt;
ins[sta[top--]]=0;
}
}
void dp(int x){
int i,j,k;
int len=(int)mp2[x].size()-1;
rep(i,0,len){
if(mp2[x][i]==x)continue;
dp(mp2[x][i]);
dep(j,m-w2[x],0){
rep(k,0,j){
f[x][j]=max(f[x][j],f[mp2[x][i]][k]+f[x][j-k]);
}
}
}
dep(i,m,0){
if(i>=w2[x]){
f[x][i]=f[x][i-w2[i]]+v2[x];
}else{
f[x][i]=0;
}
}
}
int main(){
int i,j;
scanf("%d%d",&n,&m);
mp=vector<P>(n+5);
rep(i,1,n)scanf("%d",&w[i]);
rep(i,1,n)scanf("%d",&v[i]);
rep(i,1,n){
int a;scanf("%d",&a);
mp[a].push_back(i);
}
rep(i,0,n)if(!dfn[i])dfs(i);
mp2=vector<P>(cnt+5);
rep(i,0,n){
rep(j,0,(int)mp[i].size()-1){
if(belong[i]==belong[mp[i][j]])continue;
mp2[belong[i]].push_back(belong[mp[i][j]]);
in[belong[mp[i][j]]]++;
}
v2[belong[i]]+=v[i];
w2[belong[i]]+=w[i];
}
rep(i,1,cnt){if(!in[i]){mp2[0].push_back(i);}};
dp(0);
printf("%d",f[0][m]);
return 0;
}


[bzoj 2438][中山市选2011]杀人游戏

对于一个环来说,就询问一个人就够了,因为剩下的都能由这个人直接或间接的推出来。

啥都别说,先缩点,然后就变成了DAG,显然对于缩点后入度为0的点我们都要询问一次。

然而这并不对,这就是这题的坑了,对于一个独立或是所连的点的入度大于1的点,我们不需要对它询问,因为它可以由其他n-1个点推出来,但是一张图里最多就这一个点,特判一下就好了。

答案的计算就是最朴素的计算方式,需要询问的点/总点数

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100505;
const int maxm = 500505;
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
int h[maxn],to[maxm],nx[maxm],tot;
int n,m;
int dfn[maxn],low[maxn],ti,sta[maxn],cnt,top,size[maxn],in[maxn],belong[maxn];
void add_edge(int u,int v){
to[++tot]=v;nx[tot]=h[u];h[u]=tot;
}
bool ins[maxn];
void dfs(int x){
dfn[x]=low[x]=++ti;
sta[++top]=x;ins[x]=1;
for(int i=h[x];i;i=nx[i]){
if(!dfn[to[i]]){
dfs(to[i]);
low[x]=min(low[x],low[to[i]]);
}else if(ins[to[i]]){
low[x]=min(low[x],dfn[to[i]]);
}
}
if(dfn[x]==low[x]){
int now=0;cnt++;
while(x!=now){
now=sta[top];top--;
belong[now]=cnt;
ins[now]=0;
size[belong[now]]++;
}
}
}
void build(){
int i,j;
rep(i,1,n){
for(j=h[i];j;j=nx[j]){
if(belong[i]!=belong[to[j]]){
in[belong[to[j]]]++;

}
}
}
}
int main(){
int i,a,b,ans=0;
bool flag=0;
scanf("%d%d",&n,&m);
rep(i,1,m){
scanf("%d%d",&a,&b);
add_edge(a,b);
}
rep(i,1,n)if(!dfn[i])dfs(i);;
build();
rep(i,1,cnt)if(in[i]==0)ans++;
rep(i,1,n){
if(size[belong[i]]==1&&in[belong[i]]==0){
bool flag2=1;
for(int j=h[i];j;j=nx[j]){
if(in[belong[to[j]]]==1){
flag2=0;
break;
}
}
if(flag2)flag=1;
}
}
if(flag)ans--;
printf("%.6lf",(double)(n-ans)/(double)n);
return 0;
}


[bzoj 2730][HNOI2012]矿场搭建

破坏一个点,我们就考虑破坏割点的时候,如果一张图没有割点,我们就需要两个点,保证答案合法

而对于其他因为割点分成的连通块只需要选一个就好了,剩下的就是乘法原理乱搞。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10005
using namespace std;

int ri,n,m,cnt,now,all,tot,dfsclk,fst
,pnt
,nxt
,pos
,low
,mrk
,ans1;
bool cut
; long long ans2;
void add(int x,int y){
pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x,int fa){
int p,sz=0; pos[x]=low[x]=++dfsclk;
for (p=fst[x]; p; p=nxt[p]){
int y=pnt[p]; if (y==fa) continue;
if (!pos[y]){ sz++; dfs(y,x); }
low[x]=min(low[x],low[y]);
if (low[y]>=pos[x] && fa) cut[x]=1;
}
if (!fa && sz>1) cut[x]=1; if (cut[x]) cnt++;
}
void calc(int x){
mrk[x]=now; all++; int p;
for (p=fst[x]; p; p=nxt[p]){
int y=pnt[p];
if (cut[y] && mrk[y]!=now){ mrk[y]=now; cnt++; }
if (!mrk[y]) calc(y);
}
}
int main(){
while (~scanf("%d",&m) && m){
int i,x,y; tot=n=0; memset(fst,0,sizeof(fst));
for (i=1; i<=m; i++){
scanf("%d%d",&x,&y);
n=max(n,max(x,y)); add(x,y); add(y,x);
}
memset(pos,0,sizeof(pos)); memset(low,0,sizeof(low));
dfsclk=cnt=0; memset(cut,0,sizeof(cut));
for (i=1; i<=n; i++)
if (!pos[i]) dfs(i,0);
if (!cnt){ ans1=2; ans2=(long long)n*(n-1)>>1; } else{
ans1=0; ans2=1; memset(mrk,0,sizeof(mrk));
for (i=1; i<=n; i++) if (!cut[i] && !mrk[i]){
cnt=all=0; now++; calc(i);
if (cnt==1){ ans1++; ans2*=all; }
}
}
printf("Case %d: %d %lld\n",++ri,ans1,ans2);
}
}


[bzoj 3391][Usaco2004 Dec]Tree Cutting网络破坏

太水了,随便dfs一下就完了

懒癌发作,复制一篇0_0

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define N 10005
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Node{
int to,next;
}e[2*N];
int head
,n,num
,tot=0;
bool vis
;
void add(int u,int v)
{
e[++tot]=(Node){v,head[u]};head[u]=tot;
}
void dfs(int rt)
{
num[rt]=1;vis[rt]=1;
for(int i=head[rt];i;i=e[i].next)
{
if(!vis[e[i].to])
{
dfs(e[i].to);
num[rt]+=num[e[i].to];
}
else e[i].to=-1;
}
}
int main()
{
n=read();int u,v;
for(int i=1;i<n;i++)
{
u=read();v=read();
add(u,v);add(v,u);
}
dfs(1);
double mid=n/2;
for(int i=1;i<=n;i++)
{
bool ok=true;
for(int j=head[i];j;j=e[j].next)
if(e[j].to!=-1&&num[e[j].to]>mid){ok=false;break;}
if(n-num[i]>mid)ok=false;
if(ok==false)continue;
printf("%d\n",i);
}
return 0;
}


[bzoj 2208] [Jsoi2010]连通数

有环很烦的,于是先缩点,然后重新建图,这个时候就是DAG了,于是我们就可以dp了,这里我用了bitset优化,我以为我这么卡常一定垫底了,结果我看看rk6一脸懵逼,然后我才发现他们都是传递闭包做的……

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2300;
int n,h[maxn],to[maxn*maxn],nx[maxn*maxn],tot;
bitset<2005>vis[maxn];
int bl[maxn],cnt;
int h2[maxn],to2[maxn*maxn],nx2[maxn*maxn],tot2;
char str[3000];
int dfn[maxn],low[maxn],sta[maxn],ti,top;
bool ins[maxn];
bool has_solved[maxn];
void add_edge(int u,int v){
to[++tot]=v;nx[tot]=h[u];h[u]=tot;
}
void add_edge2(int u,int v){
to2[++tot2]=v;nx2[tot2]=h2[u];h2[u]=tot2;
}
void dfs(int x){
dfn[x]=low[x]=++ti;
sta[++top]=x;ins[x]=1;
for(int i=h[x];i;i=nx[i]){
if(!dfn[to[i]]){
dfs(to[i]);
low[x]=min(low[x],low[to[i]]);
}else if(ins[to[i]]){
low[x]=min(low[x],dfn[to[i]]);
}
}
if(dfn[x]==low[x]){
int now=0;cnt++;
while(now!=x){
now=sta[top--];
bl[now]=cnt;
ins[now]=0;
vis[cnt][now]=1;
}
}
}
void build(){
for(int i=1;i<=n;i++){
for(int j=h[i];j;j=nx[j]){
if(bl[i]!=bl[to[j]]){
add_edge2(bl[i],bl[to[j]]);
}
}
}
}
void dp(int x){
if(has_solved[x]==1)return;
has_solved[x]=1;
for(int i=h2[x];i;i=nx2[i]){
dp(to2[i]);
vis[x] |= vis[to2[i]];
}
}
int getans(){
int ans=0;
for(int i=1;i<=n;i++){
ans += vis[bl[i]].count();
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",str+1);
int j=1;
while(str[j]!=0){
if(str[j]=='1'){
add_edge(i,j);
}
j++;
}
}
for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
build();
for(int i=1;i<=cnt;i++)if(!has_solved[i])dp(i);
printf("%d",getans());
return 0;
}


[bzoj 1123][POI2008]BLO

答案是有向的数对,以及算了毁掉的点,参见discuss

我们考虑有向图的dfs树是没有交叉边的(连接到别的树上的边)这样的话如果该点是割点,那么分割的点对,是可以用dfs求的,就是每个点维护一个size,而对于非割点的点,答案一定是一样的

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100050;
const int maxm = 500050;
int n,m;
int h[maxn],to[maxm<<1],nx[maxm<<1],tot;
int low[maxn],dfn[maxn],ti;
int sz[maxn];
bool vis[maxn];
typedef long long ll;
ll ans[maxn];
void ins(int u,int v){
to[++tot]=v;nx[tot]=h[u];h[u]=tot;
to[++tot]=u;nx[tot]=h[v];h[v]=tot;
}
void dfs(int x){
ll sum=0;
vis[x]=1;sz[x]=1;
low[x]=dfn[x]=++ti;
for(int i=h[x];i;i=nx[i]){
if(!dfn[to[i]]){
dfs(to[i]);
sz[x]+=sz[to[i]];
low[x]=min(low[x],low[to[i]]);
if(dfn[x]<=low[to[i]]){
ans[x]+=sum*sz[to[i]];
sum+=sz[to[i]];
}
}else if(vis[to[i]]){
low[x]=min(low[x],dfn[to[i]]);
}
}
ans[x]+=sum*(n-sum-1);
}
int main(){
scanf("%d%d",&n,&m);
int a,b;
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
ins(a,b);
}
dfs(1);
for(int i=1;i<=n;i++)
printf("%lld\n",(ans[i]+n-1)*2);
return 0;
}



[codevs 2822] 爱在心中

缩点之后是DAG各种O(n)的性质乱搞就行了,其实暴力也可以。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2000;
int n,m,mp[maxn][maxn];
int mp2[maxn][maxn],bl[maxn],cnt,ti;
int low[maxn],dfn[maxn];
int pre[maxn];
int sta[maxn],top,sz[maxn];
bool ins[maxn];
void dfs(int x){
sta[++top]=x;
ins[x]=1;
low[x]=dfn[x]=++ti;
for(int i=1;i<=n;i++){
if(!mp[x][i])continue;
if(!dfn[i]){
dfs(i);
low[x]=min(low[x],low[i]);
}else if(ins[i]){
low[x]=min(low[x],dfn[i]);
}
}
if(dfn[x]==low[x]){
int now=0;cnt++;
while(now!=x){
now=sta[top--];ins[now]=0;
bl[now]=cnt;sz[cnt]++;
}
}
}
int in[maxn];
void build(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(mp[i][j] && bl[i]!=bl[j])
mp2[bl[i]][bl[j]]=1,in[bl[j]]++;
}
void solve(int x,int va){
for(int i=1;i<=cnt;i++){
if(mp2[x][i]){
sz[i]+=va;
solve(i,va);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
mp[a][b]=1;
}
for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
build();
int ans=0;
for(int i=1;i<=cnt;i++)pre[i]=sz[i];
for(int i=1;i<=cnt;i++)if(sz[i]>1)ans++;
printf("%d\n",ans);
for(int i=1;i<=cnt;i++)solve(i,pre[i]);
int id=0;
for(int i=1;i<=cnt;i++)if(sz[i]>=n && pre[i]>1)id=i;
for(int i=1;i<=n;i++)
if(bl[i]==id){
printf("%d ",i);
}
if(id==0)printf("-1");
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: