【题解&&海量集训】day 6——图论专题

24 篇文章 1 订阅
10 篇文章 0 订阅

专辑:题解&&海量集训

一、水水的图

在这里插入图片描述


分析:

对于某两个点,如果想要把他们删除并且互不影响的话,那么他们一定不是在树上
那么根据这个性质我们就可以用树结构,广搜建立树,首先,删除的边必须在树上,才有可能对距离产生影响。而在树上的边,依题意可知,只有在其指向节点只能通过这条路到达这个深度时,这条边才是会产生影响的。因此记录使某节点在其深度的边的个数,当不止一条边可以使该节点获得这个深度,则这条树上的边也是可删除的

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len;
int linkk[1000001];
struct node{
    int y,next;
}e[1000001];
int dis[1000001];
int in[1000001];
bool vis[1000001];
struct node1{
    int l,r;
}a[10000001];
void insert(int xx,int yy){
    e[++len].next=linkk[xx];
    linkk[xx]=len;
    e[len].y=yy;
}
int main(){
	freopen("train.in","r",stdin);
	freopen("train.out","w",stdout);
	scanf("%d %d",&n,&m);
	for (int i=1;i<=m;i++){
	    scanf("%d %d",&a[i].l,&a[i].r);
	    insert(a[i].l,a[i].r);insert(a[i].r,a[i].l);
	}
	memset(dis,50,sizeof(dis));
	memset(vis,0,sizeof(vis));
	vis[1]=1;
	dis[1]=0;
	queue< int > q;
	q.push(1);
	while (!q.empty()){
	    int x=q.front();
	    q.pop();
	    for (int i=linkk[x];i;i=e[i].next){
	    	if (!vis[e[i].y]){
			    q.push(e[i].y);
			    vis[e[i].y]=1;
			    dis[e[i].y]=dis[x]+1;
			    in[e[i].y]=1;
			}
			else
			  if (dis[e[i].y]==dis[x]+1) in[e[i].y]+=1;
		}
	}
	bool f=1;
	for (int i=1;i<=m;i++){
	    int xx=a[i].l,yy=a[i].r;
	    if (dis[yy]<dis[xx]) swap(xx,yy);
	    if (dis[yy]-dis[xx]==0||in[yy]>1) printf("%d\n",i),f=0;
	}
	if (f) printf("NO");
	fclose(stdin);
	fclose(stdout);
	return 0;
}

二、重量不同的硬币

问题描述:
Fj有N个硬币,编号为1…N。
现在有W个推断,为(A,B),表示硬币A比硬币B重。
寻找并输出一个硬币编号,要求其重量明确不同于其他硬币的个数最多。
如果有多个答案,输出字典序最小的一个。
如果给出的数据有矛盾,输出"IMPOSSIBLE"


输入格式

Line 1: 两个整数: N and W.

Lines 2..W+1: 每行两个整数: A, B

输出格式

Line 1: 重量不同于其他硬币的个数最多的硬币编号。

样例数据
input
7 6
1 6
1 5
3 6
4 3
2 4
2 5

output
2

有7个硬币,6个推断对,2的重量不同于3,4,5,6


分析:对于两枚硬币如果a>b,那么必定b<a,所以硬币a和b是互不相同的。所以我们需要建一张正图表示a比b大,因为b<a,所以我们还需要建立一张反图,在正图和反图上跑两边dfs来记录比当前点小的硬币和比当前点大的硬币的个数,记录最大值即可。不过在跑BFS前我们需要用拓扑排序来判环。


那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int len1=0;
int len2=0;
int linkk1[1000001];
int linkk2[1000001];
struct node{
    int y,next;
};
node e1[1000001],e2[10000001];
int n,m;
int in[1010001];
int maxx=0,maxn;
int sum1[1000001];
int sum2[1000001];
bool vis1[1001];
bool vis2[1001];
void insert1(int xx,int yy){
    e1[++len1].next=linkk1[xx];
    e1[len1].y=yy;
    linkk1[xx]=len1;
}
void insert2(int xx,int yy){
    e2[++len2].next=linkk2[xx];
    e2[len2].y=yy;
    linkk2[xx]=len2;
}
int tuopusort(){
    queue < int > q;
    int Time=0;
    for (int i=1;i<=n;i++) if (!in[i]) q.push(i);
    while (!q.empty()){
    	Time++;
	    int x=q.front();q.pop();
	    for (int i=linkk1[x];i;i=e1[i].next){
		    in[e1[i].y]--;
		    if (!in[e1[i].y]) q.push(e1[i].y);
		}
	}
	return Time;
}
void dfs1(int k){
    sum1[k]=1;vis1[k]=1;
    for (int i=linkk1[k];i;i=e1[i].next){
    	if (vis1[e1[i].y]) continue;
    	dfs1(e1[i].y);
		sum1[k]+=sum1[e1[i].y];
	}
}
void dfs2(int k){
    sum2[k]=1;vis2[k]=1;
    for (int i=linkk2[k];i;i=e2[i].next){
    	if (vis2[e2[i].y]) continue;
    	dfs2(e2[i].y);
		sum2[k]+=sum2[e2[i].y];
	}
}
int main(){
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
    scanf("%d %d",&n,&m);
    for (int i=1;i<=m;i++){
	    int x,y;
	    scanf("%d %d",&x,&y);
	    in[y]++;
	    insert1(x,y);
	    insert2(y,x);
	}
	int t=tuopusort();
	if (t<n){
	    cout<<"IMPOSSIBLE";
	    return 0;
	}
	for (int i=1;i<=n;i++){
	    int ans=0;
	    memset(vis1,0,sizeof(vis1));
	    memset(vis2,0,sizeof(vis2));
	    dfs1(i);dfs2(i);
	    ans=sum1[i]+sum2[i];
	    if (maxx<ans) maxx=ans,maxn=i;
	}
	cout<<maxn;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

三、超级牛游戏

问题描述:
现在有N(1 <= N <= 2000)头奶牛在玩 超级牛 游戏。每头奶牛有一个唯一的ID,ID范围是 1 … 2 ^ 30-1。
超级牛比赛是淘汰赛 - 每场比赛后,输者退赛,赢者继续留在比赛,直到只剩一队游戏结束。 输赢是FJ自己决定的,或者说结果可以任意决定!
比赛的积分规则十分奇葩:积分=第一队的ID XOR 第二队的ID。 比如,12队和20队打比赛,积分是24,因为01100 XOR 10100 = 11000。
FJ认为,分越高越刺激。所以他想让总积分最高。请帮助FJ设计比赛。

输入格式
第一行包含一个整数N
以下N行包含N个队伍的ID。

输出格式
一行,一个整数,表示答案。


样例数据
input
4
3
6
9
10

output
37


输出详情:实现37的一种方法如下:

3 VS 9 ==》9胜,本场积分 3 XOR 9 = 10,目前队伍:6 9 10

6 VS 9 ==》6胜,本场积分 6 XOR 9 = 15,目前队伍:6 10

6 VS 10 ==》管他谁胜呢反正不用再比赛了,本场积分 6 XOR 10 = 12

总积分 10 + 15 + 12 = 37。

团队6和10面脱落,和队10胜。

拿下点的总数是(10)+(15)+(12)=

注:按位异或运算,由^符号通常表示,是进行逻辑异或运算的两个二进制整数的每个位置逐位操作。 规则如下 0 xor 0 = 0 0 xor 1 = 1 1 xor 0 = 1 1 xor 1 = 0


分析:因为每头牛都会进行一场比赛(显而易见的),所以假如我们把他们之间的比赛(关系)看成一张图,他们就是这张图上的节点,一共有n个节点。题目中又说两头牛经过一场比赛以后,会淘汰一头牛,也就是说这头牛不能和另外一头牛比赛,那么比赛结束后一共会进行n-1场比赛,那么这个图就是n-1场比赛将n个点连成一起,这是一棵生成树,而且题目中说值要最大,那么就是一棵最大生成树。

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL n;
struct node{
    LL x,y,v;
}e[10000001];
LL fa[10000001];
LL ans=0;
inline bool mycmp(node x,node y){
    return x.v>y.v;
}
int getfa(LL k){
    return k==fa[k]?k:fa[k]=getfa(fa[k]);
}
int len;
int main(){
	freopen("superbull.in","r",stdin);
	freopen("superbull.out","w",stdout);
    scanf("%lld",&n);
    LL a[n];
    for (LL i=1;i<=n;i++) fa[i]=i;
    for (LL i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (LL i=1;i<=n;i++)
      for (LL j=i+1;j<=n;j++)
        e[++len]=(node){i,j,a[i]^a[j]}/*,e[++len]=(node){j,i,a[i]^a[j]}*/;
    sort(e+1,e+len+1,mycmp);
    for (LL i=1;i<=len;i++){
	    LL x=getfa(e[i].x),y=getfa(e[i].y);
	    if (x!=y) ans+=e[i].v,fa[x]=y;
	}
	printf("%lld",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

四、damage

问题描述
农夫John的农场遭受了一场地震.有一些牛棚遭到了损坏,但幸运地,所有牛棚间的路经都还能使用.
FJ的农场有P(1 <= P <= 30,000)个牛棚,编号1…P. C(1 <= C <= 100,000)条双向路经联 接这些牛棚,编号为1…C. 路经i连接牛棚a_i和b_i (1 <= a_i<= P;1 <= b_i <= P).路经 可能连接a_i到它自己,两个牛棚之间可能有多条路经.农庄在编号为1的牛棚.
N (1 <= N <= P)头在不同牛棚的牛通过手机短信report_j(2 <= report_j <= P)告诉FJ它 们的牛棚(report_j)没有损坏,但是它们无法通过路径和没有损坏的牛棚回到到农场.
当FJ接到所有短信之后,找出最小的不可能回到农庄的牛棚数目.这个数目包括损坏的牛棚.


输入格式

第1行: 三个空格分开的数: P, C, 和 N

第2..C+1行: 每行两个空格分开的数: a_i 和 b_i

第C+2..C+N+1行: 每行一个数: report_j

输出格式

第1行: 一个数,最少不能回到农庄的牛的数目(包括损坏的牛棚).

样例数据

input
4 3 1
1 2
2 3
3 4
3

output
3


分析:因为发短信给FJ的牛棚是好的但是无法到达原点的牛棚,所以这个牛棚周围的牛棚必定也无法到达原点(易证),所以我们只需要将这个牛棚周围的牛棚全部标记为不能到达原点,最后从原点跑一遍bfs看能到达哪些点即可


那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int len=0;
int linkk[1000001];
int vis[1000001];
bool f[10000001];
struct node{
    int y,next;
}e[2000001];
void insert(int xx,int yy){
    e[++len].next=linkk[xx];
    linkk[xx]=len;
    e[len].y=yy;
}
void Break(int x){
    for (int i=linkk[x];i;i=e[i].next)
      vis[e[i].y]=-1,f[e[i].y]=1;
}
int main(){
	freopen("damage..in","r",stdin);
	freopen("damage..out","w",stdout);
    scanf("%d %d %d",&n,&m,&k);
    for (int i=1;i<=m;i++){
        int x,y;
        scanf("%d %d",&x,&y);
        insert(x,y);
        insert(y,x);
    }
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=k;i++){
        int x;
        scanf("%d",&x);
        vis[x]=-1;
        Break(x);
    }
    queue < int > q;
    q.push(1);
    int ans=0;
//	for (int i=1;i<=n;i++) cout<<vis[i]<<' ';
    while (!q.empty()){
        int x=q.front();
        q.pop();
        if (vis[x]==-1||f[x]) continue;
        vis[x]=1;f[x]=1;ans++;
        for (int i=linkk[x];i;i=e[i].next)
          q.push(e[i].y);
    }
    cout<<n-ans;
    fclose(stdin);
    fclose(stdout);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值