2022NOIPA层联测7

\(accoder\) 用数据告诉我们,找女朋友是个假命题

找(a)

简单推一下柿子,维护总和和平方和

code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 200005;
const int mod = 998244353;
int x[maxn], y[maxn];
int n;
int main(){
	n = read();
	for(int i = 1; i <= n; ++i)x[i] = read(), y[i] = read();
	int sx = 0, sy = 0, sxx = 0, syy = 0;
	for(int i = 1; i <= n; ++i)sx = (sx + x[i]) % mod;
	for(int i = 1; i <= n; ++i)sy = (sy + y[i]) % mod;
	for(int i = 1; i <= n; ++i)sxx = (sxx + 1ll * x[i] * x[i] % mod) % mod;
	for(int i = 1; i <= n; ++i)syy = (syy + 1ll * y[i] * y[i] % mod) % mod;
	for(int i = 1; i <= n; ++i){
		int ax = (1ll * x[i] * x[i] % mod * n % mod + sxx - 2ll * x[i] * sx % mod + mod) % mod;
		int ay = (1ll * y[i] * y[i] % mod * n % mod + syy - 2ll * y[i] * sy % mod + mod) % mod;
		printf("%d\n",(ax + ay) % mod);
	}
	return 0;
}

女(b)

赛时暴跳父亲切了。。。。。。

比较无语

最后 \(20min\) 想出来了 \(nlog^3n\) 的做法,但是调不出来。。。。

\(chino\) 大佬简单讲了一下思路。他切了,\(chino\) 太巨了

顺便帮我调出来了,,,

简单来说,在每个点维护其到其子树内黑点的最短距离 \(dist\)

那么每次查询在他的祖先链上查询 \(min(dist + dep_x - dep_f)\) 即可

本质上是在 \(lca\) 处统计贡献

考虑如何维护,

链上信息- >树剖

区间修改查询- > 线段树

但是直接取 \(min\) 无法快速消除一个点的影响,于是考虑维护所有点

具体的,在线段树每个节点维护一个 \(multiset\) 表示在当前整个区间都能贡献的黑点深度,取区间最大深度向上贡献 \(val\)

查询时,整区间直接返回 \(val\), 非整区间维护的最小深度,与查询深度最大的进行计算

每次操作到根,轻重链有 \(log\) 个, 每次线段树区间修改会最多 \(log\) 个区间,每个区间对 \(multiset\) 操作一个 \(log\)

所以总复杂度 \(qlong^3 n\)

题解做法是建出来点分树,然后暴力跳父亲

code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 100005;
const int inf = 0x3f3f3f3f;
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
int n, q;
int f[maxn], dep[maxn], cnt;
bool col[maxn];
int son[maxn], top[maxn], dfn[maxn], size[maxn];
void dfs1(int x){
	size[x] = 1;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == f[x])continue;
		f[v] = x; dep[v] = dep[x] + 1;
		dfs1(v);
		size[x] += size[v];
		son[x] = size[son[x]] > size[v] ? son[x] : v;
	}
}
int tim, id[maxn];
void dfs2(int x, int tp){
	dfn[x] = ++tim; id[tim] = x;
	top[x] = tp;
	if(son[x])dfs2(son[x], tp);
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == f[x] || v == son[x])continue;
		dfs2(v, v);
	}
}
struct seg{
	struct node{
		multiset<int>s;
		int fd, val;
	}t[maxn << 2 | 1];
	void push_up(int x){
		t[x].fd = min(t[x << 1].fd, t[x << 1 | 1].fd);
		t[x].val = min(t[x << 1].val, t[x << 1 | 1].val);
		if(t[x].s.size())t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
	}
	void built(int x, int l, int r){
		if(l == r){
			t[x].val = inf;
			t[x].fd = -dep[id[l]];
			return;
		}
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
		push_up(x);
	}
	void modify(int x, int l, int r, int L, int R, int val){
		if(L <= l && r <= R){
			t[x].s.insert(val);
			t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
			return;
		}
		int mid = (l + r) >> 1;
		if(L <= mid)modify(x << 1, l, mid, L, R, val);
		if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
		push_up(x);
	}
	void del(int x, int l, int r, int L, int R, int val){
		if(L <= l && r <= R){
			t[x].s.erase(t[x].s.lower_bound(val));
			t[x].val = inf;
			if(l != r)push_up(x);
			else if(t[x].s.size())t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
			return;
		}
		int mid = (l + r) >> 1;
		if(L <= mid)del(x << 1, l, mid, L, R, val);
		if(R > mid)del(x << 1 | 1, mid + 1, r, L, R, val);
		push_up(x);
	}
	int query(int x, int l, int r, int L, int R, int mx){
		if(L <= l && r <= R){
			return t[x].val;
		}
		int mid = (l + r) >> 1, ans = inf;
		if(t[x].s.size()) ans = *(t[x].s.begin()) - mx - mx;
		if(L <= mid)ans = min(ans, query(x << 1, l, mid, L, R, mx));
		if(R > mid)ans = min(ans, query(x << 1 | 1, mid + 1, r, L, R, mx));
		return ans;
	}
}t;

int num;
void add(int x){
	++num;
	int u = x;
	while(u){
		t.modify(1, 1, n, dfn[top[u]], dfn[u], dep[x]);
		u = f[top[u]];
	}
}
void del(int x){
	--num;
	int u = x;
	while(u){
		t.del(1, 1, n, dfn[top[u]], dfn[u], dep[x]);
		u = f[top[u]];
	}
}
int query(int x){
	if(num == 0)return -1;
	int u = x, ans = inf;
	while(u){
		ans = min(ans, t.query(1, 1, n, dfn[top[u]], dfn[u], dep[u]) + dep[x]);
		u = f[top[u]];
	}
	return ans;
}
void rev(int x){
	if(col[x]){del(x); col[x] = 0;}
	else{ add(x);col[x] = 1;}
}
int main(){
	n = read(), q = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	dep[1] = 1; dfs1(1); dfs2(1, 1);
	t.built(1, 1, n);
	rev(1);
	for(int i = 1; i <= q; ++i){
		int opt = read(), x = read();
		if(opt & 1)rev(x);
		else printf("%d\n",query(x));
	}
	return 0;
}

朋(c)

不会,弃了,数据水,判断必要条件能过

题解: 给每条边一个随机边权,然后FMT求行列式即可

假做法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 50005;
const int inf = 0x3f3f3f3f;
int n, m;
struct EDG{int u, v, w;}l[maxn];
struct wwl{
	int head[maxn], tot = 1;
	struct edge{int to, net, val;}e[maxn << 1 | 1];
	void add(int u, int v, int w){
		e[++tot].net = head[u];
		head[u] = tot;
		e[tot].to = v;
		e[tot].val = w;
	}
	void link(int u, int v, int w){
		add(u, v, w); add(v, u, 0);
	}
	int dep[maxn], now[maxn];
	int s, t;
	bool bfs(){
		for(int i = 1; i <= n + n + 5; ++i)dep[i] = 0;
		queue<int>q; now[s] = head[s]; dep[s] = 1;
		q.push(s);
		while(!q.empty()){
			int x = q.front(); q.pop();
			if(x == t)return true;
			for(int i = head[x]; i; i = e[i].net){
				int v = e[i].to;
				if(e[i].val > 0 && !dep[v]){
					dep[v] = dep[x] + 1;
					now[v] = head[v];
					q.push(v);
				}
			}
		}
		return false;
	}
	int dfs(int x, int from){
		if(x == t || from <= 0)return from;
		int res = from, i;
		for(i = now[x]; i; i = e[i].net){
			int v = e[i].to;
			if(dep[v] == dep[x] + 1 && e[i].val > 0){
				int k = dfs(v, min(res, e[i].val));
				if(k <= 0)dep[v] = 0;
				res -= k;
				e[i].val -= k;
				e[i xor 1].val += k;
				if(res <= 0)break;
			}
		}
		now[x] = i;
		return from - res;
	}
	int dinic(){
		int mx = 0;
		while(bfs())mx += dfs(s, inf);
		return mx;
	}
	bool init(int k){
		tot = 1; for(int i = 1; i <= n + n + 5; ++i)head[i] = 0;
		s = n + n + 1, t = n + n + 2;
		for(int i = 1; i <= m; ++i)if((l[i].w & k) == k)link(l[i].u, l[i].v + n, 1);
		for(int i = 1; i <= n; ++i)link(s, i, 1);
		for(int i = 1; i <= n; ++i)link(i + n, t, 1);
		if(dinic() == n)return true;
		else return false;
	}
}w;

int ans[129];
int num[129];
bool vis[129];
int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; ++i)l[i].u = read(), l[i].v = read(), l[i].w = read();
	for(int i = 1; i <= m; ++i)++num[l[i].w], vis[l[i].w] = 1;
	for(int i = 126; i >= 0; --i){
		for(int j = 1; j < 128; j += j)
			if((i | j) > i)num[i] += num[i | j];
	}
	for(int k = 127; k >= 0; --k){
		if(num[k] < n)continue;
		int f = 127;
		for(int j = 1; j < 128; j += j)if((k | j) > k && num[k | j])f &= (k | j);
		if(f > k && !vis[k])continue;
		ans[k] = w.init(k);
	}
	for(int i = 0; i <= 127; ++i)printf("%d",ans[i]);
	return 0;
}

友(d)

学习了点分治。。。这东西我现在才学。。。

直接树形背包是 \(nm^2\) 的,虽然数据水能卡过。。

树形背包的问题是转移太多了,需要拼接贡献

考虑如何减少转移,

假如我们只考虑包含根节点的联通块,那么就可以直接转移而非拼接

具体来讲,处理出来 \(dfs\)序,按照其进行转移

考虑是否选择当前点,如果选择,那么可以考虑他的第一个儿子是否选择,不选择,就只能跳出当前子树

具体一点,如果当前点有儿子,那么选择当前点贡献到儿子

选择或者不选择当前点贡献到跳出该子树的第一个位置,即子树内最大的\(dfs\)\(+1\) 的位置

这样转移方向只有两个,而且只需考虑当前点选或不选,所以处理一个大小为 \(n\) 的树只需要 \(nm\)

于是每次考虑了过定点的联通块,那么后面再考虑就没有选择当前根的必要,也就是说可以删除当前点

那么套上点分治就可以 \(nmlogn\) 解决问题

code
#pragma GCC optimize(3)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<cassert>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 1005;
const int maxm = 10005;
const int inf = 0x3f3f3f3f;
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
int a[maxn], b[maxn], n, m;
int size[maxn], mx[maxn], sum, root;
bool vis[maxn];
void findroot(int x, int fa){
	size[x] = 1; mx[x] = 0;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa || vis[v])continue;
		findroot(v, x);
		size[x] += size[v];
		mx[x] = max(mx[x], size[v]);
	}
	mx[x] = max(mx[x], sum - size[x]);
	if(mx[x] < mx[root])root = x;
}

ll f[maxn][maxm], ans;
int dfn[maxn], dfnr[maxn], id[maxn], tim;
void dfs(int x, int fa){
	dfn[x] = ++tim; id[tim] = x;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to; 
		if(v == fa || vis[v])continue;
		dfs(v, x);
	}
	dfnr[dfn[x]] = tim;
}
void calc(int x){
	tim = 0;
	dfs(x, 0);
	for(int i = 1; i <= tim + 1; ++i)
		for(int j = 0; j <= m; ++j)
			f[i][j] = -inf;
	f[1][0] = 0;
	for(int i = 1; i <= tim; ++i){
		if(i + 1 <= dfnr[i]){
			int now = id[i];
			for(int j = 0; j <= m - a[now]; ++j){
				f[i + 1][j + a[now]] = max(f[i + 1][j + a[now]], f[i][j] + b[now]);
			}
		}
		for(int j = 0; j <= m; ++j){
			int now = id[i];
			if(j + a[now] <= m)f[dfnr[i] + 1][j + a[now]] = max(f[dfnr[i] + 1][j + a[now]], f[i][j] + b[now]);
			f[dfnr[i] + 1][j] = max(f[dfnr[i] + 1][j], f[i][j]);
		}
	}
	for(int i = 0; i <= m; ++i)ans = max(ans, f[tim + 1][i]);
}

void solve(int x){
	calc(x); vis[x] = true;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(vis[v])continue;
		sum = size[v]; root = 0;
		findroot(v, 0); solve(root);
	}
}

int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)a[i] = read(), b[i] = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	mx[0] = sum = n;
	findroot(1, 0);
	solve(root);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2022-10-11 18:14  Chen_jr  阅读(57)  评论(2编辑  收藏  举报