1. 程式人生 > >[題解]NOIP2018 Day2 Solution - by xyz32768

[題解]NOIP2018 Day2 Solution - by xyz32768

Z natizen gala ovasug narrin qhe ba zbik
Quatorm arrticive nafjenvt uane hak cfnik
Wafhte unitalize napivet uavidnafk fhatox
Orz pyz ak ioi
(火星文)
——《Ydjadf fha de NOIP 2018》

Day2 T1 旅行 travel

演算法:模擬

  • 一棵樹的情況,直接從 1 1
    開始 DFS ,為了保證字典序最小,遞迴時需要按標號從小到大列舉子節點
  • 複雜度 O ( n ) O(n)
  • 基環樹的情況,列舉環上一條邊刪掉,再執行上述過程
  • 複雜度 O ( n 2 ) O(n^2)
  • 注意子節點的編號大小順序需要預處理
  • 否則複雜度多個 log \log

程式碼

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 5005, M = N << 1;

int n, m, ecnt, nxt[M], adj[N], st[M], go[M], a[N], T, ans[N], lins[N][N], X, Y;
bool vis[N], adiao;

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; st[ecnt] = v; go[ecnt] = u;
}

void dfs(int u, int fu)
{
	int i;
	a[++T] = u;
	For (i, 1, lins[u][0])
	{
		if (lins[u][i] == fu) continue;
		dfs(lins[u][i], u);
	}
}

void dfs2(int u, int fu)
{
	int i;
	a[++T] = u;
	vis[u] = 1;
	For (i, 1, lins[u][0])
	{
		if (lins[u][i] == fu || (u == X && lins[u][i] == Y)
			|| (u == Y && lins[u][i] == X)) continue;
		if (!vis[lins[u][i]]) dfs2(lins[u][i], u);
		else adiao = 1;
	}
}

bool compares()
{
	int i;
	For (i, 1, n)
	{
		if (a[i] < ans[i]) return 1;
		if (a[i] > ans[i]) return 0;
	}
	return 0;
}

int main()
{
	int i, j, x, y;
	n = read(); m = read();
	For (i, 1, m) x = read(), y = read(),
		add_edge(x, y);
	For (i, 1, n) Edge(i) lins[i][++lins[i][0]] = v;
	For (i, 1, n) std::sort(lins[i] + 1, lins[i] + lins[i][0] + 1);
	if (m == n - 1)
	{
		dfs(1, 0);
		For (i, 1, n) printf("%d ", a[i]);
		std::cout << std::endl;
		return 0;
	}
	For (i, 1, n) ans[i] = n + 1;
	For (i, 1, m)
	{
		T = 0; adiao = 0;
		X = st[i << 1], Y = go[i << 1];
		For (j, 1, n) vis[j] = 0;
		dfs2(1, 0);
		if (!adiao && T == n && compares())
		{
			For (j, 1, n) ans[j] = a[j];
		}
	}
	For (i, 1, n) printf("%d ", ans[i]);
	std::cout << std::endl;
	return 0;
}

Day2 T2 填數遊戲 game

演算法:狀壓 DP + 找規律

  • 先思考一下什麼樣的 01 01 矩陣滿足條件
  • 先把矩陣畫成對角線的形式,如下圖
    在這裡插入圖片描述
  • 上圖中同色的方塊在一個對角線上
  • 瞎 jb 推一波,可以得出一個合法的 01 01 矩陣需要同時滿足兩個條件:
  • (1)同一個對角線要麼全 0 0 ,要麼全 1 1 ,要麼下面一段全 1 1 而上面一段全 0 0
  • (2)對於任意一個 1 &lt; i n , 1 j &lt; m 1&lt;i\le n,1\le j&lt;m ,如果 ( i , j ) (i,j) ( i 1 , j + 1 ) (i-1,j+1) 填的數相同,那麼 ( i , j + 1 ) (i,j+1) 及右下角的所有方塊中,一條斜線上的所有數需要全相等,如圖
    在這裡插入圖片描述
  • 證明由讀者自行撕烤

80pts

  • 我們有了一個 80 80 分的狀壓 DP
  • f [ i ] [ j ] [ S ] f[i][j][S] 表示從下(右)往左(上)前 i i 個對角線,第 i i 個對角線放了 j j 1 1 (由第一點性質得到, j j 唯一確定了第 i i 個對角線的方案), S S 是一個集合,如果第 i i 個對角線第 k k 個格子及右下角的子矩陣每個對角線都滿足其中填的數全相等則 S S 中包含 k k ,否則 S S 不包含 k k
  • 大力列舉 k k ,進行轉移 f [ i 1 ] [ j ] [ S ] f [ i ] [ k ] [ T ] f[i-1][j][S]\rightarrow f[i][k][T]
  • T T 可以由 j , S , k j,S,k 計算出
  • 注意需要判斷第二點性質是否滿足
  • 使用滾動陣列優化空間
  • 理論複雜度 O ( 2 n m n 3 ) O(2^nmn^3) (計算 T T 需要 O ( n ) O(n) 的時間)
  • 但實際上很多轉移都不滿足第二點性質,可以通過 80 % 80\% 的資料

100pts