2020.09.09——java型別轉換
P2607 [ZJOI2008]騎士
題目描述
Z 國的騎士團是一個很有勢力的組織,幫會中匯聚了來自各地的精英。他們劫富濟貧,懲惡揚善,受到社會各界的讚揚。
最近發生了一件可怕的事情,邪惡的 Y 國發動了一場針對 Z 國的侵略戰爭。戰火綿延五百里,在和平環境中安逸了數百年的 Z 國又怎能抵擋的住 Y 國的軍隊。於是人們把所有的希望都寄託在了騎士團的身上,就像期待有一個真龍天子的降生,帶領正義打敗邪惡。
騎士團是肯定具有打敗邪惡勢力的能力的,但是騎士們互相之間往往有一些矛盾。每個騎士都有且僅有一個自己最厭惡的騎士(當然不是他自己),他是絕對不會與自己最厭惡的人一同出征的。
戰火綿延,人民生靈塗炭,組織起一個騎士軍團加入戰鬥刻不容緩!國王交給了你一個艱鉅的任務,從所有的騎士中選出一個騎士軍團,使得軍團內沒有矛盾的兩人(不存在一個騎士與他最痛恨的人一同被選入騎士軍團的情況),並且,使得這支騎士軍團最具有戰鬥力。
為了描述戰鬥力,我們將騎士按照 \(1\) 至 \(n\) 編號,給每名騎士一個戰鬥力的估計,一個軍團的戰鬥力為所有騎士的戰鬥力總和。
輸入格式
第一行包含一個整數 \(n\),描述騎士團的人數。
接下來 \(n\) 行,每行兩個整數,按順序描述每一名騎士的戰鬥力和他最痛恨的騎士。
輸出格式
應輸出一行,包含一個整數,表示你所選出的騎士軍團的戰鬥力。
輸入輸出樣例
輸入 #1
3
10 2
20 3
30 1
輸出 #1
30
說明/提示
資料規模與約定
對於 30% 的測試資料,滿足\(n \le 10\);
對於 60% 的測試資料,滿足 \(n \le 100\);
對於 80% 的測試資料,滿足\(n \le 10 ^4\)
對於 100% 的測試資料,滿足 \(1\le n \le 10^6\),每名騎士的戰鬥力都是不大於 \(10^6\) 的正整數。
首先,我們對於互相憎恨的騎士連一條雙向邊,然後我們就可以求全圖的最大獨立子集(只相鄰的點不能同時被選)。
就轉化為了類似於上司的舞會那道題。
就有轉移 \(f[x][0] += max(f[to][0],f[to][1])\), \(f[x][1] += f[to][0]\)
\(f[x][0/1]\) 表示 在以 \(x\) 為根的子樹中選或不選 \(x\) 節點得到的最大權值。
可這可能會出現環的情況,變成基環樹或者基環樹森林。
這,我們還是按照套路,找出每個環,然後斷掉環上的一條邊,在對整顆樹做一遍樹形\(dp\)
一個比較好的優化就是 我們只需要列舉斷掉的環上的兩個端點,在對整棵樹跑一邊 \(dp\).
而不至於對每條邊的端點都跑一遍。
因為當你這個點的狀態確定的話,後面所有點的狀態都會是確定的(可以感性理解一下)。
最後再說一下比較坑的一個點:
-
找到環之後要 continue 而不是直接return ,因為你可能兩個騎士互相憎恨,這兩個點就成了一個環,
-
然後又連線了其他的點,這樣你就會 \(MLE\)
具體圖例長這樣:
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int N = 1e6+10;
int n,m,tot = 1,x,st,en,id;
int head[N],w[N];
LL f[N][2],ans,maxn;
bool vis[N];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
struct node
{
int to,net;
}e[N<<1];
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void find(int x,int from)//from 指從他來的編號
{
vis[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if((i ^ 1) == from) continue;//他不能是連向他父親的邊
if(vis[to])
{
st = x;//記錄一下刪除的邊的編號以及這條邊的左右端點
en = to;
id = i;
continue;//要把整棵樹都搜完
// return;
}
else find(to,i);
}
}
void dp(int x,int from)//求最大獨立集
{
f[x][1] = w[x]; f[x][0] = 0;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if((i ^ 1) == from || (i == (id ^ 1)) || i == id) continue;//他不可以是被刪除的邊,或者是返祖邊
dp(to,i);
f[x][1] += f[to][0];
f[x][0] += max(f[to][1],f[to][0]);
}
}
int main()
{
n = read();
for(int i = 1; i <= n; i++)
{
w[i] = read(); x = read();
add(x,i); add(i,x);
}
for(int i = 1; i <= n; i++)
{
if(vis[i]) continue;//可能是基環樹森林,要對每棵樹都跑一邊dp
// printf("-------->\n");
find(i,0); maxn = 0;//找環
// cout<<st<<" "<<en<<" "<<id<<endl;
dp(st,0); //列舉兩個端點的狀態
maxn = max(maxn,f[st][0]);
dp(en,0);
maxn = max(maxn,f[en][0]);
ans += maxn;//加上每棵樹的答案
}
printf("%lld\n",ans);
return 0;
}