2022NOIPA层联测6

设密码比较失败,所以,

A. 构造字符串(str)

并查集维护一下相同的位置,注意到$ LCP + 1 $ 位置不同,于是每个集合取出来最靠前的为代表,两个集合不同,大集合向小集合连边,每次集合复制为能扫到的 \(mex\)

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

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 = 10005;
int f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){
	x = fa(x); y = fa(y);
	if(x > y)swap(x, y);
	f[y] = x;
}
int n, m;
int ans[maxn];
struct opt{int x, y, z;}op[maxn];
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;
}
bool vis[maxn];
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)f[i] = i;
	for(int i = 1; i <= m; ++i){
		int x = read(), y = read(), z = read();
		for(int j = 0; j < z; ++j)merge(x + j, y + j);
		op[i].x = x; op[i].y = y; op[i].z = z;
	}
	for(int i = 1; i <= m; ++i){
		int u = fa(op[i].x + op[i].z), v = fa(op[i].y + op[i].z);
		if(u <= n && v <= n && u && v){
			if(fa(u) == fa(v)){
				printf("-1\n");
				return 0;
			}
			add(max(u, v), min(u, v));
		}
	}
	for(int x = 1; x <= n; ++x)if(fa(x) == x){
		for(int i = head[x]; i; i = e[i].net){
			int v = e[i].to;
			if(ans[v] > -1)
			vis[ans[v]] = 1;
		}
		ans[x] = 0;
		while(vis[ans[x]])++ans[x];
		for(int i = head[x]; i; i = e[i].net){
			int v = e[i].to;
			vis[ans[v]] = 0;
		}
	}
	for(int i = 1; i <= n; ++i)printf("%d ",ans[fa(i)]);
	return 0;
}

B. 寻宝(treasure)

并查集维护整块,传送门加有向边,询问变成能否从某个点到另外一个点

发现边数很少,于是直接 \(DFS\)

如果边数增多,好像就需要 \(tarjan\) 缩点, 然后 \(bitset\) 优化

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

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 = 55000;
int n, m, k, q;
char c[maxn], s[maxn];
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 f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){
	x = fa(x); y = fa(y);
	if(x != y)f[y] = x;
}
int id(int x, int y){return (x - 1) * m + y;}
bool vis[maxn];
int sta[maxn], top;
int check(int x, int y){
	if(x == y)return true;
	vis[x] = 1; sta[++top] = x;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(vis[v])continue;
		if(v == y)return true;
		if(check(v, y))return true;
	}
	return false;
}
int main(){
	n = read(), m = read(), k = read(), q = read();
	for(int i = 1; i <= n * m; ++i)f[i] = i;
	for(int i = 1; i <= n; ++i){
		scanf("%s",c + 1);
		for(int j = 1; j <= m; ++j)s[id(i, j)] = c[j];
		if(i != 1){
			for(int j = 1; j <= m; ++j)if(c[j] == '.' && s[id(i - 1, j)] == '.')merge(id(i, j), id(i - 1, j));
		}
		for(int j = 2; j <= m; ++j)if(c[j] == '.' && c[j - 1] == '.')merge(id(i, j - 1), id(i, j));
	}
	for(int i = 1; i <= k; ++i){
		int a = read(), b = read(), c = read(), d = read();
		if(s[id(a, b)] == '#' || s[id(c, d)] == '#' || fa(id(a, b)) == fa(id(c, d)))continue;
		add(fa(id(a, b)), fa(id(c, d)));
	}
	for(int i = 1; i <= q; ++i){
		int a = read(), b = read(), c = read(), d = read();
		printf("%d\n",check(fa(id(a, b)), fa(id(c, d))));
		while(top > 0)vis[sta[top--]] = false;
	}	
	return 0;
}

C. 序列(seq)

学习了李超线段树,学长讲过但是从来没打过。。

前缀和一下,所求为 \(sa_r - sa_{l - 1} - k (sb_r - sb_{l - 1})[l <= p <= r]\)

拆成

\(sa_r - k sb_r[r >= p]\)

\(-sa_{l - 1} + k sb_{l - 1}[l <= p]\)

上面是两个一次函数的形式,于是用类似扫描线的方法用李超线段树维护一下直线即可

code
#pragma GCC optimize(3)
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

ll read(){
	ll x = 0;bool f = 0; char c = getchar();
	while(!isdigit(c)){if(c == '-')f = 1;c = getchar();}
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 1000005;
const int lim = 1000000;
int n, m;
ll a[maxn], b[maxn], suma[maxn], sumb[maxn], p, k;
struct lct{
	struct node{
		ll k, b;
		node(){b = -0x3f3f3f3f3f3f3f3f;k = 0;}
	}d[maxn << 3 | 1];
	ll calc(ll x, ll k, ll b){return k * x + b;}
	void insert(int x, int l, int r, ll k, ll b){
		int mid = (l + r) >> 1;
		ll v1 = calc(mid, k, b), v2 = calc(mid, d[x].k, d[x].b);
		if(v1 > v2){
			swap(k, d[x].k);
			swap(b, d[x].b);
		}
		if(l == r)return;
		if(calc(l, k, b) > calc(l, d[x].k, d[x].b))insert(x << 1, l, mid, k, b);
		if(calc(r, k, b) > calc(r, d[x].k, d[x].b))insert(x << 1 | 1, mid + 1, r, k, b);
	}
	ll query(int x, int l, int r, int pos){
		ll ans = calc(pos, d[x].k, d[x].b);
		if(l == r)return ans;
		int mid = (l + r) >> 1;
		if(pos <= mid)ans = max(ans, query(x << 1, l, mid, pos));
		else ans = max(ans, query(x << 1 | 1, mid + 1, r, pos));
		return ans;
	}
}t1;
struct query{
	ll p, k; int id;
	friend bool operator < (const  query &x, const query &y){
		return x.p < y.p;
	}
}q[maxn];
ll ans[maxn];
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)suma[i] = suma[i - 1] + a[i];
	for(int i = 1; i <= n; ++i)sumb[i] = sumb[i - 1] + b[i];
	for(int i = 1; i <= m; ++i)q[i].p = read(), q[i].k = read(), q[i].id = i;
	sort(q + 1, q + m + 1);
	int ins = 1;
	t1.insert(1, -lim, lim, 0, 0);
	for(int i = 1; i <= m; ++i){
		while(ins <= n && ins + 1 <= q[i].p)t1.insert(1, -lim, lim, sumb[ins], -suma[ins]), ++ins;
		ans[q[i].id] += t1.query(1, -lim, lim, q[i].k);
	}
	for(int i = 1; i <= maxn << 3; ++i)t1.d[i].k = 0, t1.d[i].b = -0x3f3f3f3f3f3f3f3f;
	ins = n;
	for(int i = m; i >= 1; --i){
		while(ins >= 1 && ins >= q[i].p)t1.insert(1, -lim, lim, -sumb[ins], suma[ins]), --ins;
		ans[q[i].id] += t1.query(1, -lim, lim, q[i].k);
	}
	for(int i = 1; i <= m; ++i)printf("%lld\n",ans[i]);
	return 0;
}

D. 构树(tree)

一个 \(n\) 个点, \(cnt\) 个联通块的森林的生成树的个数为 \(n^{cnt - 2}\Pi size_i\)

考虑用 \(g_i\) 表示至少有 \(i\) 条边与原树相同的方案数

考虑通过 \(DP\) 求出 \(\Pi size_i\)

\(f_{i, j, 1 / 0}\) 表示以 \(i\) 为根的子树内,有 \(j\) 个联通块, \(i\) 所在的联通块是否选出了一个元素

\(1/ 0\)的关键在于一个联通块恰好从 \(size_i\) 个点每个点贡献一次

转移用类似树形背包的方法,考虑是否选择当前根与儿子的边

于是 \(g_i = n^{n - i - 2}f_{1, n - i, 1}\)

然后二项式反演一下(我不会,直接褐)

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

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 = 8005;
const int mod = 1e9 + 7;
int qpow(int x, int y){
	if(y <= 0)return 1;
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n, head[maxn], tot, fac[maxn], inv[maxn];
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 si[maxn];
vector<int>f[maxn][2];
int tmp[maxn][2];
void add(int &x, ll y){x += y; x = x >= mod ? x - mod : x;}
void dfs(int x, int fa){
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		dfs(v, x);
	}
	f[x][0].resize(2);f[x][1].resize(2);
	f[x][0][1] = f[x][1][1] = 1; si[x] = 1;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		f[x][0].resize(si[x] + 1 + si[v]);
		f[x][1].resize(si[x] + 1 + si[v]);
		for(int j = 1; j <= si[x] + si[v]; ++j)tmp[j][1] = 0, tmp[j][0] = 0;
		for(int j = 1; j <= si[x]; ++j)
			for(int k = 1; k <= si[v]; ++k){
				add(tmp[j + k][0], 1ll * f[v][1][k] * f[x][0][j] % mod);
				add(tmp[j + k][1], 1ll * f[v][1][k] * f[x][1][j] % mod);
				add(tmp[j + k - 1][0], 1ll * f[v][0][k] * f[x][0][j] % mod);
				add(tmp[j + k - 1][1], (1ll * f[v][0][k] * f[x][1][j] % mod + 1ll * f[v][1][k] * f[x][0][j] % mod) % mod);
			}
		si[x] += si[v];
		for(int j = 1; j <= si[x]; ++j)f[x][1][j] = tmp[j][1], f[x][0][j] = tmp[j][0];
	}
}
int c(int n, int m){
	if(n < 0 || m < 0 || n < m)return 1;
	return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int g[maxn];
int ans[maxn];
int main(){
	n = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	dfs(1, 1);
	fac[0] = inv[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i >= 1; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	for(int i = 0; i < n - 1; ++i)g[i] = 1ll * qpow(n, n - i - 2) * f[1][1][n - i] % mod;
	g[n - 1] = 1;
	for(int i = 0; i < n; ++i){
		for(int j = i; j < n; ++j)
			if((j - i) & 1)add(ans[i], mod - 1ll * c(j, i) * g[j] % mod);
			else add(ans[i], 1ll * c(j, i) * g[j] % mod);
	}
	for(int i = 0; i < n; ++i)printf("%d ",ans[i]);
	return 0;
}
posted @ 2022-10-10 21:40  Chen_jr  阅读(70)  评论(0编辑  收藏  举报