2022NOIP A层联测22

A. 极源流体

本场考试最失败的题

人总是会产生一些奇妙的错觉

就比如我觉得我能卡常切掉这题

然而用了将近一个小时不但没有减小什么常数,反而把正确性卡没了

\(79 - > 15\)

失败失败

不扯了

这题首先能够发现向上向下等价,向左向右等价,于是枚举向下多少步,然后向左走走到联通

使用一点小技巧可以把复杂度变成 \(n^3\) 但是好像常数有点 \(huge\)

你时限但凡再开大 1s

于是学习了另外的小常数做法

对每个点找出不同横坐标差下,纵坐标差最小的点建两个权值的边

然后枚举向下走的距离加边,统计答案

这样常数小在于黑点少的时候边数也很有限

更加 \(NB\) 的做法是使用 \(LCT\) 维护,懒得写了

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
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 = 750;
char c[maxn][maxn];
int n, m, ans, f[maxn * maxn], tot, cnt;
int id(int x, int y){return  (x - 1) * m + y;}
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, --cnt;}
vector<int>H[maxn], L[maxn];
struct edge{int a, b, x, y;}d[maxn * maxn * 2];
vector<edge>v[maxn];
vector<int>le;
bool cmp(const edge &x, const edge &y){return x.x == y.x ? x.y < y.y : x.x < y.x;}
int main(){
	freopen("fluid.in","r",stdin);
	freopen("fluid.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)scanf("%s",c[i] + 1);
	for(int i = 1; i <= n; ++i)for(int j = 1; j <= m; ++j)
		if(c[i][j] == '1')H[i].push_back(j), L[j].push_back(i);
	for(int i = 1; i <= n; ++i){
		for(int x : H[i]){	
			for(int j = x; j <= m; ++j){
				if(c[i][j] == '1' && j != x)break;
				int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
				if(p >= L[j].size())continue;
				p = L[j][p];
				d[++tot] = {id(i, x), id(p, j), p - i - 1, j - x - 1};
			}
			for(int j = x - 1; j >= 1; --j){
				if(c[i][j] == '1')break;
				int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
				if(p >= L[j].size())continue;
				p = L[j][p];
				d[++tot] = {id(i, x), id(p, j), p - i - 1, x - j - 1};
			}
		}
		for(int j = 0; j + 1 < H[i].size(); ++j)d[++tot] = {id(i, H[i][j]), id(i, H[i][j + 1]), -1, H[i][j + 1] - H[i][j] - 1};
	}
	sort(d + 1, d + tot + 1, cmp);
	for(int i = 1; i <= n * m; ++i)f[i] = i;
	for(int i = 1; i <= tot; ++i)if(d[i].x <= 0 && d[i].y <= 0)merge(d[i].a, d[i].b);
	for(int i = 1; i <= tot; ++i){
		d[i].a = fa(d[i].a), d[i].b = fa(d[i].b);
		if(d[i].a != d[i].b)v[d[i].y + 2].push_back(d[i]);
	}
	for(int i = 1; i <= n; ++i)for(int j = 1; j <= m; ++j){
		int d = id(i, j); if(fa(d) == d && c[i][j] == '1')le.push_back(d);
	}
	int ans = 0x3f3f3f3f;
	for(int i = 0; i <= n; ++i){
		for(int x : le)f[x] = x;
		cnt = le.size();
		for(int j = 1; j <= m + 2; ++j){
			for(edge x : v[j])if(x.x <= i)merge(x.a, x.b);
			if(cnt == 1){ans = min(ans, i + max(0, j - 2));break;}
			if(i + max(0, j - 2) > ans)break;
		}
	}
	printf("%d\n",ans);
	return 0;
}

B. 等差数列

枚举公差,计算每个数对应的首相,取 \(max\) 即可

复杂度 \(O(w/(n - 1) \times n)\)

code
#include<bits/stdc++.h>

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 = 300005;
int n, w, a[maxn], b[maxn], cnt[maxn], ans;

int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	n = read(), w = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	for(int i = 1; i <= n; ++i)++cnt[a[i]];
	for(int i = 1; i <= w; ++i)ans = max(ans, cnt[i]);
	for(int i = 1; i <= n; ++i)--cnt[a[i]];
	int up = ans == n ? -1 : w / (n - 1);
	for(int d = 1; d <= up; ++d){
		for(int i = 2; i <= n; ++i)a[i] -= (i - 1);
		int mx = w - 1ll * (n - 1) * d;
		if(mx < 1)break;
		for(int i = 1; i <= n; ++i)if(a[i] >= 1 && a[i] <= mx)++cnt[a[i]], ans = max(ans, cnt[a[i]]);
		for(int i = 1; i <= n; ++i)if(a[i] >= 1 && a[i] <= mx)--cnt[a[i]];
	}
	printf("%d\n", n - ans);
	return 0;
}

C. 送给好友的礼物

考场策略问题又没有时间看这个,最后半小时勉强搓了个\(2^{leafkey}\)暴力,数据水能骗到 \(77\)

首先肯定按照 \(dfs\) 序处理,因为数据范围很小,直接 \(n^2\) 求出任意两点间的距离

按照 \(dfs\)序排序后,就是划分两个集合,暴力可以状压

其实离正解很近了

把状压变成 \(DP\) ,设 \(f_{i, j, k}\) 表示按照 \(DFS\) 序 考虑前 \(i\) 个点, 与 \(i\) 划分为不同集合的点最后一个为 \(j\)\(i\)所在 集合目前需要时间 \(k\), 另外一个集合的最小时间

转移考虑当前点划分在哪个集合中

code
#include<bits/stdc++.h>

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 = 450;
int n, k;
vector<int>g[maxn], v;
int key[maxn], is[maxn];
int dis[maxn][maxn];
void dfs(int x, int fa, int root){
	for(int v : g[x]){
		if(v == fa)continue;
		dis[root][v] = dis[root][x] + 1; 
		dfs(v, x, root);
	}
}
short min(short x, short y){return x < y ? x : y;}
int dfn[maxn], tim, si[maxn];
void sol(int x, int fa){
	dfn[x] = ++tim;
	for(int v : g[x]){
		if(v == fa)continue;
		sol(v, x);
		si[x] += si[v];
	}
	if(is[x] && !si[x])v.push_back(x);
	si[x] += is[x];
}
short f[2][maxn][maxn + maxn];
void Ckmi(short &x, short y){x = x < y ? x : y;}
int main(){
	// freopen("gift.in","r",stdin);
	// freopen("gift.out","w",stdout);
	n = read(), k = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		g[u].push_back(v); g[v].push_back(u);
	}
	for(int i = 1; i <= k; ++i)is[key[i] = read()] = 1;
	for(int i = 1; i <= n; ++i)dfs(i, 0, i);
	if(k == 1){ printf("%d\n",dis[1][key[1]] * 2); return 0;}
	v.push_back(1); sol(1, 0);
	memset(f, 0x3f, sizeof(f));
	f[1][0][dis[1][v[1]]] = 0;
	int s = v.size();
	for(int i = 1; i < s - 1; ++i){
		for(int j = 0; j < i; ++j){
			for(int t = 0; t <= n + n; ++t)if(f[i & 1][j][t] != f[0][0][0]){
				Ckmi(f[(i + 1) & 1][i][f[i & 1][j][t] + dis[v[j]][v[i + 1]]], t);
				Ckmi(f[(i + 1) & 1][j][t + dis[v[i]][v[i + 1]]], f[i & 1][j][t]);
				f[i & 1][j][t] = f[0][0][0];
			}
		}
	}
	short ans = f[0][0][0];
	for(short i = 0; i < s - 1; ++i)
		for(short j = 0; j <= min((short)n + n, ans); ++j)
			ans = min(ans, max(j + dis[v[s - 1]][1], f[(s - 1) & 1][i][j] + dis[v[i]][1]));
	printf("%d\n",ans);
	return 0;
}

D. 非常困难的压轴题

水题,标题党踩了

code
#include<bits/stdc++.h>

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 = 242857;
char c[maxn];
int ss[maxn], sl[maxn], n;

int main(){
	freopen("lws.in","r",stdin);
	freopen("lws.out","w",stdout);
	scanf("%s",c + 1);
	n = strlen(c + 1);
	for(int i = 1; i <= n; ++i)sl[i] = sl[i - 1] + (c[i] == 'L');
	for(int i = n; i >= 1; --i)ss[i] = ss[i + 1] + (c[i] == 'S');
	ll ans = 0;
	for(int i = 2; i < n; ++i)if(c[i] == 'W')ans += 1ll * sl[i] * ss[i];
	printf("%lld\n", ans);
	return 0;
}

posted @ 2022-11-07 17:25  Chen_jr  阅读(15)  评论(0编辑  收藏  举报