ツリー(木)
[問題点]説明
ツリー非環式無向グラフとしてグラフ理論。木を考えると、各ノードは、インジケータとボタンがあります。ボタンがノードを押された場合は、ランプからのノードが(プレスを前消灯時)消灯、または(押す前に点灯している)光までオフになります。そして、同じノードのすぐ隣にも変化します。最初は、すべてのライトが消灯しています。ライトのすべてのノードを作成し、点灯する回数を最小Yaoanボタンを計算するためにプログラミングしてください。
[入力形式の
データの入力ファイル複数のセット。
最初の行の入力は、整数n、ツリーのノードの数を含んでいます。各ノードは、1からnまで番号が付けられています。
次のnを入力- 1行、それぞれ含む二つの整数x、yは、ノードxとyとの間の無向エッジを発現しました。
入力nは、入力の終了を示し、0のとき。
[出力形式]
各試験のために、最小回数点灯及び点灯のすべてのノードを作成する出力Yaoanボタン。別のライン上のデータの各セット。
サンプル入力:
3
1 2
1 3
0
出力サンプル:
1
この質問は、文字の上に指で死んで太った男見inexplicable'veを思い出させる、その後近くを指すようになりますが、私は~~のタイトルを忘れて
家にクローサー、トピックは非常に明確にされているが、あなたは木だった教えて、それは非常にすることができツリーはDPを考えるのは簡単ですが、状態遷移方程式を探している重要な問題です。
まず、使用f[x][0]
息子、孫、息子、孫が...また、最小回数でのライトをオンにしながら、X上のサブツリー、ライトのルートとしてXに。
そしてf[x][1]
サブツリーのルートとしてXに、Xライト、そして彼の息子の世代へのライトの最小数をオフにします。
f[x][2]
これは、サブツリーのルートノードで表され、X、Xライトをオフにし、また最小回数のライトをオフに彼の息子の世代。
だから、f[x][3]
表現されているもの、それはライトのサブツリー、Xターンのルートは、彼の息子の世代の最小数も存在しないことは明らかである、ライトをオフにするので、Xにされているf[x][3]
ことを除外します。
Fの列の定義が考え出し、その次が容易になります!
また、3つの段階で:
f[x][0]
最高の数、もし私と私の世代のサブ点灯し、その後、私の友人や家族は、ライトをオンにする必要はありません、なぜ理由f[x][0]+=f[x][2]
2.でも次の場合f[x][2]
とf[x][1]
どのようにそれを計算するには?私たちは奇妙なプレスによって口座にボタンを私の息子の可能性のある世代を取る必要があり、最後の状態は、それを変更しますので(PS:何退屈な息子...)ので、私は途中で私をカウントする変数が必要何回(私は、この変数のボタン数を呼び出します)。どのようにして「を、科学的なシルクの責任」であり、我々は最小数を表すために変数を使用する必要があり、それがあるtmp+=min(f[x][2],f[x][1])
こと!
3. f[x][2]和f[x][1]
どのようにそれを継承するには?それは緑である-チベット-高-カウント私たちの最も最も最も美しいボタンを反映して、ここで元、ああ、偏った言うと、(2時、私は再読み込みするのか分からない、と述べました!)、状態はあなたが直接奇数の次の数は、行に順序を逆に好奇心にしたいので、奇数はその正反対のものであるので、ここで私はあなたのスキル、奇数と偶数を伝えることができ、全く逆であるf[x][2]=t
にも言葉がある一方で、f[x][1]=t
残りの部分あなたは、コードそれで直接見て考えることはできません場合、私は、私は、あなたが最初に考えて、あなたを教えてくれません!
我々はそれを完了するため、balabala、全体の質問をカタログの制作に続いて、状態遷移方程式を発見したでしょう!
私は、コードを添付していますここで、コードが詳細に説明されています。
#include<cmath>
#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;//据说这个比long long更大,不过后面输出要用llu。
//你细心可以发现我跟本没用long long^_^
struct node//边目录
{
int x,y,next;//x和y表示一条边的两个端点,next表示下一条边的编号
}a[210];int len,last[210];//last表示与当前边相连的最后一条边的编号,len是边的数目
void ins(int x,int y)
{
len++;//边的数目增加一条
a[len].x=x;a[len].y=y;//给边赋值
a[len].next=last[x];last[x]=len;//边的联系
}
int f[210][3];//我们上面讲到的定义
bool b[210];//因为我用的是双向边,而有可能会进入死循环
void treeDP(int x)
{
/*
f[x][0]x开灯,开灯
f[x][1]x不开灯,开灯
f[x][2]x不开灯,不开灯
*/
f[x][0]=1;f[x][1]=f[x][2]=0;//一开始的以x为结点的子树(包括点x)全部开灯就有一种方法:点亮x
//其他的归零
int minn=99999999,num=0,tmp=0;//这里的minn用的十分巧妙,num就是按钮计数,tmp就是存储最少次数
for(int k=last[x];k;k=a[k].next)//访问x的亲朋好友
{
int y=a[k].y;//找到x点所在的边的另一个点y
if(b[y]==false)//判断是否找过y
{
b[y]=true;treeDP(y);//标记找过,然后递归找y
f[x][0]+=f[y][2];//如果我和我的子辈都开着灯了,那么我的亲朋好友就不用开灯了啦!
tmp+=min(f[y][0],f[y][1]);//去最小的值
minn=min(abs(f[y][0]-f[y][1]),minn);//这里非常巧妙:
/*假设f[y][0]>f[y][1]的话,那么此时tmp加的就是f[y][1],而minn里面的就是f[y][0]-f[y][1]
那么tmp+minn实际上就是f[y][1]+f[y][0]-f[y][1]=f[y][0]
而反之,tmp加的就是f[y][0],那么minn里面的就是f[y][1]-f[y][0]
那么tmp+minn就是f[y][0]+f[y][1]-f[y][0]=f[y][1]了
*/
if(f[y][1]>=f[y][0])num++;
//如果不开灯比开灯要贵,那么我们肯定选开灯的,而此时就按了一次按钮,所以num++
}
}
if(num%2==1)
{
f[x][2]=tmp+minn;//此时,x不开灯,而他的子辈也不开灯,就要让其中一个儿子帮他开灯
f[x][1]=tmp;//此时,x不开灯,要他的子辈开灯,那怎么无缘无故开灯呢?所以就让他的儿子去开灯
}
else
{
f[x][2]=tmp;//同上,我说过是相反的
f[x][1]=tmp+minn;
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset(f,0,sizeof(f));
if(n==0)break;
len=0;memset(last,0,sizeof(last));
for(int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
ins(x,y);ins(y,x);
}
memset(b,false,sizeof(b));b[1]=true;
treeDP(1);//从1开始递归,因为1是根节点
printf("%d\n",min(f[1][0],f[1][1]));//最后找这两个的最小值就行了!
}
return 0;
}
あなたは動的計画8タイトルああcaioj.cn木の上にそれを行うことができ、類推によって学ぶ、あなたの時計をありがとう!