2020.11.25 選拔賽題解

訓練賽地址c++

A (最短路變形)

⭐⭐算法

題意:給出一張圖,圖中的節點分爲黑點白點,求出黑點對之間的最短距離並輸出這兩個黑點的編號安全

解析:網絡

  1. 經過題目中關鍵字最短距離,大致能夠想到爲求最短路,可是這裏是把全部的點分爲了兩類,求黑點之間的最短距離。
  2. 考慮到dijkstra算法能夠算出單源最短路,可是若是在初始化優先隊列時,將同類型的點一併push入隊列中,即可以獲得多源最短路(本題中爲各點到黑點的最近距離),這樣處理的複雜度爲\(O((m+n)\log n)\)
  3. 對於一段從黑點到黑點的路徑,能夠轉化爲下述這樣的狀況

\[\underbrace{\overbrace{1\cdots0}^{Path_1}\ \overbrace{0\cdots1}^{Path_2}}_{k\in\left[0,n-2\right]} \]

那麼假設一個點爲\(a\),另外一個點爲\(b\),如若這兩個點的最近黑點不是一個點,那麼就考慮全部這樣的\(<a,b>\)組合,所組成路徑的最小值即爲答案,即複雜度爲暴力枚舉全部邊\(O(m)\)app

注意:\(pre\)必定要是黑點才行,因此一開始定義全部的\(pre\)都是-1,遇到是-1的點直接跳過他的邊枚舉優化

總結:spa

\[result=\min(d[i]+d[j]+dis(i,j))\{i,j\mid pre[i]\ne pre[j]\wedge \text{previous point is black}\} \]

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;

const long long INF = 0x3f3f3f3f3f3f3f3fll;
const int maxn = 1e5 + 10;
typedef pair <long long, int> P;
int n, m;
int cnt, sign[maxn];;
bool vis[maxn];
vector<P> e[maxn];
vector<int> pre;
vector<long long> d;
priority_queue<P, vector<P>, greater<P>> q;


void dij()
{
	pre.assign(n + 1, -1), d.assign(n + 1, INF);
	for (int i = 1; i <= n; ++i)
		if (sign[i])
			pre[i] = i, d[i] = 0, q.push(P(d[i], i));
	while (!q.empty())
	{
		P t = q.top(); q.pop();
		if (d[t.second] < t.first) continue;
		for (auto& i : e[t.second])
		{
			if (d[i.second] > d[t.second] + i.first)
			{
				d[i.second] = d[t.second] + i.first;
				q.push(P(d[i.second], i.second));
				pre[i.second] = pre[t.second];
			}
		}
	}
}

void add(int u, int v, int c)
{
	e[u].push_back(P(c, v));
	e[v].push_back(P(c, u));
}

int main()
{
	int a, b, c;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		scanf("%d", &sign[i]);
	while (m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}
	dij();
	long long ret = 0x3f3f3f3f3f3f3f3fll;
	for (int i = 1; i <= n; ++i)
	{
		if (pre[i] == -1) continue;
		for (auto& j : e[i])
			if (~pre[j.second] && pre[i] != pre[j.second] && d[i] + d[j.second] + j.first < ret)
				ret = d[i] + d[j.second] + j.first, a = pre[i], b = pre[j.second];
	}
	if (ret >= 0x3f3f3f3f3f3f3f3fll)
		printf("No luck at all");
	else
		printf("%lld\n%d %d", ret, a, b);
}

B (區間dp)

⭐⭐.net

題意:
你有\(n\)個任務須要完成,任務按照時間順序交付到你的手上,你在任意時刻能夠花費\(d\)完成以前全部未完成的任務,可是當每個任務被延遲完成一個單位時間會花費\(c\),請問完成這些任務最少花費多少code

解析:隊列

  1. 定義\(dp[i]\)狀態爲完成第\(i\)個任務所須要花費的最小時間,\(time[i]\)爲第\(i\)個任務的交付時間,不可貴到狀態轉移方程

\[dp[i]=dp[j]+\sum_{k=j+1}^{i-1}(time[i]-time[k])\times c+d \]

  1. 可是若是樸素的去統計求和部分,複雜度會達到\(O(n^3)\),明顯會超時,因此考慮將上述轉移方程進行化簡

\[dp[i]=dp[j]+((i-j-1)\times time[i]\underline{+time[i]-time[i]}-\sum_{k=j+1}^{i-1}time[k])\times c+d= \]

\[=dp[j]+((i-j)\times time[i]-(sum[i]-sum[j]))\times c+d \]

這樣的話求和部分就能夠用前綴和進行\(O(1)\)獲取了

注意:

  1. 不開\(long\ long\)必WA
  2. 第二步的化簡讓公式更簡潔
#include<cstdio>
#include<string>
#include<algorithm>
#define MEM(X,Y) memset(X,Y,sizeof(X))
typedef long long LL;
using namespace std;
/*===========================================*/

LL dp[1005], sum[1005];
LL dat[1005];

int main(void)
{
	int n, d, c;
	scanf("%d%d%d", &n, &d, &c);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld", &dat[i]);
		sum[i] = sum[i - 1] + dat[i];
	}
	MEM(dp, INF);
	dp[0] = 0;
	for (int i = 1; i <= n; ++i)
		for (int j = 0; j < i; ++j)
			dp[i] = min(dp[i], dp[j] + c * ((1LL * i - j) * dat[i] - sum[i] + sum[j]) + d);
	printf("%lld", dp[n]);
}

C (區間dp)

⭐⭐⭐⭐

題意:
\(n\)個整數,定義一個數是有序的當且僅當它左邊的數都小於等於它,它右邊的數都大於等於它,排列這\(n\)個整數能組成多少個序列使得全部數都是無序的(答案模取\(10^9+9\)

解析:

  1. 若是將從\([l,r]\)的數的不重複排列個數記爲\(permu[l][r]\)\(dp[i]\)定義爲從\(1\sim i\)的數據構成無序序列的個數,那麼能夠考慮問題的反面,無序數列的個數,等於這些全部數的全排列減去至少有一個數保持有序性對應的排列數
  2. 爲了避免重不漏的尋找到長度爲\(n\)的序列至少有一個數保持有序性對應的排列數,考慮枚舉第\(i\)個數爲第一個有序的數,須要保證在這個數左邊的部分\([1,i)\)均是小於它的且均爲無序數列,而右邊的部分\((i,n]\)只要是大於\(dat[i]\)便可
  3. 而對於左邊和右邊的相對於\(dat[i]\)的偏序關係,能夠進行\(sort\)來實現
  4. 這樣問題轉化成了如何求出\([l,r]\)數構成的不重複排列個數,很明顯這個值等於

\[permu[l][r]=\sum_{i=l}^r\frac{(r-l+1)!}{c_1!c_2!\cdots c_k!} \]

\(c\)爲區間內每一個數對應的個數,去除選擇前後性帶來的影響
很明顯能夠發現這個式子能夠進行遞推記錄即

\[permu[l][r]=\frac{permu[l][r-1]\times(r-l+1)}{c_{dat[r]}} \]

總結:

\[dp[i]=\sum_{j=1}^idp[j-1]\times permu[j+1][i] \]

注意:

  1. \(permu\)在遞推過程當中要訪問到\(permu[l][l-1]\)(即區間內只有一個數)這個量,因此應該提早賦值爲1
  2. \(dp\)遞推過程當中也要訪問到\(permu[i+1][i]\)這個量(即右端點的那個值爲第一個有序的,也就是右端點是最大的),這與上述第一點的賦值相對應(最高賦值到了\(permu[n][n-1]\)),因此應該再多賦值一個\(permu[n+1][n]=1\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 5e3 + 3, mod = 1e9 + 9;
LL inv[maxn], permu[maxn][maxn];
LL dat[maxn];
LL dp[maxn];
map<int, int> m;

int main()
{
	int n;
	scanf("%d", &n);
	inv[1] = 1;
	for (int i = 2; i <= n; ++i)
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	for (int i = 1; i <= n; ++i)
		scanf("%lld", &dat[i]);
	sort(dat + 1, dat + n + 1);
	for (int i = 1; i <= n; ++i)
	{
		m.clear();
		permu[i][i - 1] = 1;
		for (int j = i; j <= n; ++j)
			permu[i][j] = permu[i][j - 1] * (j - i + 1) % mod * inv[++m[dat[j]]] % mod;
	}
	permu[n + 1][n] = 1;
	dp[0] = 1;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= i; ++j)
			dp[i] = (dp[i] + dp[j - 1] * permu[j + 1][i] % mod) % mod;
		dp[i] = ((permu[1][i] - dp[i]) % mod + mod) % mod;
	}
	printf("%lld", dp[n]);
}

D (網絡流+二分離散化+狀態壓縮)

⭐⭐⭐⭐
題目連接

題意:
\(n\)個城市\(m\)條道路,道路是雙向的,其中有\(s\)個城市是安全點,每一個城市中都有\(p[i]\)居民,每一個安全點可容納的居民爲\(C[i]\),請問全部城市的居民都到達安全點的最小時間是多少,保證有解

解析:

  1. 由題意很明顯能夠看出是一道網絡流,問題須要獲得\(time_{min}\),而這個值顯然能夠經過二分進行獲取,用網絡流判斷最大流是否等於\(sum_{p}\)進行檢驗
  2. 建圖方式能夠採用下述辦法

\[S\rightarrow Cities\rightarrow safe\rightarrow T \]

可是這道題目中輸出流量的城市和最終最大流沒有直接關係,所以能夠將全部流向一樣安全點的城市彙總成一個狀態\(st\),這樣的話點數從\(10^5\)下降到\(2^{10}=1024\)
3. 同時須要進行將城市與狀態相匹配的判斷,這樣就得預先對每一個安全點跑一遍\(dijkstra\),記錄每一個安全點到每一個城市的最小距離,對於每一個城市\(i\),檢測它到每一個安全點的最小距離是否小於當前檢測的\(time\),能夠則將\(i\)歸於狀態\(st\)\(st\)中增長對應的人口數量
4. 爲了下降複雜度,還能夠在二分的範圍上進行優化。能夠將\(dijkstra\)出現的全部可能時間進行記錄後,離散化處理。不一樣時間的種類數最大爲\(10^6\),這樣二分的時間複雜度就從\(\log_210^9\approx50\)下降到\(\log_210^6\approx20\)

注意:

  1. 與流量相關的數據都必需要開\(long\ long\),由於總流量最大值爲\(10^{14}\)

總結:

  1. 先對安全點跑最短路,對後面的連邊檢測作好預處理,同時記錄全部時間,進行離散化處理
  2. 在離散化得到的區間進行二分查找
  3. 對相同狀態的城市進行合併,連邊,跑\(dinic\)

\[S\rightarrow States\rightarrow safe\rightarrow T \]

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define FRE freopen("abc.in", "r", stdin)
#define MEM(X,Y) memset(X,Y,sizeof(X))
typedef long long LL;
using namespace std;
/*===========================================*/

typedef long long LL;
typedef pair<LL, int> P;
LL sum;
int people[100005];
P saf[15];
LL d[15][100005];
LL st[1050];

vector<pair<LL, int>> e[100005];
priority_queue<P, vector<P>, greater<P> > q;

void dij(int x)
{
	int id = saf[x].second;
	MEM(d[x], INF);
	d[x][id] = 0;
	q.push(P(0, id));
	while (!q.empty())
	{
		P p = q.top(); q.pop();
		int v = p.second;
		if (d[x][v] < p.first) continue;
		for (auto& i : e[v])
		{
			if (d[x][i.second] > d[x][v] + i.first)
			{
				d[x][i.second] = d[x][v] + i.first;
				q.push(P(d[x][i.second], i.second));
			}
		}
	}
}

//使用非vector鏈式前向星 
class MAXFLOW
{
public:
	static const int MAXN = 100005;

	struct Edge
	{
		int v, next;
		LL flow;
	} e[MAXN * 50];

	int head[MAXN], edge_num, layer[MAXN], start, end;

	void reload()
	{
		edge_num = 0;
		memset(head, -1, sizeof(head));
		memset(st, 0, sizeof(st));
	}

	void addedge(int u, int v, LL w)
	{
		e[edge_num].v = v;
		e[edge_num].flow = w;
		e[edge_num].next = head[u];
		head[u] = edge_num++;
		e[edge_num].v = u;
		e[edge_num].flow = 0;
		e[edge_num].next = head[v];
		head[v] = edge_num++;
	}

	bool bfs()
	{
		queue<int> Q;
		Q.push(start);
		memset(layer, 0, sizeof(layer));
		layer[start] = 1;
		while (Q.size())
		{
			int u = Q.front();
			Q.pop();

			if (u == end)
				return true;

			for (int j = head[u]; j != -1; j = e[j].next)
			{
				int v = e[j].v;

				if (layer[v] == false && e[j].flow)
				{
					layer[v] = layer[u] + 1;
					Q.push(v);
				}
			}
		}

		return false;
	}
	LL dfs(int u, LL MaxFlow, int End)
	{
		if (u == End)
			return MaxFlow;

		LL uflow = 0;

		for (int j = head[u]; j != -1; j = e[j].next)
		{
			int v = e[j].v;

			if (layer[v] - 1 == layer[u] && e[j].flow)
			{
				LL flow = min(MaxFlow - uflow, e[j].flow);
				flow = dfs(v, flow, End);
				e[j].flow -= flow;
				e[j ^ 1].flow += flow;
				uflow += flow;

				if (uflow == MaxFlow)
					break;
			}
		}
		if (uflow == 0)
			layer[u] = 0;
		return uflow;
	}
	LL dinic()
	{
		LL MaxFlow = 0;

		while (bfs())
			MaxFlow += dfs(start, 0x3f3f3f3f3f3f3f3f, end);
		return MaxFlow;
	}
}sol;

int n, m, s;
vector<LL> tim;

bool check(LL time)
{
	sol.reload();
	sol.start = 0;
	for (int i = 1; i <= n; ++i)
	{
		int t = 0;
		for (int j = 1; j <= s; ++j)
			if (d[j][i] <= time)
				t |= 1 << (j - 1);
		st[t] += people[i];
	}
	int mx = 1 << s;
	for (int i = 1; i < mx; ++i)
	{
		sol.addedge(sol.start, i, st[i]);
		if (!st[i]) continue;
		for (int j = 1; j <= s; ++j)
			if (i & (1 << (j - 1)))
				sol.addedge(i, mx + j, INF);
	}
	sol.end = mx + s + 1;
	for (int i = 1; i <= s; ++i)
		sol.addedge(i + mx, sol.end, saf[i].first);
	return sol.dinic() == sum;
}

int main(void)
{
	int a, b;
	LL c;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i <= n; ++i)
		scanf("%d", &people[i]), sum += people[i];
	while (m--)
	{
		scanf("%d%d%lld", &a, &b, &c);
		e[a].push_back(P(c, b));
		e[b].push_back(P(c, a));
	}
	for (int i = 1; i <= s; ++i)
	{
		scanf("%d%lld", &a, &c);
		saf[i] = P(c, a);
		dij(i);
		for (int j = 1; j <= n; ++j)
			tim.push_back(d[i][j]);
	}
	sort(tim.begin(), tim.end());
	tim.erase(unique(tim.begin(), tim.end()), tim.end());
	int l = 0, r = tim.size() - 1;
	while (l < r)
	{
		int m = l + (r - l) / 2;
		if (check(tim[m])) r = m;
		else l = m + 1;
	}
	printf("%lld", tim[r]);
}
相關文章
相關標籤/搜索