1002 图论专练 解题报告
T1 重量不同的硬币
有N(1 <= N <= 1,000)个硬币,编号为1..N。
给出W(1 <= W <= 3,000)个推断对(A,B),表示硬币A比硬币B重。
寻找并输出一个硬币编号,要求其重量明确不同于其他硬币的个数最多。如果有多个答案,输出编号最小的一个。如果给出的数据有矛盾,输出"IMPOSSIBLE"
PROBLEM NAME: coins
INPUT FORMAT:
* Line 1: 两个整数: N and W.
* Lines 2..W+1: 每行两个整数: A, B
OUTPUT FORMAT:
* Line 1: 重量不同于其他硬币的个数最多的硬币编号。
【题解】
根据题意建图,注意正反图都要建(蒟蒻考试时没有建反图,挂掉)
如果图中有环,显然是"IMPOSSIBLE",所以先用tarjan判环。
然后两遍dfs遍历,找到每个结点的子树大小,输出最大的编号就行了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<ctime> 7 #include<algorithm> 8 #include<queue> 9 using namespace std; 10 #define INF 1000000000 11 #define MAXN 3010 12 struct node{int y,next,v;}e[MAXN*2]; 13 int n,m,len,ans,now,bcnt,top,maxx,Link[MAXN],stack[MAXN],inst[MAXN],vis[MAXN],dfn[MAXN],low[MAXN],belong[MAXN],cnt1[MAXN],cnt2[MAXN]; 14 inline int read() 15 { 16 int x=0,f=1; char ch=getchar(); 17 while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} 18 while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} 19 return x*f; 20 } 21 void insert(int x,int y,int v) 22 { 23 e[++len].next=Link[x]; 24 Link[x]=len; 25 e[len].y=y; 26 e[len].v=v; 27 } 28 void tarjan(int x) 29 { 30 dfn[x]=low[x]=++now; 31 stack[++top]=x; inst[x]=1; 32 for(int i=Link[x];i;i=e[i].next) 33 if(e[i].v) 34 { 35 if(!dfn[e[i].y]) 36 { 37 tarjan(e[i].y); 38 low[x]=min(low[x],low[e[i].y]); 39 } 40 else if(inst[e[i].y]) low[x]=min(low[x],dfn[e[i].y]); 41 } 42 if(dfn[x]==low[x]) 43 { 44 int y; bcnt++; 45 do 46 { 47 y=stack[top--]; 48 inst[y]=0; 49 belong[y]=bcnt; 50 }while(y!=x); 51 } 52 } 53 void dfs1(int x) 54 { 55 cnt1[x]=1; vis[x]=1; 56 for(int i=Link[x];i;i=e[i].next) 57 if(!vis[e[i].y]&&e[i].v) 58 { 59 dfs1(e[i].y); 60 cnt1[x]+=cnt1[e[i].y]; 61 } 62 } 63 void dfs2(int x) 64 { 65 cnt2[x]=1; vis[x]=1; 66 for(int i=Link[x];i;i=e[i].next) 67 if(!vis[e[i].y]&&!e[i].v) 68 { 69 dfs2(e[i].y); 70 cnt2[x]+=cnt2[e[i].y]; 71 } 72 } 73 int main() 74 { 75 freopen("coins.in","r",stdin); 76 freopen("coins.out","w",stdout); 77 n=read(); m=read(); 78 for(int i=1;i<=m;i++) 79 { 80 int x=read(),y=read(); 81 insert(x,y,1); insert(y,x,0); 82 } 83 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); 84 for(int i=1;i<=n;i++) 85 { 86 if(vis[belong[i]]) {printf("IMPOSSIBLE\n"); return 0;} 87 else vis[belong[i]]=1; 88 } 89 for(int i=1;i<=n;i++) 90 { 91 memset(vis,0,sizeof(vis)); 92 dfs1(i); 93 memset(vis,0,sizeof(vis)); 94 dfs2(i); 95 if(cnt1[i]+cnt2[i]>maxx) {maxx=cnt1[i]+cnt2[i]; ans=i;} 96 } 97 printf("%d\n",ans); 98 return 0; 99 }
T2 杀人游戏
【问题描述】
天黑请闭眼……
杀人游戏其实小x并不会玩,于是他自己设计了一个杀人游戏。
现在有n个人,每个人都有一个自己要杀的对象,并且轮到他后,他肯定可以杀掉自己想杀的人,当然,如果有人想自杀,轮到自己的时候,也是可以杀掉自己的。
现在,作为游戏的主宰,你可以设计杀人的顺序,顺序不一样,死的人的数目肯定也不一样。现在你想知道,如何设计顺序,让死的最多和最少。
【输入】
第一行,一个整数n,表示有n个人。
第二行,n个用空格隔开整数Xi,Xi表示第i个人要杀的人的编号。
【输出】
空格隔开的两个整数,minn和maxx,表示最少的杀人数和最多的杀人数。
【题解】
问题1,最少死几个人:
没有入度的点必然不死,不死的点指向的点必死。使用拓扑排序实现,若最后剩下环且环上所有点都不死,则每个环死亡人数为(L+1)/2。
问题2,最多死几个人:
没有入度的点必然不死,若存在没有叶子且长度大于1的环,则该环上有一个人不死。其余人都可以死亡。
【吐槽】
考试时策略想错了,搞了2个多小时的平衡树,只得了10分。。。
这启示这我们没事别瞎搞高级数据结构。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<ctime> 7 #include<algorithm> 8 using namespace std; 9 #define MAXN 1000010 10 int n,head,tail,ans1,ans2,a[MAXN],id[MAXN],q[MAXN],die[MAXN],undie[MAXN]; 11 inline int read() 12 { 13 int x=0,f=1; char ch=getchar(); 14 while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} 15 while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} 16 return x*f; 17 } 18 int main() 19 { 20 freopen("maf.in","r",stdin); 21 freopen("maf.out","w",stdout); 22 n=read(); 23 for(int i=1;i<=n;i++) {a[i]=read(); id[a[i]]++;} 24 for(int i=1;i<=n;i++) if(!id[i]) q[++tail]=i; ans1=tail; 25 while(++head<=tail) 26 { 27 int x=q[head]; ans2=tail; 28 if(die[a[x]]) continue; 29 die[a[x]]=1; undie[a[a[x]]]=1; 30 if(--id[a[a[x]]]==0) q[++tail]=a[a[x]]; 31 } 32 for(int i=1;i<=n;i++) 33 if(id[i]&&!die[i]) 34 { 35 int len=0,flag=0; 36 for(int j=i;!die[j];j=a[j]) 37 { 38 die[j]=1; len++; 39 flag|=undie[j]; 40 } 41 if(!flag&&len>1) ans1++; 42 ans2+=len/2; 43 } 44 printf("%d %d",n-ans2,n-ans1); 45 return 0; 46 }
T3 board
你在玩一个棋盘游戏. 给出一个R × C 的棋盘. 棋盘的每个格子雕刻了一个0到9的数字,或障碍物(用H表示).你的任务是从左上角开始走,最多能走多少步. 每一步的走法是这样的:
(1)查看你现在所处的格子,看它雕刻的数字,设为x.
(2)你可以选择四个方向走: 上、下、左、右.
(3)在你选择的方向跨x步(中间可以有障碍物)。
例如,棋盘用b[R][C]表示,b[1][1] = 3, 而你此刻就在第一行第一列,你决定向右走,那么你将到达第一行第四列。
当你跳进了一个障碍物格子,或者跳出界了,那么游戏结束.
输入:
第一行 : R 和 C, 表示棋盘的行数和列数. 1 <= R , C <=50
接下来有R行,每行C个字符,每个字符要么是0-9的数字,要么是字符H.
输出:
从左上角开始走,你最多能走多少步. 如果可以走无穷步,输出-1
【题解】
首先建图:每一个点设置一个编号,终点设为n*m+1,每个点先它能到达的点连一条边。
然后tarjan判环:如果图中有环,显然应该输出-1.
最后在图上跑最长路,dis值最大的就是答案。
【吐槽】
考试时没有判环,而且写了一个带回溯的dfs,成功挂掉。
昨天改了一下午,一直是秘制错误,蒙大神Cydiater指点,原来是数组开小了。。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<ctime> 7 #include<algorithm> 8 #include<queue> 9 using namespace std; 10 #define INF 1000000000 11 #define MAXN 1000000 12 const int dx[4]={1,0,-1,0}; 13 const int dy[4]={0,-1,0,1}; 14 struct node{int y,next,v;}e[MAXN]; 15 int n,m,len,ans,top,cnt,flag,now,bcnt,node[55][55],belong[MAXN],dis[MAXN],Link[MAXN],q[MAXN],vis[MAXN],dfn[MAXN],low[MAXN],stack[MAXN],instack[MAXN]; 16 char map[55][55]; 17 inline int read() 18 { 19 int x=0,f=1; char ch=getchar(); 20 while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} 21 while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} 22 return x*f; 23 } 24 void insert(int x,int y,int v) 25 { 26 e[++len].next=Link[x]; 27 Link[x]=len; 28 e[len].y=y; 29 e[len].v=v; 30 } 31 int cal(int a,int b) 32 { 33 if(a<1||b<1||a>n||b>m) return n*m+1; 34 if(map[a][b]=='H') return n*m+1; 35 return (a-1)*m+b; 36 } 37 void tarjan(int x) 38 { 39 dfn[x]=low[x]=++now; 40 stack[++top]=x; instack[x]=1; 41 for(int i=Link[x];i;i=e[i].next) 42 { 43 if(!dfn[e[i].y]) 44 { 45 tarjan(e[i].y); 46 low[x]=min(low[x],low[e[i].y]); 47 } 48 else if(instack[e[i].y]) low[x]=min(low[x],dfn[e[i].y]); 49 } 50 if(dfn[x]==low[x]) 51 { 52 int y; bcnt++; 53 do 54 { 55 y=stack[top--]; 56 instack[y]=0; 57 belong[y]=bcnt; 58 }while(x!=y); 59 } 60 } 61 void spfa() 62 { 63 memset(vis,0,sizeof(vis)); 64 memset(dis,0,sizeof(dis)); 65 int head=0,tail=1; 66 q[1]=1; vis[1]=1; dis[1]=0; 67 while(++head<=tail) 68 { 69 int x=q[head]; 70 for(int i=Link[x];i;i=e[i].next) 71 { 72 if(dis[x]+e[i].v>dis[e[i].y]) 73 { 74 dis[e[i].y]=dis[x]+e[i].v; 75 if(!vis[e[i].y]) 76 { 77 vis[e[i].y]=1; 78 q[++tail]=e[i].y; 79 } 80 } 81 } 82 vis[x]=0; 83 } 84 } 85 86 int main() 87 { 88 freopen("board.in","r",stdin); 89 freopen("board.out","w",stdout); 90 n=read(); m=read(); 91 for(int i=1;i<=n;i++) 92 { 93 scanf("\n"); 94 for(int j=1;j<=m;j++) 95 scanf("%c",&map[i][j]); 96 } 97 for(int i=1;i<=n;i++) 98 for(int j=1;j<=m;j++) 99 if(map[i][j]!='H') 100 { 101 int p=map[i][j]-'0',x=cal(i,j),y=0; 102 y=cal(i+p,j); insert(x,y,1); 103 y=cal(i-p,j); insert(x,y,1); 104 y=cal(i,j+p); insert(x,y,1); 105 y=cal(i,j-p); insert(x,y,1); 106 } 107 for(int i=1;i<=n*m+1;i++) if(!dfn[i]) tarjan(i); 108 for(int i=1;i<=n*m+1;i++) 109 { 110 if(vis[belong[i]]) {printf("-1\n"); return 0;} 111 else vis[belong[i]]=1; 112 } 113 spfa(); 114 for(int i=1;i<=n*m+1;i++) ans=max(ans,dis[i]); 115 printf("%d\n",ans); 116 return 0; 117 }
T4 魔法逮鱼
【问题描述】
Jzyz的人工湖里有很多很多鱼,小x发现,在湖底有n个坑,到每条鱼会找一个坑进行休息,如果一个坑被一条鱼占据,就不会有其他鱼来休息。当然,不一定每一个坑都有鱼。
现在小x会一种魔法,可以判断出从连续的坑里的鱼的个数是奇数还是偶数。当然,魔法必须要消耗魔力,如果判断一次从第i个坑到底j个坑的鱼的情况,小x需要话费Cij的魔力。
现在问题来了,小x想知道,最少需要话费多少魔力,才能知道每个坑里是否有鱼。
【输入】
第一行,一个整数n
接下来n行,第i行有n-i+1个整数,表示一次询问话费的魔力,其中Cij为第i行第j-i列个数 (1<=i<=j<=n,1<=c_ij<=10^9)
【输出】
一个整数,表示最小的魔力话费。
【题解】
一个神奇的最小生成树问题。
我就不再写题解了,详见:http://www.cnblogs.com/zrts/p/bzoj3714.html
【吐槽】
考试时并查集写错了,无限想抽自己。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<ctime> 7 #include<algorithm> 8 #include<queue> 9 using namespace std; 10 #define INF 1000000000 11 #define MAXN 4000010 12 struct node{int x,y,v;}e[MAXN]; 13 int n,len,k,f[MAXN]; 14 long long ans; 15 inline int read() 16 { 17 int x=0,f=1; char ch=getchar(); 18 while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} 19 while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} 20 return x*f; 21 } 22 bool mycmp(node a,node b) {return a.v<b.v;} 23 void insert(int x,int y,int v) {e[++len].x=x;e[len].y=y;e[len].v=v;} 24 int find(int x) {return f[x]==x?x:f[x]=find(f[x]);} 25 int main() 26 { 27 freopen("kug.in","r",stdin); 28 freopen("kug.out","w",stdout); 29 n=read(); 30 for(int i=1;i<=n;i++) 31 for(int j=i;j<=n;j++) 32 { 33 int v=read(); 34 insert(i,j+1,v); 35 } 36 sort(e+1,e+len+1,mycmp); 37 for(int i=1;i<=n;i++) f[i]=i; 38 for(int i=1;i<=len;i++) 39 { 40 int x=find(e[i].x),y=find(e[i].y); 41 if(x!=y) 42 { 43 f[x]=y; 44 ans+=e[i].v; 45 } 46 } 47 printf("%I64d\n",ans); 48 return 0; 49 }
T5 这题有点玄学,先留个坑。
T6 黑暗城堡(castle.pas/c/cpp)
题目描述
在顺利攻破 Lord lsp 的防线之后,lqr 一行人来到了 Lord lsp 的城堡下方。Lord lsp 黑化 之后虽然拥有了强大的超能力,能够用意念力制造建筑物,但是智商水平却没怎么增加。现 在 lqr 已经搞清楚黑暗城堡有 N 个房间,M 条可以制造的双向通道,以及每条通道的长度。
lqr 深知 Lord lsp 的想法, 为了避免每次都要琢磨两个 房间之间的最短路径,Lord lsp 一定会把城堡修建成树形的; 但是,为了尽量提高自己的移 动效率,Lord lsp 一定会使得 城堡满足下面的条件:设 Di 为如果所有的通道都被修建, 第 i 号房间与第 1 号房间的最 短路径长度;而 Si 为实际修建 的树形城堡中第 i 号房间与第1 号房间的路径长度,对于所有满足 1≤i≤N 的整数 i,有 Si = Di。
为了打败 Lord lsp,lqr 想知道有多少种不同的城堡修建方案。于是 lqr 向 applepi 提出了这个问题。由于 applepi 还 要忙着出模拟赛,所以这个任务就交给你了。当然,你只需要输出答案对 231 – 1 取模之后 的结果就行了。
输入格式
第一行有两个整数 N 和 M。
之后 M 行,每行三个整数 X,Y 和 L,表示可以修建 X 和 Y 之间的一条长度为 L 的通
道。
输出格式
输出一个整数,表示答案对 231 – 1 取模之后的结果。
【题解】
======================题解来自mzx============================
很显然题目要求的是一个图的生成树,这棵生成树要求满足根节点到每个结点的距离都等于原图中的最短距离
我们不妨将这样的树称之为这个图的最短路径生成树
首先生成树中的边肯定是最短路径子图中的边
我来解释一下这个概念……
首先图是由点集和边集两个元素组成的
那么子图的点集和边集肯定是原图中的子集
最短路径子图的点集肯定和原图一样
顾名思义边集就是所有可能出现在最短路径中的边的集合
即E'={e|dis[e.u]+e.l=dis[e.v]}
我们可以用显然法证明,最短路径生成树一定是最短路径子图的生成树
然鹅最短路径子图的生成树却并不一定是最短路径生成树_(:зゝ∠)_
举个例子
V={1,2,3},E={(1,2,1),(2,3,1),(1,3,2)}(最后一个元素代表边的长度)
显然这个图的每一条边都可能出现在最短路径中
所以这个图的最短路径子图就是它本身
{(1,3,2)(2,3,1)}是这个图的最短路径子图的一个生成树
然鹅并不是这个图的最短路径生成树,因为1到2的距离是3
有了以上的思路之后,我们可以先求出来最短路径子图
最短路径子图怎么求?
根据定义,跑一遍任何一个单源最短路算法(dij,SPFA),然后dis[i]+l[i][j]=dis[j]的边就是最短路径子图的边
然后我们将这些点按dis排序
假设我们现在已经求出了连通1...i-1这几个点的最短路径生成树个数
首先第i个点肯定是由1...i-1中的其中一个点连接的,而不可能是dis比i还大的那些点连接过来的。
然后1...i-1的生成树的形态和1...i-1中哪个点连接i这个点是没有关系的
也就是说这两个方案是相互独立的,计数中的互相独立意味着乘法原理的可行性
那么连通1...i的最短路径生成树个数就是1...i-1的生成树个数乘以1...i-1中任何一个点连接i这个点的最短路边的个数
讲完了
是不是很玄学……我也觉得,很想细讲但是发现没办法再讲了,自己脑补吧……
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<ctime> 7 #include<algorithm> 8 #include<queue> 9 using namespace std; 10 #define INF 1000000000 11 #define MAXN 1010 12 #define mod ((1<<31)-1) 13 struct node1{long long y,next,v;}e[MAXN*MAXN]; 14 struct node2{long long id,v;}dis[MAXN]; 15 long long n,m,len,ans=1,Link[MAXN],vis[MAXN],q[MAXN],map[MAXN][MAXN]; 16 inline long long read() 17 { 18 long long x=0,f=1; char ch=getchar(); 19 while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} 20 while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} 21 return x*f; 22 } 23 void insert(long long x,long long y,long long v) {e[++len].next=Link[x];Link[x]=len;e[len].y=y;e[len].v=v;} 24 bool cmp(node2 a,node2 b) {return a.v<b.v;} 25 void spfa() 26 { 27 for(long long i=1;i<=n;i++) dis[i].id=i,dis[i].v=INF; 28 long long head=0,tail=1; 29 q[1]=1; vis[1]=1; dis[1].v=0; 30 while(++head<=tail) 31 { 32 long long x=q[head]; 33 for(long long i=Link[x];i;i=e[i].next) 34 { 35 if(dis[x].v+e[i].v<dis[e[i].y].v) 36 { 37 dis[e[i].y].v=dis[x].v+e[i].v; 38 if(!vis[e[i].y]) 39 { 40 q[++tail]=e[i].y; 41 vis[e[i].y]=1; 42 } 43 } 44 } 45 vis[x]=0; 46 } 47 } 48 int main() 49 { 50 freopen("castle.in","r",stdin); 51 freopen("castle.out","w",stdout); 52 n=read(); m=read(); 53 memset(map,10,sizeof(map)); 54 for(long long i=1;i<=m;i++) 55 { 56 long long x=read(),y=read(),v=read(); 57 insert(x,y,v); insert(y,x,v); 58 map[x][y]=min(map[x][y],v); 59 map[y][x]=map[x][y]; 60 } 61 spfa(); 62 sort(dis+1,dis+n+1,cmp); 63 for(long long i=2;i<=n;i++) 64 { 65 long long temp=0; 66 for(long long j=1;j<i;j++) 67 { 68 long long x=dis[i].id,y=dis[j].id; 69 if(dis[i].v==dis[j].v+map[x][y]) temp++; 70 } 71 ans=ans*temp%mod; 72 } 73 printf("%d\n",ans); 74 return 0; 75 }