复杂的按钮

该博客讨论了一个关于按钮操作的问题,其中按钮按下会改变其他按钮的状态。问题转化为图论问题,寻找是否存在使得所有按钮关闭的最小步骤数。通过分析按钮的入度和构建有向图,可以找到解决方案。如果存在入度为0的节点,可以通过贪心策略按顺序按这些节点,直到堆为空。若无入度为0的节点,说明存在环,问题无解。

复杂的按钮
(button.pas/c/cpp)
【题目描述】
小K在遗迹探险时遇到了N个按钮,刚开始所有的按钮都处于开状态,小K的经验告诉他把所有的按钮都关上会有“好事”发生,可是有些按钮按下时会让其他一些已经闭合的按钮弹开。经过研究,每个按钮都对应着一个固定的弹开集合,这个按钮按下时,弹开集合中所有的按钮都会变为开状态。现在小K想知道是否能让所有的按钮变为闭合状态。如果能,打印最少步数以及方案,否则,打印“no solution”。
【输入格式】
第一行一个整数N,表示按钮的个数;
接下来N行,表示编号为1到N个按钮的弹开集合,格式为Mi,B1B2B3...BMi,表示编号为i的按钮按下时,会让编号为B1B2B3...BMi的按钮弹开(注:其中不会出现重复)
【输出格式】
如果无解,输出“no solution”;否则,第一行输出最少步数ret,第二行输出ret个数,表示按顺序按下编号为这些数的按钮就可以解决,每2个整数之间用一个空格隔开;如果有多种方案,请输出字典序最小的方案。
【样例输入】
6
2 2 3
0
2 4 5
0
0
0

【样例输出】
6
1 2 3 4 5 6

【数据规模】
对于40%的数据: 1≤N≤10;
对于100%的数据:1≤N≤30,000;令M=M1+M2+M3+...+MN,则0≤M≤1,000,000;

首先,要看出来这是个鬼畜的图论题。咋么看出来的?比方说,按钮1,不受任何按钮的牵制,更何况他的序号更小,那么肯定先按他,以后就不会再次按到他了。那么我们说第一问的答案如果有解肯定是n。为什么?举个例子,所有按钮都没牵制任何按钮,那么最少最少,也需要按n次。因为你需要的是按下每一个按钮,而每个按钮原来都是开着的,而且只能是自己按下按钮,而不是按下其他按钮导致的,所以得出ans1>=n。那么如果ans1>n,就说明n次以后肯定剩下了几个按钮,比如1,5,6。那么如果再按下1,5,6,必定又会有按钮弹起,比如2,5。那么如此反复,是永远得不到结果的,说明有解的情况ans1=n。那么再回到图论,解决第二问。如果某一个按钮不受别人牵制,那么就相当于他的入度为0嘛。那么,相应的,如果a牵制b,那么a到b就有一条有向边。那么,首先我们要按下的肯定都是入度为0的结点,然后把相应的边删了。当然,如果没有结点入度为0,那就是有环了,肯定无解了。那么,我们每次要找个入度为0且序号最小的结点,把他输出,并且修改相应边与相应的点,如此反复。那么这样的效率是n^2的,100分别想。那么,我们每次取个序号最小的,这里用了o(n)的时间,所以这里可以优下来,就是调队。每次用logn时间get,在删边的同时,判断他的子节点入度是否为0,为0则入堆。最后什么时候结束?len=0,即堆为空的时候,那么这题就解完了。

 1 var n,i,j,p,x,min,tot,t,len:longint;
 2     out_,in_,a,heap:array[0..30005] of longint;
 3     son,nxt:array[0..1000005] of longint;
 4     lnk:array[0..30005] of longint;
 5 procedure print_no;
 6 begin
 7   writeln('no solution');
 8   close(input); close(output);
 9   halt;
10 end;
11 procedure put(id:longint);
12 var i:longint;
13 begin
14   inc(len); heap[len]:=id; i:=len;
15   while (i>1) do
16   begin
17     if (heap[i>>1]>heap[i]) then
18     begin
19       heap[0]:=heap[i]; heap[i]:=heap[i>>1]; heap[i>>1]:=heap[0];
20       i:=i>>1;
21     end
22     else break;
23   end;
24 end;
25 function get:longint;
26 var fa,son:longint;
27 begin
28   get:=heap[1]; heap[1]:=heap[len]; dec(len); fa:=1;
29   while (fa<<1<=len) do
30   begin
31     if (fa<<1+1>len) or (heap[fa<<1]<heap[fa<<1+1]) then son:=fa*2
32                                                     else son:=fa*2+1;
33     if heap[fa]>heap[son] then
34     begin
35       heap[0]:=heap[fa]; heap[fa]:=heap[son]; heap[son]:=heap[0];
36       fa:=son;
37     end
38     else break;
39   end;
40 end;
41 procedure add(x,y:longint);
42 begin
43   inc(tot); son[tot]:=y; nxt[tot]:=lnk[x]; lnk[x]:=tot;
44 end;
45 begin
46   readln(n);
47   for i:=1 to n do
48   begin
49     read(out_[i]);
50     for j:=1 to out_[i] do
51     begin
52       read(x); inc(in_[x]); add(i,x);
53     end;
54   end;
55   min:=maxlongint;
56   for i:=1 to n do
57   if (in_[i]=0) then begin min:=0; put(i); end;
58   if min<>0 then print_no;
59   repeat
60     p:=get; inc(t); a[t]:=p; j:=lnk[p];
61     in_[p]:=-1; 
62     while j<>0 do
63     begin
64       dec(in_[son[j]]);
65       if in_[son[j]]=0 then put(son[j]);
66       j:=nxt[j];
67     end;
68   until len=0;
69   writeln(t);
70   for i:=1 to t do write(a[i],' ');
71 end.
View Code

 

转载于:https://www.cnblogs.com/whc200305/p/7103653.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值