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