1. 程式人生 > 實用技巧 >2020.12.26 模擬賽 題解

2020.12.26 模擬賽 題解

0x00 前言

一些吐槽。

考得很變態誒,看每道題平均兩秒的時限就知道了。。。

T1 降智了想到優化懶得打。

T2 口胡了假優化,結果和暴力分一樣??

T3 黑題還綁點??

淪為平民了www。


0x01 T1

一 道 好 題。

題目描述不在贅述,Link。這道題抽象概括出模型後反而更復雜))

首先,不難往 \(dp\) 方向去想。

我們定義 \(dp[i][j]\) 表示處理到第 \(i\) 個語句時,第 \(i\) 個語句處在第 \(j\) 個縮排時的總共方案數。

找邊界,你會發現只有 for 語句能貢獻新的縮排,即有多少個 for 語句就最多有多少個縮排,故我們記 for 語句的個數為為 \(m\)

如果第 \(i-1\) 個語句是一個 for 語句,則當前語句一定在上一個語句的下一個縮排。

這很顯然吧,畢竟 for 語句一定不能在整個程式碼的最後一個。這個時候更新就很簡單了。

\(dp[i][j] = dp[i - 1][j - 1] (\forall j, 1 \leq j \leq m)\)

那如果不是 for 語句呢?

我們想一想一個縮排的性質,如果上一個語句可以處於第 \(j\) 個縮排,則當前語句處於 \(i,0 \leq i \leq j\) 都是合法的。

因為如果上一個語句處於第 \(j\) 個縮排,則一定能在上面找到對應的使它合法的 for(當然如果 \(j = 0\)

就找不到),那麼在下面再接一個語句都是沒問題的,相當於我們在我們找到的那個 for 下再接了一個語句。

這樣就很明朗了。

\(dp[i][j] = \sum_{k = j}^m dp[i - 1][k]\)

當然,你會發現這裡其實有個字尾和,那麼每次維護上一個狀態的字尾和就可以實現優化。

注意,這道題玄學卡字首和(或許是卡 long long??),我下來調就是因為這個 \(80pt\) 的。

實現就按以上轉移即可。注意取模。

#include <cstdio>

typedef long long LL;
const int MAXN = 5e3 + 5;
const LL mod = (LL)1e9 + 7;
int a[MAXN];
LL dp[MAXN][MAXN], sum[MAXN];
// 第 i 個數,當前 j 個縮排。 

int main() {
	int n, m = 0;
	scanf ("%d", &n);
	for(int i = 1; i <= n; i++) {
		char st[5];
		scanf ("%s", &st);
		if(st[0] == 's')
			a[i] = 0;
		else 
			a[i] = 1;
		m += a[i];
	}
	m++;
	dp[1][0] = 1;
	for(int i = 2; i <= n; i++) {
		sum[m + 1] = 0;
		for(int j = m; j >= 0; j--)
			sum[j] = (sum[j + 1] % mod + dp[i - 1][j] % mod) % mod;
		if(a[i] == 0) {		
			if(a[i - 1] == 1) {
				for(int j = 1; j <= m; j++)
					dp[i][j] = dp[i - 1][j - 1] % mod;
				continue;
			}				
			for(int j = 0; j <= m; j++)	
				dp[i][j] = sum[j] % mod; 
		}
		else {
			if(a[i - 1] == 1) {
				for(int j = 1; j <= m; j++)
					dp[i][j] = dp[i - 1][j - 1] % mod;
				continue;
			}
			for(int j = 0; j <= m; j++)	
				dp[i][j] = sum[j] % mod;
		}
	} 
	LL ans = 0;
	for(int i = 0; i <= m; i++)
		ans = (ans % mod + dp[n][i] % mod) % mod; 
	printf("%lld\n", ans);
	return 0;
}

0x03 T3

眾所周知,水是黑的。

洛谷上的連結link,原題來自 BZOJ 水壺,OJ 上的題顯然是喵喵喵根據「AnOI 2020」格式改的。。

首先,我們來證明一個性質:

對於一個圖,其中任意兩個點間的路徑我們記為 \(p\)。每個路徑上最大的權值為 \(q\)。則滿足 \(q\) 最小的路徑 \(p_0\) 一定在原圖的最小生成樹(森林)上。

我們設這顆最小生成樹(森林)為 \(T\),兩點間路徑 \(p\) 上的最大值為 \(\max(p)\)。假設 \(p\) 不是 \(T\) 上的路徑,則一定存在一條路徑 \(p_1\) 使得 \(\max(p_1)\)\(\max(p)\) 更小。我們考慮將 \(p_1\) 加入 \(T\)

將這個路徑加入後,一定會在原樹上出現一個環,而這個環上的最大值一定為 \(\max(p)\)

你會驚訝的發現,如果刪掉那條權值為 \(\max(p)\) 的邊,你會得到一顆新的樹,而明顯這才是真正的最小生成樹。

推出矛盾!原性質得證。

那麼我們現在得到了這樣一個性質,抽象概括出來也就是說我們的答案一定在原圖的 最小生成樹(森林) 上,因為樹上兩點路徑唯一,所以答案就是兩點間路徑上的最大邊。這顯然可以 樹上倍增 解決。

最後講講預處理,也就是如何去建圖。很顯然,暴力 BFS 是不可取的。於是我們考慮更高效的預處理方法。

其實還是 BFS,類雙向 BFS。我們首先將所有的城市裝入 BFS 的佇列,然後同時開始進行擴充套件。對一個點我們打兩種標記:\(flag\) 表示這個點是由哪個城市第一次走到的,\(ans\) 表示第一個走到這個點的城市到它的距離。那麼對於當前擴充套件到的節點,它的標記有兩種情況。

假設上一個點為 \(v\),當前點為 \(u\)

  • \(ans_u\) 裡沒有值,也就是說當前是第一次走到,更新 \(flag_u\) 繼續擴充套件即可。
ans[u] = ans[v] + 1;
flag[u] = flag[v];
  • ans 裡有值,也就是說之前有城市擴充套件到了,那麼就可以將當前這個城市和之前擴充套件的城市進行連邊,權值就是上一個點和這個點的 \(ans\) 之和。
Add_Edge(flag[u], flag[v], ans[u] + ans[v]);

然後按正常 BFS 繼續遍歷即可,完結撒花。

(程式碼略顯冗長,僅供參考。

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

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
	write(x);
	putchar(s);
}

int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }
void Swap(int &X, int &Y) {
    int t = X;
    X = Y;
    Y = t;
}

const int MAXN = 2e3 + 5;
const int MAXL = 4e6 + 5;
int mp[MAXN][MAXN], h, w, p;
char s[MAXN];

struct node {
    int x, y;
    node() {}
    node(int X, int Y) {
        x = X;
        y = Y;
    }
};

struct edge {
    int u, v, w;
    edge() {}
    edge(int U, int V, int W) {
        u = U;
        v = V;
        w = W;
    }
} e[MAXL];
int len = 0;

int dx[4] = { 1, -1, 0, 0 };
int dy[4] = { 0, 0, 1, -1 };
int ans[MAXN][MAXN], flag[MAXN][MAXN];
queue<node> q;

void bfs() {
    while (!q.empty()) {
        node now = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int cx = now.x + dx[i];
            int cy = now.y + dy[i];
            if (cx < 1 || cx > h)
                continue;
            if (cy < 1 || cy > w)
                continue;
            if (mp[cx][cy])
                continue;
            if (ans[cx][cy] == -1) {
                ans[cx][cy] = ans[now.x][now.y] + 1;
                flag[cx][cy] = flag[now.x][now.y];
                q.push(node(cx, cy));
            } else if (flag[cx][cy] != flag[now.x][now.y])
                e[++len] = edge(flag[now.x][now.y], flag[cx][cy], ans[cx][cy] + ans[now.x][now.y]);
        }
    }
}

struct data {
    int ma, fa;
    data() {}
    data(int Ma, int Fa) {
        ma = Ma;
        fa = Fa;
    }
} f[MAXL][25];

int fa[MAXL];

bool cmp(edge x, edge y) { return x.w < y.w; }

void Make_Set(int n) {
    for (int i = 1; i <= n; i++) fa[i] = i;
}

int Find_Set(int x) {
    if (fa[x] == x)
        return x;
    return fa[x] = Find_Set(fa[x]);
}

struct graph {
    int v, w;
    graph() {}
    graph(int V, int W) {
        v = V;
        w = W;
    }
};

vector<graph> g[MAXL];

void Add_Edge(int u, int v, int w) {
    g[u].push_back(graph(v, w));
    g[v].push_back(graph(u, w));
}

void kruskal() {
    Make_Set(p);
    sort(e + 1, e + len + 1, cmp);
    for (int i = 1; i <= len; i++) {
        int S = Find_Set(e[i].u), E = Find_Set(e[i].v);
        if (S == E)
            continue;
        Add_Edge(S, E, e[i].w);
        fa[S] = E;
    }
}

int dep[MAXL];

void dfs(int x, int fa) {
    for (int i = 0; i < g[x].size(); i++) {
        int v = g[x][i].v;
        if (v == fa)
            continue;
        dep[v] = dep[x] + 1;
        f[v][0].fa = x;
        f[v][0].ma = g[x][i].w;
        for (int j = 0; j <= 20; j++) {
            f[v][j + 1].fa = f[f[v][j].fa][j].fa;
            f[v][j + 1].ma = Max(f[v][j].ma, f[f[v][j].fa][j].ma);
        }
        dfs(v, x);
    }
}

int lca(int x, int y) {
    if (dep[x] < dep[y])
        Swap(x, y);
    int ans = 0;
    for (int i = 20; i >= 0; i--)
        if (dep[f[x][i].fa] >= dep[y]) {
            ans = Max(ans, f[x][i].ma);
            x = f[x][i].fa;
        }
    if (x == y)
        return ans;
    for (int i = 20; i >= 0; i--)
        if (f[x][i].fa != f[y][i].fa) {
            ans = Max(ans, f[x][i].ma);
            ans = Max(ans, f[y][i].ma);
            x = f[x][i].fa;
            y = f[y][i].fa;
        }
    ans = Max(ans, f[x][0].ma);
    ans = Max(ans, f[y][0].ma);
    return ans;
}

int main() {
//    freopen("04-04.in", "r", stdin);
//    freopen("ans1.out", "w", stdout);
    int Q;
    h = read(), w = read(), p = read(), Q = read();
    for (int i = 1; i <= h; i++) {
        scanf("%s", s + 1);
        for (int j = 1; j <= w; j++)
            if (s[j] == '.')
                mp[i][j] = 0;
            else
                mp[i][j] = 1;
    }
    memset(ans, -1, sizeof ans);
    for (int i = 1; i <= p; i++) {
        int x = read(), y = read();
        flag[x][y] = i;
        ans[x][y] = 0;
        q.push(node(x, y));
    }
    bfs();
    //	printf("%d\n", len);
    kruskal();
    for (int i = 1; i <= p; i++)
        if (fa[i] == i)
            dfs(i, -1);
    for (int i = 1; i <= Q; i++) {
        int x = read(), y = read();
        if (Find_Set(x) != Find_Set(y))
            print(-1, '\n');
        else
            print(lca(x, y), '\n');
    }
    return 0;
}