[赛记] csp-s模拟11 && 多校A层冲刺NOIP2024模拟赛07

玩水 (water) 100pts

一道结论题,考场一眼出,结果认为不对,然后被硬控了2h结果打出了个抽象DP然后过了;

赛后发现,这DP和那个结论是等价的。。。

首先考虑只有两个人怎么做,那么我们只需找出一个位置 $ (i, j) $ 满足 $ a_{i + 1, j} = a_{i, j + 1} $ 即可;

那么三个人呢?

设现在有两个满足上述性质的位置 $ (x_1, y_1) \ (x_2, y_2) $,不难想到这三种情况:

  1. $ x_1 < x_2, y_1 < y_2 $;

  2. $ y_1 = y_2, x_1 = x_2 + 1 $;

  3. $ x_1 = x_2, y_1 = y_2 + 1 $;

反之同理;

那么这三种情况就包含了所有情况,因为这玩意是有一个传递性的(就是三条路组成的字符串都要完全相同);

这DP太抽象,所以代码仅供参考

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int t;
int n, m;
char a[1005][1005];
bool f[1005][1005][5][2];
int main() {
	freopen("water.in", "r", stdin);
	freopen("water.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> t;
	while(t--) {
		cin >> n >> m;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				for (int k = 1; k <= 3; k++) {
					f[i][j][k][0] = false;
					f[i][j][k][1] = false;
				}
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				cin >> a[i][j];
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				f[i][j][1][0] = true;
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				if (i == 1 && j == 1) continue;
				for (int k = 2; k <= 3; k++) {
					f[i][j][k][0] |= (f[i - 1][j][k][0] | f[i][j - 1][k][0]);	
					if (i + 1 <= n && j - 1 >= 1 && a[i][j] == a[i + 1][j - 1]) {
						f[i][j][k][1] |= f[i][j - 1][k - 1][0];
					}
					if (i - 1 >= 1 && j + 1 <= m && a[i][j] == a[i - 1][j + 1]) {
						f[i][j][k][1] |= f[i - 1][j][k - 1][0];
					}
					if (i - 1 >= 1 && j - 1 >= 1) {
						if (a[i - 1][j] == a[i][j - 1]) {
							f[i][j][k][0] |= (f[i - 1][j - 1][k - 1][1] | f[i - 1][j - 1][k - 1][0]);
						}
					}
				}
			}
		}
		if (f[n][m][3][0]) cout << 1 << '\n';
		else cout << 0 << '\n';
	}
	return 0;
}

限速(speed)100pts

可以说是思维题了;

考虑求出原树的最小生成树,那么如果树边都小于等于 $ k $,就直接输出所有边中最靠近 $ k $ 的边与 $ k $ 的差,否则输出所有树边中大于 $ k $ 的边与 $ k $ 的差之和;

考虑这为什么是对的,因为 Kruskal 本质上就是在尽可能多的选小的边,每次找的都是链接两个连通块的最小的边,而小的边越多越好,所以是对的;

从另一个角度说,Kruskal 不对,当且仅当我们判断连通性舍弃边时出现问题,那我们分情况考虑:

  1. 当舍弃的边 $ > k $ 时,不管我们以前的边的大小如何,舍弃这条边都是更优的;

  2. 当 $ < k $ 时,这两个点已经联通说明其至少一条边直接或间接的联通而且呈链状,那么我们删掉这条边自然是不劣的(因为如果选这条边,其不会带来更多的小边);

所以是对的;

时间复杂度:$ \Theta(m \log m) $,瓶颈在排序;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n, m;
long long k;
struct sss{
	int f, t;
	long long w;
}e[500005];
bool cmp(sss x, sss y) {
	return x.w < y.w;
}
bool da, xiao;
int fa[500005];
int find(int x) {
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
long long Kru() {
	long long ans = 0;
	for (int i = 1; i <= n; i++) fa[i] = i;
	int sum = 0;
	for (int i = 1; i <= m; i++) {
		if (sum == n - 1) break;
		int x = find(e[i].f);
		int y = find(e[i].t);
		if (x != y) {
			fa[x] = y;
			ans += e[i].w;
			sum++;
		}
	}
	return ans;
}
int main() {
	freopen("speed.in", "r", stdin);
	freopen("speed.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	cin >> k;
	xiao = da = true;
	for (int i = 1; i <= m; i++) {
		cin >> e[i].f >> e[i].t >> e[i].w;
		if (e[i].w < k) da = false;
		if (e[i].w > k) xiao = false;
	}
	if (xiao) {
		long long ma = 0;
		for (int i = 1; i <= m; i++) {
			ma = max(ma, e[i].w);
		}
		cout << k - ma;
		return 0;
	}
	if (da) {
		sort(e + 1, e + 1 + m, cmp);
		long long now = Kru();
		cout << now - (n - 1) * k;
		return 0;
	}
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= n; i++) fa[i] = i;
	int sum = 0;
	long long mi = 0x3f3f3f3f3f3f3f3f;
	for (int i = 1; i <= m; i++) {
		mi = min(mi, 1ll * abs(k - e[i].w));
	}
	long long ans = 0;
	for (int i = 1; i <= m; i++) {
		if (sum == n - 1) break;
		int x = find(e[i].f);
		int y = find(e[i].t);
		if (x != y) {
			fa[x] = y;
			if (e[i].w > k) ans += (e[i].w - k);
			sum++;
		}
	}
	if (ans == 0) cout << mi;
	else cout << ans;
	return 0;
}

酒鬼 (drunkard)27pts

赛时打了两个特殊性质27pts;

考虑正解,其实是一堆特判?

是的,所以不太想写了

溜了。。。

点击查看代码
#include <iostream>
#include <cstdio>
#include <string>
#include <set>
#include <cmath>
using namespace std;
int n, m;
set<pair<int, int> > s;
int main() {
	freopen("drunkard.in", "r", stdin);
	freopen("drunkard.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	string ss;
	int x, t;
	int ma = 2e9, pos = 2e9, ls = -1;
	bool vis = true;
	for (int i = 1; i <= m; i++) {
		cin >> ss;
		if (ss == "clue") {
			cin >> x >> t;
			if (x - 1 > t) {
				vis = false;
				continue;
			}
			if (x > 1) {
				ma = min(ma, t - (x - 1));
				pos = min(pos, t);
			}
			s.insert({t, x});
			auto ptr = s.find({t, x});
			if (ptr != s.begin() && next(ptr) != s.end() && ls == (next(ptr) -> first)) ls = -1;
			if (ptr != s.begin()) {
				auto pp = prev(ptr);
				if ((t - pp -> first) < abs(x - pp -> second)) {
					vis = false;
					continue;
				}
				if ((pp -> first + pp -> second) % 2 != (x + t) % 2) ls = max(ls, t);
			}
			if (next(ptr) != s.end()) {
				auto pp = next(ptr);
				if ((pp -> first - t) < abs(x - pp -> second)) {
					vis = false;
					continue;
				}
				if ((pp -> first + pp -> second) % 2 != (x + t) % 2) ls = max(ls, pp -> first);
			}
			if (pos < ls) {
				vis = false;
				continue;
			}
		}
		if (ss == "min") {
			if (!vis) {
				cout << "bad" << '\n';
				continue;
			}
			if (ls != -1) {
				auto ptr = s.lower_bound({ls, 0});
				cout << ((prev(ptr) -> first) + 1) << '\n';
			} else {
				if (s.empty()) cout << 0 << '\n';
				else {
					auto ptr = s.begin();
					cout << (ptr -> first + ptr -> second + 1) % 2 << '\n';
				}
			}
		}
		if (ss == "max") {
			if (!vis) {
				cout << "bad" << '\n';
				continue;
			}
			if (ma == 2e9) cout << "inf" << '\n';
			else cout << ma << '\n';
		}
	}
	return 0;
}

距离(distance)40pts

赛时居然没有打出 $ \Theta(n^2) $ 暴力?还是太菜

对于暴力,$ \Theta(n^3) $ 很好打,$ \Theta(n^2) $ 考虑枚举每一对点,计算其贡献并加到其对应的祖先上,这个可以用树上差分解决(实在不行套个树剖也行);

考虑正解,首先如果把 $ \min $ 换成 $ \max $ 的话,那么我们把 $ (a_x, b_x) \ (a_y, b_y) $ 看成两个点,所求即它们两个的切比雪夫距离

所以用 $ \min-\max $ 容斥,即 $ a + b = \min(a, b) + \max(a, b) $,将要求的式子 $ \min(|a_x - a_y|, |b_x - b_y|) $ 换成 $ |a_x - a_y| + |b_x - b_y| - \max(|a_x - a_y|, |b_x - b_y|) $;

发现后面的那个还是不好求,所以考虑将切比雪夫距离转化为曼哈顿距离

设 $ p_i = \frac{a_i + b_i}{2} \ q_i = \frac{a_i - b_i}{2} $,则 $ \max(|a_x - a_y|, |b_x - b_y|) = |p_x - p_y| + |q_x - q_y| $;

证明展开 $ \max(|a_x - a_y|, |b_x - b_y|) $ 中的绝对值即可(或者考虑曼哈顿距离转化为切比雪夫距离,然后逆推即可);

所以原式就转化成了 $ |a_x - a_y| + |b_x - b_y| - |p_x - p_y| + |q_x - q_y| $,那么我们只需处理 $ 4 $ 个形如 $ |a - b| $ 的问题即可;

一种比较容易的想法是合并(毕竟好像只能从下往上合并),有两种,一种启发式合并,一种线段树合并,后一种比较好想,且时间复杂度较优;

那么我们的问题就转化为了如何在值域线段树上进行 push_up 操作;

首先离散化,因为有小数,所以可以都乘 $ 2 $;

考虑左对右的对答案的贡献,可以发现其贡献为 $ cnt_l \times sum_r - cnt_r \times sum_l $,其中 $ cnt $ 代表数出现的次数,$ sum $ 代表数的和;

这是大数减小数,反之同理,要乘 $ 2 $,但因为我们离散化的时候已经乘了,所以就不用乘了(居然形成了闭环!我现在才发现);

所以我们就维护完了;

因为这个题卡空间,所以要分四次做,并且每次要清空线段树,所以常数很大,时间复杂度 $ \Theta(n \log n) $,挂一个 $ 7, 8 $ 的常数;

点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const long long mod = 1e9 + 7;
#define FI(n) FastIO::read(n)
#define FO(n) FastIO::write(n)
#define Flush FastIO::Fflush()
namespace FastIO {
    const int SIZE = 1 << 16;
    char buf[SIZE], obuf[SIZE], str[60];
    int bi = SIZE, bn = SIZE, opt;
    inline int read(register char *s) {
        while(bn) {
            for (; bi < bn && buf[bi] <= ' '; bi = -~bi);
            if (bi < bn) break;
            bn = fread(buf, 1, SIZE, stdin);
            bi &= 0;
        }
        register int sn=0;
        while(bn) {
            for (; bi < bn && buf[bi] > ' '; bi = -~bi) s[sn++] = buf[bi];
            if(bi < bn) break;
            bn = fread(buf,1,SIZE,stdin);
            bi &= 0;
        }
        s[sn] &= 0;
        return sn;
    }
    inline bool read(register int &x){
        int n = read(str), bf = 0;
        if(!n) return 0;
        register int i=0;
        (str[i] == '-') && (bf = 1, i = -~i);
		(str[i] == '+') && (i = -~i);
        for (x = 0; i < n; i = -~i) x = (x << 3) + (x << 1) + (str[i] ^ 48);
        bf && (x = ~x + 1);
        return 1;
    }
    inline bool read(register long long &x) {
        int n = read(str), bf = 1;
        if(!n) return 0;
        register int i=0;
        (str[i] == '-') && (bf = -1,i = -~i);
        for (x = 0; i < n; i= -~i) x = (x << 3) + (x << 1) + (str[i] ^ 48);
        (bf < 0) && (x = ~x + 1);
        return 1;
    }
    inline void write(register int x) {
        if(!x) obuf[opt++] = '0';
        else {
            (x < 0) && (obuf[opt++] = '-', x = ~x + 1);
            register int sn = 0;
            while(x) str[sn++] = x % 10 + '0', x /= 10;
            for (register int i = sn - 1; i >= 0; i = ~-i) obuf[opt++] = str[i];
        }
        (opt >= (SIZE >> 1)) && (fwrite(obuf, 1, opt, stdout), opt &= 0);
    }
    inline void write(register long long x) {
        if(!x) obuf[opt++] = '0';
        else {
            (x < 0) && (obuf[opt++] = '-', x = ~x + 1);
            register int sn = 0;
            while(x) str[sn++] = x % 10 + '0', x /= 10;
            for (register int i = sn - 1; i >= 0; i = ~-i) obuf[opt++] = str[i];
        }
        (opt >= (SIZE >> 1)) && (fwrite(obuf, 1, opt, stdout), opt &= 0);
    }
    inline void write(register unsigned long long x){
        if(!x) obuf[opt++] = '0';
        else {
            register int sn=0;
            while(x) str[sn++] = x % 10 + '0', x /= 10;
            for (register int i = sn - 1 ; i >= 0 ; i = ~-i)obuf[opt++] = str[i];
        }
        (opt >= (SIZE >> 1)) && (fwrite(obuf, 1, opt, stdout), opt &= 0);
    }
    inline void write(register char x) {
        obuf[opt++] = x;
        (opt >= (SIZE >> 1)) && (fwrite(obuf, 1, opt, stdout), opt &= 0);
    }
    inline void Fflush(){
        opt && fwrite(obuf, 1, opt, stdout);
        opt &= 0;
    }
}
int n;
struct sss{
	int t, ne;
}e[2000005];
int h[2000005], cnt;
void add(int u, int v) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
long long a[500005], b[500005], c[2000005], ans[500005];
int len;
int rt[500005], tot;
namespace SEG{
	struct sss{
		int ls, rs;
		int cnt;
		long long sum, ans;
	}tr[15000005];
	inline void push_up(int id) {
		tr[id].ans = (tr[tr[id].ls].ans + tr[tr[id].rs].ans) % mod;
		tr[id].ans = (tr[id].ans + (tr[tr[id].ls].cnt * tr[tr[id].rs].sum % mod - tr[tr[id].rs].cnt * tr[tr[id].ls].sum % mod + mod) % mod) % mod;
		tr[id].cnt = (tr[tr[id].ls].cnt + tr[tr[id].rs].cnt) % mod;
		tr[id].sum = (tr[tr[id].ls].sum + tr[tr[id].rs].sum) % mod;
	}
	void add(int &id, int l, int r, int pos, long long val) {
		if (!id) id = ++tot;
		if (l == r) {
			tr[id].cnt++;
			tr[id].cnt %= mod;
			tr[id].sum += val;
			tr[id].sum %= mod;
			return;
		}
		int mid = (l + r) >> 1;
		if (pos <= mid) add(tr[id].ls, l, mid, pos, val);
		else add(tr[id].rs, mid + 1, r, pos, val);
		push_up(id);
	}
	int merge(int x, int y, int l, int r) {
		if (!x || !y) return x + y;
		if (l == r) {
			tr[x].ans += tr[y].ans;
			tr[x].ans %= mod;
			tr[x].sum += tr[y].sum;
			tr[x].sum %= mod;
			tr[x].cnt += tr[y].cnt;
			tr[x].cnt %= mod;
			return x;
		}
		int mid = (l + r) >> 1;
		tr[x].ls = merge(tr[x].ls, tr[y].ls, l, mid);
		tr[x].rs = merge(tr[x].rs, tr[y].rs, mid + 1, r);
		push_up(x);
		return x;
	}
}
void dfs(int x, int fa, int s) {
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == fa) continue;
		dfs(u, x, s);
		rt[x] = SEG::merge(rt[x], rt[u], 1, len);
	}
	if (s == 1) SEG::add(rt[x], 1, len, lower_bound(c + 1, c + 1 + len, 2 * a[x]) - c, 2 * a[x]);
	if (s == 2) SEG::add(rt[x], 1, len, lower_bound(c + 1, c + 1 + len, 2 * b[x]) - c, 2 * b[x]);
	if (s == 3) SEG::add(rt[x], 1, len, lower_bound(c + 1, c + 1 + len, a[x] + b[x]) - c, a[x] + b[x]);
	if (s == 4) SEG::add(rt[x], 1, len, lower_bound(c + 1, c + 1 + len, a[x] - b[x]) - c, a[x] - b[x]);
	if (s <= 2) ans[x] = (ans[x] + SEG::tr[rt[x]].ans) % mod;
	else ans[x] = (ans[x] - SEG::tr[rt[x]].ans + mod) % mod;
}
int main() {
	freopen("distance.in", "r", stdin);
	freopen("distance.out", "w", stdout);
	FI(n);
	int x, y;
	for (int i = 1; i <= n - 1; i++) {
		FI(x);
		FI(y);
		add(x, y);
		add(y, x);
	}
	for (int i = 1; i <= n; i++) {
		FI(a[i]);
		FI(b[i]);
		c[++len] = 2 * a[i];
		c[++len] = 2 * b[i];
		c[++len] = a[i] + b[i];
		c[++len] = a[i] - b[i]; 
	}
	sort(c + 1, c + 1 + len);
	len = unique(c + 1, c + 1 + len) - c - 1;
	for (int i = 1; i <= 4; i++) {
		dfs(1, 0, i);
		for (int j = 1; j <= n; j++) rt[j] = 0;
		for (int j = 1; j <= tot; j++) {
			SEG::tr[j].ls = SEG::tr[j].rs = SEG::tr[j].cnt = SEG::tr[j].sum = SEG::tr[j].ans = 0;
		}
		tot = 0;
	}
	for (int i = 1; i <= n; i++) {
		FO(ans[i]);
		FO('\n');
	}
	Flush;
	return 0;
}
posted @ 2024-10-16 18:02  Peppa_Even_Pig  阅读(27)  评论(0编辑  收藏  举报