藍橋杯安慰奶牛

紀念一下,這是我第一次不依靠網上的代碼自己寫及格的一次題。雖然仍然運行超時,但是真的已經很給我這個代碼小白信心了

問題描述

Farmer John變得非常懶,他不想再繼續維護供奶牛之間供通行的道路。道路被用來連接N個牧場,牧場被連續地編號爲1到N。每一個牧場都是一個奶牛的家。FJ計劃除去P條道路中儘可能多的道路,但是還要保持牧場之間 的連通性。你首先要決定那些道路是需要保留的N-1條道路。第j條雙向道路連接了牧場Sj和Ej(1 <= Sj <= N; 1 <= Ej <= N; Sj != Ej),而且走完它需要Lj的時間。沒有兩個牧場是被一條以上的道路所連接。奶牛們非常傷心,因爲她們的交通系統被削減了。你需要到每一個奶牛的住處去安慰她們。每次你到達第i個牧場的時候(即使你已經到過),你必須花去Ci的時間和奶牛交談。你每個晚上都會在同一個牧場(這是供你選擇的)過夜,直到奶牛們都從悲傷中緩過神來。在早上 起來和晚上回去睡覺的時候,你都需要和在你睡覺的牧場的奶牛交談一次。這樣你才能完成你的 交談任務。假設Farmer John採納了你的建議,請計算出使所有奶牛都被安慰的最少時間。

輸入格式

第1行包含兩個整數N和P。

接下來N行,每行包含一個整數Ci

接下來P行,每行包含三個整數Sj, Ej和Lj

輸出格式
輸出一個整數, 所需要的總時間(包含和在你所在的牧場的奶牛的兩次談話時間)。
樣例輸入
5 7
10
10
20
6
30
1 2 5
2 3 5
2 4 12
3 4 17
2 5 15
3 5 6
樣例輸出
176
數據規模與約定

5 <= N <= 10000,N-1 <= P <= 100000,0 <= Lj <= 1000,1 <= Ci <= 1,000。

首先,數據給的是錯的,要是有人看的話,建議不要按他給的數據用

然後,這裏主要考察了最小生成樹的思想。

下面是我一開始的思路

#include<stdio.h>
int a[100005];//宏定義,要是在int main裏沒辦法開 
int b[100005];
int c[100005];
int l[10005];
struct Node//定義一個節點,包含他的指向和指向點所對應的距離和一個flag變量 
{
int zhi;
int ju;
int flag=0;
};
Node node[10005];
int main()
{
int n,p;//n點p線
scanf("%d%d",&n,&p);
FILE *fpRead=fopen("D:\\p.txt","r");  //這裏是爲了方便debug做的文件輸入可以採用 
    int i,j;
    for( i=1;i<=p;i++)  
    {  
        fscanf(fpRead,"%d%d%d ",&a[i],&b[i],&c[i]);   //同上 
    }  
int temp,temp1;
int k=0;
//for (i=1;i<=p;i++)
//{
// scanf("%d%d%d",&a[i],&b[i],&c[i]);
//}
//實際上這裏是一個冒泡排序。因此會運行超時。我覺得可以改善成快排,但是我不想去改了 
int min;
for (i=1;i<=n-1;i++)
{
min=99999;//每次找最小邊的時候先將最小邊定義爲一個很大的值 
for(j=1;j<=p;j++)
{
if (c[j]<min&&(node[a[j]].flag!=node[b[j]].flag||node[a[j]].flag==0||node[b[j]].flag==0))
//flag初始都爲0,表示沒有任何的連線,一旦flag不是0意味着有連線
//那麼,既然要避免相連的再次入選,就不能讓非零相等項進入判別 
{
min=c[j];
temp=j;
}
}
//這段代碼理解較爲困難。
//我們應該注意到,每個雙向的邊它可以用單向邊去表示。
//如果沒有下面這一段代碼,若是有一個點需要連着兩條或者更多條線路
//那麼後面的就會覆蓋前面的
//本來應該使用之前學的dnf那一塊思想去遍歷
//後來覺得太煩,就發現總共只有n-1條邊,那麼一個點應該只有不到1條定義邊
//那麼我就可以對即將被覆蓋掉的數據,先送給他所指向的那個點
//這樣雖然會反掉順序,但是不會丟失數據 
 
if (node[a[temp]].zhi!=0&&node[node[temp].zhi].zhi==0)
{
node[node[a[temp]].zhi].zhi=a[temp];
node[node[a[temp]].zhi].ju=node[a[temp]].ju;
   }
   
   //賦值語句,很好理解 
node[a[temp]].zhi=b[temp];
node[a[temp]].ju=min;

//flag全爲0,直接賦值就可以了。爲了不讓k重複,就要讓他不斷增加 
if (node[a[temp]].flag==node[b[temp]].flag&&node[b[temp]].flag==0)
{
node[a[temp]].flag=++k;
node[b[temp]].flag=node[a[temp]].flag;
}
//一個爲0,將另一個爲0的替換 
if (node[a[temp]].flag!=node[b[temp]].flag&&(node[a[temp]].flag==0||(node[b[temp]].flag==0)))
{
if (node[a[temp]].flag==0)
node[a[temp]].flag=node[b[temp]].flag; 
else
node[b[temp]].flag=node[a[temp]].flag;
}
 
//這裏就是和上面的全部"歸爲一個點 "呼應了。改掉一個點並不夠
//還要將和他一樣的點全部改掉 
//找大的其實沒有必要就是爲了好看 

if (node[a[temp]].flag!=node[b[temp]].flag&&node[b[temp]].flag!=0&&(node[a[temp]].flag!=0))
{
if (node[a[temp]].flag>node[b[temp]].flag)
   {
    temp1=node[b[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[a[temp]].flag;
}
}
   
if (node[a[temp]].flag<node[b[temp]].flag)
   {
    temp1=node[a[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[b[temp]].flag;
}
}
}
temp=99999;//temp重新置無窮 
}
for (i=1;i<=n;i++)
{
if (node[i].zhi!=0)
{
printf("%d %d %d",i,node[i].zhi,node[i].ju);//輸出 
printf("\n");
}
}
return 0;

}

我一開始寫的時候遇到一個難題。就是後面出現的數據會覆蓋掉我之前記載的數據。

這個問題的最好的解法可能和之前的那些dfs之類差不多

但是我在想,既然只有n-1條路,而我有n個點可以去存儲數據,那我調換一下位置不就沒有任何問題了嗎

於是出現了上面那一段代碼。




實際上那還是錯誤的

因爲那個代碼只能調換一次,調換之後如果再遇到仍然會覆蓋。這樣去寫一個迭代直到找到flag==0的時候應該也能解決問題。

但是那太煩了

我覺得還不如新開幾個數組來存儲

但是那樣又太耗費空間了

那這樣不如找到一個立刻輸出就是了

於是有了這一段代碼

#include<stdio.h>
int a[100005];//宏定義,要是在int main裏沒辦法開 
int b[100005];
int c[100005];
int l[10005];
int a1[100005];
int b1[100005];
int c1[100005];
struct Node//定義一個節點,包含他的指向和指向點所對應的距離和一個flag變量 
{
int zhi;
int ju;
int flag=0;
};
Node node[10005];
int main()
{
int n,p;//n點p線
scanf("%d%d",&n,&p);
//FILE *fpRead=fopen("D:\\c.txt","r");  //這裏是爲了方便debug做的文件輸入可以採用 
    int i,j;
    //for( i=1;i<=p;i++)  
    //{  
    //    fscanf(fpRead,"%d%d%d ",&a[i],&b[i],&c[i]);   //同上 
    //}  
int temp,temp1;
int k=0;
for (i=1;i<=p;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
}
int min;
for (i=1;i<=n-1;i++)
{
min=99999;//每次找最小邊的時候先將最小邊定義爲一個很大的值 
for(j=1;j<=p;j++)
{
if (c[j]<min&&(node[a[j]].flag!=node[b[j]].flag||node[a[j]].flag==0||node[b[j]].flag==0))
//flag初始都爲0,表示沒有任何的連線,一旦flag不是0意味着有連線
//那麼,既然要避免相連的再次入選,就不能讓非零相等項進入判別 
{
min=c[j];
temp=j;
}
}
printf("%d %d %d\n",a[temp],b[temp],c[temp]);
 

//flag全爲0,直接賦值就可以了。爲了不讓k重複,就要讓他不斷增加 
if (node[a[temp]].flag==node[b[temp]].flag&&node[b[temp]].flag==0)
{
node[a[temp]].flag=++k;
node[b[temp]].flag=node[a[temp]].flag;
}
//一個爲0,將另一個爲0的替換 
if (node[a[temp]].flag!=node[b[temp]].flag&&(node[a[temp]].flag==0||(node[b[temp]].flag==0)))
{
if (node[a[temp]].flag==0)
node[a[temp]].flag=node[b[temp]].flag; 
else
node[b[temp]].flag=node[a[temp]].flag;
}
 
//這裏就是和上面的全部"歸爲一個點 "呼應了。改掉一個點並不夠
//還要將和他一樣的點全部改掉 
//找大的其實沒有必要就是爲了好看 

if (node[a[temp]].flag!=node[b[temp]].flag&&node[b[temp]].flag!=0&&(node[a[temp]].flag!=0))
{
if (node[a[temp]].flag>node[b[temp]].flag)
   {
    temp1=node[b[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[a[temp]].flag;
}
}
   
if (node[a[temp]].flag<node[b[temp]].flag)
   {
    temp1=node[a[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[b[temp]].flag;
}
}
}
temp=99999;//temp重新置無窮 
}

return 0;

}

好了這樣我們就有了一個傻瓜式的最小生成樹

這樣代入到題目裏面去

#include<stdio.h>
int a[100005];//宏定義,要是在int main裏沒辦法開 
int b[100005];
int c[100005];
int c2[100005];
int l[10005];
int g[100005];//奶牛談話時間 
struct Node//定義一個節點,包含他的指向和指向點所對應的距離和一個flag變量 
{
int zhi;
int ju;
int flag=0;
};
Node node[10005];
int main()
{
int n,p;//n點p線
scanf("%d%d",&n,&p);
//FILE *fpRead=fopen("D:\\c.txt","r");  這裏是爲了方便debug做的文件輸入可以採用 
    int i,j;
    //for( i=1;i<=p;i++)  
    //{  
    //    fscanf(fpRead,"%d%d%d ",&a[i],&b[i],&c[i]);   同上 
    //}  
int temp,temp1;
int k=0;
for (i=1;i<=n;i++)
{
scanf("%d",&g[i]);
}
for (i=1;i<=p;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c2[i]);
}
for (i=1;i<=p;i++)
{
c[i]=2*c2[i]+g[a[i]]+g[b[i]];//加權賦值 
}
int min;
int sum=0;
int t=99999;
for (i=1;i<=n;i++)
{
if(t>g[i])
t=g[i];//找到最小的那個點去睡覺 
}
for (i=1;i<=n-1;i++)
{
min=99999;//每次找最小邊的時候先將最小邊定義爲一個很大的值 
for(j=1;j<=p;j++)
{
if (c[j]<min&&(node[a[j]].flag!=node[b[j]].flag||node[a[j]].flag==0||node[b[j]].flag==0))
//flag初始都爲0,表示沒有任何的連線,一旦flag不是0意味着有連線
//那麼,既然要避免相連的再次入選,就不能讓非零相等項進入判別 
{
min=c[j];
temp=j;
}
}
   sum+=c[temp];

//flag全爲0,直接賦值就可以了。爲了不讓k重複,就要讓他不斷增加 
if (node[a[temp]].flag==node[b[temp]].flag&&node[b[temp]].flag==0)
{
node[a[temp]].flag=++k;
node[b[temp]].flag=node[a[temp]].flag;
}

//一個爲0,將另一個爲0的替換 
if (node[a[temp]].flag!=node[b[temp]].flag&&(node[a[temp]].flag==0||(node[b[temp]].flag==0)))
{
if (node[a[temp]].flag==0)
node[a[temp]].flag=node[b[temp]].flag; 
else
node[b[temp]].flag=node[a[temp]].flag;
}
 
//這裏就是和上面的全部"歸爲一個點 "呼應了。改掉一個點並不夠
//還要將和他一樣的點全部改掉 
//找大的其實沒有必要就是爲了好看 

if (node[a[temp]].flag!=node[b[temp]].flag&&node[b[temp]].flag!=0&&(node[a[temp]].flag!=0))
{
if (node[a[temp]].flag>node[b[temp]].flag)
   {
    temp1=node[b[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[a[temp]].flag;
}
}
   
if (node[a[temp]].flag<node[b[temp]].flag)
   {
    temp1=node[a[temp]].flag;
for (j=1;j<=n;j++)
    if (node[j].flag==temp1)
    {
    node[j].flag=node[b[temp]].flag;
}
}
}
temp=99999;//temp重新置無窮 
}



printf("%d",sum+t);
return 0;

}

我覺得還是學學網上的官方算法比較好。

但是我畢竟要比賽了,一堆算法我也記不住

還不如自己寫個能拿60分的

大家不必要關心我的代碼,但是這個學習方法還是值得留意的哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章