Solution -「ABC 217」题解

D - Cutting Woods

记录每一个切割点,每次求前驱后驱就好了,注意简单判断一下开闭区间。

考场上采用的 FHQ_Treap 无脑莽。

#include <cstdio>
#include <cstdlib>
using namespace std;
 
typedef long long LL;
LL Max(LL x, LL y) { return x > y ? x : y; }
LL Min(LL x, LL y) { return x < y ? x : y; }
LL Abs(LL x) { return x < 0 ? -x : x; }
 
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;
}
 
LL read_LL() {
    int k = 1;
	LL 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(LL x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
 
void print(LL x, char s) {
    write(x);
    putchar(s);
}
 
// Fhq-Treap
const int MAXN = 2e5 + 5;
 
struct Fhq_Treap {
    struct Fhq_Node {
        int l, r, val, key, size;
#define lson tr[p].l
#define rson tr[p].r
        Fhq_Node() {}
        Fhq_Node(int L, int R, int Val, int Key, int Size) {
            l = L;
            r = R;
            val = Val;
            key = Key;
            size = Size;
        }
    } tr[MAXN];
    int tot, root;
 
    Fhq_Treap() {
        tot = 0, root = 0;
    }
 
    int Get(int val) {
        tr[++tot] = Fhq_Node(0, 0, val, rand(), 1);
        return tot;
    }
 
    void Push_Up(int p) { tr[p].size = tr[lson].size + tr[rson].size + 1; }
 
    void Split(int p, int val, int &x, int &y) {
        if (!p) {
            x = 0, y = 0;
            return;
        }
        if (tr[p].val <= val) {
            x = p;
            Split(rson, val, rson, y);
        } else {
            y = p;
            Split(lson, val, x, lson);
        }
        Push_Up(p);
    }
 
    int Merge(int x, int y) {
        if (!x || !y)
            return x + y;
        if (tr[x].key <= tr[y].key) {
            tr[x].r = Merge(tr[x].r, y);
            Push_Up(x);
            return x;
        } else {
            tr[y].l = Merge(x, tr[y].l);
            Push_Up(y);
            return y;
        }
    }
 
    void Insert(int val) {
        int x, y;
        Split(root, val, x, y);
        root = Merge(Merge(x, Get(val)), y);
    }
 
    void Delete(int val) {
        int x, y, z;
        Split(root, val, x, z);
        Split(x, val - 1, x, y);
        y = Merge(tr[y].l, tr[y].r);
        root = Merge(Merge(x, y), z);
    }
 
    int Get_Rank(int val) {
        int x, y, ret;
        Split(root, val - 1, x, y);
        ret = tr[x].size + 1;
        root = Merge(x, y);
        return ret;
    }
 
    int Get_Val(int Rank) {
        int p = root;
        while (p) {
            if (tr[lson].size + 1 == Rank)
                return tr[p].val;
            else if (Rank <= tr[lson].size)
                p = lson;
            else {
                Rank -= (tr[lson].size + 1);
                p = rson;
            }
        }
        return 0;
    }
 
    int Get_Pre(int val) {
        int x, y, p;
        Split(root, val - 1, x, y);
        p = x;
        while (rson) p = rson;
        int ret = tr[p].val;
        root = Merge(x, y);
        return ret;
    }
 
    int Get_Next(int val) {
        int x, y, p;
        Split(root, val, x, y);
        p = y;
        while (lson) p = lson;
        int ret = tr[p].val;
        root = Merge(x, y);
        return ret;
    }
#undef lson
#undef rson
} tree;
 
int main() {
	int l = read(), q = read();
	tree.Insert(0);
	tree.Insert(l);
	for(int i = 1, opt, x; i <= q; i++) {
		opt = read(), x = read();
		if(opt == 1)
			tree.Insert(x);
		else
			print(tree.Get_Next(x) - tree.Get_Pre(x), '\n');
	}
	return 0;
}

E - Sorting Queries

将所有的插入操作先存进一个临时序列。

在遇到排序操作时再处理这些序列里的元素。

具体来说就是直接丢进优先队列。也可以学我赛时直接 copy D 的 code 来改。

#include <queue>
#include <cstdio>
using namespace std;
 
typedef long long LL;
LL Max(LL x, LL y) { return x > y ? x : y; }
LL Min(LL x, LL y) { return x < y ? x : y; }
LL Abs(LL x) { return x < 0 ? -x : x; }
 
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;
}
 
LL read_LL() {
    int k = 1;
	LL 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(LL x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
 
void print(LL x, char s) {
    write(x);
    putchar(s);
}
 
const int MAXN = 2e5 + 5;
 
int num[MAXN], len = 0, tot = 1;
priority_queue<int, vector<int>, greater<int> > q;
 
int main() {
	int n = read();
	for(int i = 1, opt; i <= n; i++) {
		opt = read();
		if(opt == 1) {
			int x = read();
			num[++len] = x;
		}
		else if(opt == 2) {
			if(!q.empty()) {
				print(q.top(), '\n');
				q.pop();
			}
			else {
				print(num[tot], '\n');
				tot++;
			}
		}
		else {
			for(int i = tot; i <= len; i++)	
				q.push(num[i]);
			tot = 1, len = 0;
		}
	}
	return 0;
}

F - Make Pair

小清新的区间 dp 题。

相邻删除这样的操作就应该往区间 dp 上去想。

定义 \(f(i, j)\) 表示将区间 \([i, j]\) 匹配完的方案数,显然需要满足 \(2 \mid r - l + 1\)

可得:

\[f(i, j) = \sum_{k \in (l, r], 2 | k - i + 1} f(i + 1, k) \times f(k + 1, j) \times \binom{\frac {j - i + 1} {2}}{\frac {j - k + 1} {2}} \]

其中 \(k\) 同学和 \(i\) 同学关系友好,且对于任意 \(i\),若 \(0 \leq i \leq n\),有 \(f(i, i + 1) = 1\)

其中组合数可以理解为我们共有 \(\frac {j - i + 1} {2}\) 个盒子(盒子间被认为是不同的),且我们有 \(\frac {k - i - 1} {2}\)\(a\) 球,\(1\)\(b\) 球,和 \(\frac {j - k} {2}\)\(c\) 球(同种类的球被认为是相同的,这是因为在上面的转移柿子中我们已经考虑了球种类内部的情况),且 \(b\) 球放入的盒子编号必须大于任意一个 \(a\) 球放入的盒子编号。

感性理解一下其实就是我们在所有的盒子中选 \(\frac {j - k} {2}\) 个出来放 \(c\) 球。

时间复杂度 \(O(n^3)\)

#include <cstdio>

typedef long long LL;
int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }
int Abs(int x) { return x < 0 ? -x : x; }

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

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

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

const int MAXN = 4e2 + 5;
const int mod = 998244353;

bool w[MAXN][MAXN]; 
LL q[MAXN], sq[MAXN], dp[MAXN][MAXN];
 
LL A (int x, int y) { return q[x] * sq[x - y] % mod;}
 
LL C (int x, int y) { return A(x, y) * sq[y] % mod;}
 
LL Quick_Pow(LL a, LL b) {
	LL res = 1;
	while(b) {
		if(b & 1)
			res = res * a % mod;
		a = a * a % mod;
		b >>= 1;		
	}
	return res % mod;
}

int main() {
	int n = read(), m = read();
	q[0] = 1, sq[0] = 1;
	for(int i = 1; i <= (n << 1); i++) {
		q[i] = q[i - 1] * i % mod;
		sq[i] = Quick_Pow(q[i], mod - 2);
	}
	for(int i = 1, a, b; i <= m; i++) {
		a = read(), b = read();
		w[a][b] = w[b][a] = true;
	}
	for(int i = 1; i <= (n << 1) + 1; i++)
		dp[i][i - 1] = 1;
	for(int len = 2; len <= (n << 1); len += 2) 
		for(int l = 1; l + len - 1 <= (n << 1); l++) {
			int r = l + len - 1;
			for(int k = l + 1; k <= r; k += 2)
				if(w[l][k])
					dp[l][r] = (dp[l][r] + dp[l + 1][k - 1] * dp[k + 1][r] % mod * C(len / 2, (r - k + 1) / 2) % mod) % mod;
		}
	print(dp[1][n << 1], '\n');
	return 0;
}

G - Groups

还是一道 dp 呢。

我们定义 \(f(i, j)\) 表示分完前 \(i\) 个数且当前分了 \(j\) 个组。

考虑当前数的两种决策。

第一种,新开一个组,即方案数为 \(f(i - 1, j - 1)\)

第二种,不开新的组而是丢进已存在的组,不难发现当前数可以丢入的组共有 \(j - t(i, i \bmod m)\) 个,其中 \(t(x, y) = \sum_{i = 1}^{x} [i \bmod m = y]\)

故可得,\(f(i, j) = f(i - 1, j - 1) + f(i - 1, j) \times (j - t(i, i \bmod m))\)

初始状态为 \(f(0, 0) = 1\)。其他的比如 \(t(x, y)\) 的转移啦、答案状态啦就很简单了嘛,时间复杂度 \(O(n^2)\)

#include <cstdio>

typedef long long LL;
LL Max(LL x, LL y) { return x > y ? x : y; }
LL Min(LL x, LL y) { return x < y ? x : y; }
LL Abs(LL x) { return x < 0 ? -x : x; }

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

LL read_LL() {
    int k = 1;
	LL 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(LL x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

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

const int MAXN = 5e3 + 5;
const int mod = 998244353; 

int t[MAXN];
LL dp[MAXN][MAXN];

int main() {
	int n = read(), m = read();
	dp[0][0] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) 
			dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j] * ((j - t[i % m] + mod) % mod) % mod) % mod;	
		t[i % m]++;	
	}
	for(int i = 1; i <= n; i++)
		print(dp[n][i], '\n');
	return 0;
}

H - Snuketoon

不会了不会了不会了 /fad。

posted @ 2021-09-06 15:36  STrAduts  阅读(59)  评论(0编辑  收藏  举报