1. 程式人生 > 實用技巧 >2020.09.09——java型別轉換

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;
}