[HZOI] CSP-S模拟28
CSP-S模拟28
话说这是什么信心赛吗?
T1:挑战
开题第一眼以为是贪心,然后狂贪不止。
发现过不了大样例,然后开始造小数据手膜。
之后越膜细节越多。
一位巨犇曾说,假做法就是开始很容易,越膜细节越多,然后永远也调不出来。
之后果断放弃贪心,开始尝试 \(dp\)。
之后就切了。
考虑两个二维的 \(dp\)、分别记录将其左边的点移到该点的最小花费和将其右边的点移到该点的最小花费。
之后直接统计答案即可。
细节有亿亿亿亿亿亿亿亿点多。
狂写 1 hour 然后直接切
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lid (id << 1)
#define rid (id << 1 | 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 3e5 + 7;
constexpr int INF = 0x3f3f3f3f;
int T;
int n;
int ans;
int sum[N];
int mp[3][N];
int dp1[3][N]; // dp[1][i] dp[2][i]
int dp2[3][N];
string s;
inline void solve(int pos)
{
int ans1u = 0,ans1d = 0,ans2u = 0,ans2d = 0;
for(int i = 1;i < pos;i ++)
{
if(!sum[i]) continue;
if(mp[2][i]) ans1u ++;
if(mp[1][i]) ans1d ++;
ans1u ++;
ans1d ++;
}
for(int i = pos + 1;i <= n;i ++)
{
if(mp[2][i]) ans2u ++;
if(mp[1][i]) ans2d ++;
ans2u ++;
ans2d ++;
if(sum[i] == sum[n]) break;
}
ans = min({ans,ans1u + ans2u + mp[2][pos],ans1d + ans2d + mp[1][pos],ans1u + ans2d + 1,ans1d + ans2u + 1});
}
signed main()
{
freopen("challenge.in","r",stdin);freopen("challenge.out","w",stdout);
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> T;
while(T --)
{
ans = INF;
cin >> n;
cin >> s;
for(int i = 1;i <= n;i ++)
{
mp[0][i] = s[i - 1] == '*' ? 1 : 0;
}
cin >> s;
for(int i = 1;i <= n;i ++)
{
mp[1][i] = s[i - 1] == '*' ? 1 : 0;
}
dp1[0][0] = dp1[1][0] = dp2[0][n + 1] = dp2[1][n + 1] = 0;
for(int i = 1;i <= n;i ++)
{
dp1[0][i] = min(dp1[0][i - 1] + mp[1][i] + (dp1[0][i - 1] != 0 || dp1[1][i - 1] != 0),dp1[1][i - 1] + 2);
dp1[1][i] = min(dp1[1][i - 1] + mp[0][i] + (dp1[1][i - 1] != 0 || dp1[0][i - 1] != 0),dp1[0][i - 1] + 2);
// cout << i << ' ' << dp1[0][i] << ' ' << dp1[1][i] << '\n';
}
// cout << '\n';
for(int i = n;i >= 1;i --)
{
dp2[0][i] = min(dp2[0][i + 1] + mp[1][i] + (dp2[0][i + 1] != 0 || dp2[1][i + 1] != 0),dp2[0][i + 1] + 2);
dp2[1][i] = min(dp2[1][i + 1] + mp[0][i] + (dp2[1][i + 1] != 0 || dp2[0][i + 1] != 0),dp2[1][i + 1] + 2);
// cout << i << ' ' << dp2[0][i] << ' ' << dp2[1][i] << '\n';
}
for(int i = 1;i <= n;i ++)
{
// cout << dp1[0][i] + dp2[0][i] << '\n';
// cout << dp1[1][i] + dp2[1][i] << '\n';
// cout << dp1[0][i] + dp2[1][i] + 1 << '\n';
// cout << dp2[0][i] + dp1[1][i] + 1 << '\n';
ans = min({ans,dp1[0][i] + dp2[0][i] - (mp[0][i] && mp[1][i]),dp1[1][i] + dp2[1][i] - (mp[0][i] && mp[1][i]),dp1[0][i] + dp2[1][i] + 1,dp2[0][i] + dp1[1][i] + 1});
}
// for(int i = 1;i <= n;i ++) solve(i);
cout << ans << '\n';
}
Blue_Archive;
}
// challenge
T2:染色
超级简单题,非常简单贪心。
5分钟直接切(话说为什么不放到T1?)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lid (id << 1)
#define rid (id << 1 | 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 2e5 + 7;
constexpr int INF = 0x3f3f3f3f;
int T;
int n;
int top;
int ans;
int a[N];
int stk[N];
string s;
signed main()
{
freopen("color.in","r",stdin);freopen("color.out","w",stdout);
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> T;
while(T --)
{
int cnt0 = 0,cnt1 = 0;
cin >> n;
cin >> s;
for(int i = 1;i <= n;i ++)
{
a[i] = (s[i - 1] == 'R' ? 1 : 0);
if(!a[i]) cnt0 ++;
}
if(cnt0 > n / 2)
{
cout << -1 << '\n';
continue;
}
int op = 0;
ans = top = cnt0 = 0;
for(int i = n;i >= 1;i --)
{
if(a[i]) stk[++ top] = i;
}
for(int i = 1;i <= n;i ++)
{
if(a[i]) cnt1 ++;
else cnt0 ++;
if(cnt0 > cnt1)
{
swap(a[stk[++ op]],a[i]);
ans ++;
cnt0 --;
cnt1 ++;
}
}
cout << ans << '\n';
}
Blue_Archive;
}
T3:海
好题啊好题。
很容易想到直接一个贪心,那就是每次选取一个只含有一个全局最大值的区间,这样选一定不劣,而且只需要考虑全局最大值一个限制。
我们只需要求 \(i\) 次魔法的最大贡献 \(op_i\),对于每次询问直接二分答案即可。
然后怎么做?
赛时没想出特别高明的解法,但是还是想叨叨一下赛时思路qwq。
我们将每两个最大值之间的区间提出来,我们发现:每次用魔法都只会选取一个区间或者两个相邻的小区间。
设一共有 \(top\) 个最大值,则有 \(top + 1\) 个这样的小区间。
于是考虑 \(dp\)。
很容易可以推出转移方程: \(dp[i][j] = max({dp[i][j - 1],dp[i - 1][j - 1] + a[j] + 1,dp[i - 1][j - 1] + a[j] + a[j - 1] + 1})\)
然后非常显然的,存答案的数组 \(op_i\),就等于 \(dp[i][top + 1]\)。
之后滚动数组优化一下就能做到 \(O(top ^ 2)\)
这样就能拿到 72 pts
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lid (id << 1)
#define rid (id << 1 | 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 5e5 + 7;
constexpr int INF = 0x3f3f3f3f;
int n;
int q;
int mx;
int top;
int ans;
int a[N];
int b[N];
int op[N];
int stk[N];
int dp[2][N];
inline void solve()
{
bool now = 1;
for(int i = 1;i <= top;i ++)
{
for(int j = 1;j <= top + 1;j ++)
{
dp[now][j] = max({dp[now][j - 1],dp[now ^ 1][j - 2] + b[j] + b[j - 1] + 1,dp[now ^ 1][j - 1] + b[j] + 1});
}
op[i] = dp[now][top + 1];
now ^= 1;
}
for(int i = tot + 1;i <= top;i ++) op[i] = op[i - 1] + 1;
for(int i = 1,x;i <= q;i ++)
{
cin >> x;
if(top == 1) cout << 1 << ' ';
else // erfen daan
{
int l = 1,r = top,mid;
ans = -1;
while(l <= r)
{
mid = (l + r) >> 1;
if(op[mid] >= x) r = mid - 1,ans = mid;
else l = mid + 1;
}
cout << ans << ' ';
}
}
exit(0);
}
signed main()
{
freopen("summer.in","r",stdin);freopen("summer.out","w",stdout);
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> q;
for(int i = 1;i <= n;i ++)
{
cin >> a[i];
mx = max(a[i],mx);
}
for(int i = 1;i <= n;i ++)
{
if(a[i] == mx)
{
stk[++ top] = i;
}
}
stk[top + 1] = n + 1;
for(int i = 1;i <= top + 1;i ++) b[i] = stk[i] - stk[i - 1] - 1;
solve();
Blue_Archive;
}
然后我们发现,如果整个序列都为同一个数,那么这个玩意儿的时间复杂度直接退化到 \(O(n ^ 2)\)。
爆炸了。恼火了。
于是开始考虑优化。
然后发现如果没有 \(top\) 个小区间的话,我们直接跑小区间的个数,然后后边的依次加一即可。
赛时为了求稳,特判了一下 \(q == 1\) 的情况。
然后就得了 76 tps。(这也太少了叭)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lid (id << 1)
#define rid (id << 1 | 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 5e5 + 7;
constexpr int INF = 0x3f3f3f3f;
int n;
int q;
int mx;
int top;
int ans;
int a[N];
int b[N];
int op[N];
int stk[N];
int dp[2][N];
inline void solve1()
{
int x;
cin >> x;
if(x == n)
{
cout << top << '\n';
exit(0);
}
bool now = 1;
for(int i = 1;i <= top;i ++)
{
for(int j = 1;j <= top + 1;j ++)
{
dp[now][j] = max({dp[now][j - 1],dp[now ^ 1][j - 2] + b[j] + b[j - 1] + 1,dp[now ^ 1][j - 1] + b[j] + 1});
}
op[i] = dp[now][top + 1];
if(op[i] >= x)
{
cout << i << '\n';
exit(0);
}
now ^= 1;
}
}
inline void solve()
{
int tot = 0;
for(int i = 1;i <= top + 1;i ++)
{
if(b[i]) tot ++;
}
bool now = 1;
for(int i = 1;i <= tot;i ++)
{
for(int j = 1;j <= top + 1;j ++)
{
dp[now][j] = max({dp[now][j - 1],dp[now ^ 1][j - 2] + b[j] + b[j - 1] + 1,dp[now ^ 1][j - 1] + b[j] + 1});
}
op[i] = dp[now][top + 1];
now ^= 1;
}
for(int i = tot + 1;i <= top;i ++) op[i] = op[i - 1] + 1;
for(int i = 1,x;i <= q;i ++)
{
cin >> x;
if(top == 1) cout << 1 << ' ';
else // erfen daan
{
int l = 1,r = top,mid;
ans = -1;
while(l <= r)
{
mid = (l + r) >> 1;
if(op[mid] >= x) r = mid - 1,ans = mid;
else l = mid + 1;
}
cout << ans << ' ';
}
}
exit(0);
}
signed main()
{
freopen("summer.in","r",stdin);freopen("summer.out","w",stdout);
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> q;
for(int i = 1;i <= n;i ++)
{
cin >> a[i];
mx = max(a[i],mx);
}
for(int i = 1;i <= n;i ++)
{
if(a[i] == mx)
{
stk[++ top] = i;
}
}
stk[top + 1] = n + 1;
for(int i = 1;i <= top + 1;i ++) b[i] = stk[i] - stk[i - 1] - 1;
if(q == 1) solve1();
solve();
Blue_Archive;
}
之后就不会做了,开始发呆发呆发呆发呆发呆。。。。。。
最后一发交错了,结果 76 tps --> 72 tps
好气 O(≧口≦)O
接下来说正解。
其实正解思路跟我的很像,只是我是用 \(dp\) 去维护答案数组。
正解是用的反悔贪心(用优先队列维护)。
时间复杂度 \(O(top log_{2}{top})\)
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define Blue_Archive return 0
using namespace std;
const int N = 5e5 + 10;
int n;
int q;
int top;
int a[N];
int f[N];
int pre[N];
int nxt[N];
int stk[N];
bool del[N];
struct node
{
int l,r,val;
bool operator<(const node &b) const { return val < b.val;}
};
priority_queue<node> Q;
signed main()
{
freopen("summer.in","r",stdin);freopen("summer.out","w",stdout);
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> q;
int mx = 0;
for(int i = 1;i <= n;i ++)
{
cin >> a[i];
mx = max(mx,a[i]);
}
int lst = 0;
stk[++ top] = 0;
for(int i = 1;i <= n;i ++)
{
if(a[i] == mx) stk[++ top] = i - lst - 1,lst = i;
}
stk[++ top] = n - lst;
for(int i = 1;i <= top;i ++)
{
pre[i] = i - 1;
nxt[i] = i + 1;
Q.push(node({i,i + 1,stk[i] + stk[i + 1]}));
}
nxt[top + 1] = top + 1;
for(int i = 1;i <= top;i ++)
{
node x = Q.top();
Q.pop();
while(Q.size() && (del[x.l] || del[x.r])) x = Q.top(),Q.pop();
if(x.l && x.l <= top) del[x.l] = 1;
if(x.r && x.r <= top) del[x.r] = 1;
f[i] = f[i - 1] + x.val + 1;
x.l = pre[x.l];
x.r = nxt[x.r];
nxt[x.l] = x.r;
pre[x.r] = x.l;
x.val = stk[x.l] + stk[x.r];
Q.push(x);
}
for(int i = 1,x;i <= q;i ++)
{
cin >> x;
cout << (lower_bound(f + 1,f + top + 1,x) - f) << ' ';
}
Blue_Archive;
}
T4:星白
题目背景是星白欸,诶嘿嘿。
大简单题(存疑)
打 \(O(q n ^ 2)\) 暴力能有 \(66\) 分!
打 \(O(n ^ 2 log_n)\) 暴力能有 \(83\) 分!
太神秘了。。。
但是我把我的 \(66\) tps 暴力覆盖了wwwwww
0 tps 带回,什么运气啊啊啊
暴力还是很好想的,这里就直接开始正解!
考虑到这个题有 \(INF\) 个限制条件,直接做肯定是不行的,于是我们逐个击破。
首先我们有个大概的思路,因为它要求询问区间内是否含有有问题的二元组。
肯定是要把问题离线的,然后预处理出二元组,再查询询问。
好,现在放空一下大脑,考虑一下有那些限制条件。
\(x < y\) ,且 \(p_x < p_y\)
存在一个 \(i\) 满足 \(x < i < y\), \(p_i = \min\limits_{j=x}^{y} p_j\) 并且 \(p_i | (p_x × p_y)\)
后边这个同时要满足 \(p_i = \min\limits_{j=x}^{y} p_j\) 并且 \(p_i | (p_x × p_y)\)。
好麻烦~
所以我决定将其分开,分别维护。
首先有一个初步的思路是,枚举最小值,然后枚举一端端点,找合法的另一端端点。
枚举较小的区间,以此来保证复杂度(like 启发式合并)
然后我们发现,欸,这不是笛卡尔树嘛!
对于每个最小值,对于其左右子树,枚举一棵,找另一棵。
放到序列上来说就是:找到当前区间的最小值,将其作为 \(mid\),然后去递归地去处理左右区间。
然后我们再次发现: 欸,这不是 \(cdq\) 嘛!
笛卡尔树上二分跟cdq序列二分都是对的,都可以实现(我用的是cdq)
为了保证复杂度,每次在区间查找最小值用 st 表维护。
之后先考虑最简单的条件: \(p_i | (p_x × p_y)\)。(因为这玩意儿具有单调性)
我们现在已经枚举了 \(p_i\) 和 \(p_x\) 现在要去找 \(p_y\) ,我们发现,如果要满足 \(p_i | (p_x × p_y)\) 就是要去满足 \(p_i | \gcd(p_x,p_i) | p_y\)。
直接去枚举倍数肯定是不行的,所以我们直接考虑预处理出每个因数的集合(因为题目给的是序列,所以实际上的时间复杂度是 \(n / 1 + n / 2 + n / 3 + n / 4 + ... + n / n\),这玩意儿是调和级数,显然是 \(n log_2{n}\) 的)
因为我们是枚举的最小值,所以 \(p_i = \min\limits_{j=x}^{y} p_j\) 也满足了欸。
接下来我们只需要保证 \(x < y\) ,且 \(p_x < p_y\) 就行了。
这个直接在 \(\gcd(p_x,p_i)\) 因子的线段树上区间查询即可。
这样我们就能枚举出所有的合法二元组,对于查询,直接当做二维数点做就行了(二维偏序)
用树状数组处理查询。
时间复杂度 \(O(n log^2{n})\) ,空间复杂度 \(O(n log^2{n})\)(都花在建线段树上了)
用到了线段树、树状数组、st表、cdq(笛卡尔树) 等数据结构。(什么拼拼乐)
这样,我们就写完了(写了我一下午呜呜呜)。
接下来放上正解代码。(可算是写完了)
点击查看代码
#include <bits/stdc++.h>
#define lid ls[id]
#define rid rs[id]
#define lowbit(x) (x & -x)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 3e5 + 3;
constexpr int Q = 1e6 + 3;
constexpr int D = 4e7 + 3;
constexpr int INF = 0x3f3f3f3f;
int n;
int q;
int tot;
int a[N];
int rt[N];
int ma[D];
int mn[D];
int ls[D];
int rs[D];
int pos[N];
int ans[Q];
int st[19][N];
bool tr[N];
vector<int> ln[N];
vector<pair<int,int>> ask[N];
inline int read()
{
int k = 0,f = 1;
char c = getchar_unlocked();
while(c < '0' || c > '9')
{
if(c == '-') f = -1;
c = getchar_unlocked();
}
while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
return k * f;
}
inline void write(int x)
{
if(x < 0) putchar_unlocked('-'),x = -x;
if(x > 9) write(x / 10);
putchar_unlocked(x % 10 + '0');
}
inline int get(int x,int y){return a[x] < a[y] ? x : y;}
inline void init()
{
for(int i = 1;i <= n;i ++) st[0][i] = i;
for(int i = 1;i <= 18;i ++)
{
for(int j = 1;j + (1 << i) - 1 <= n;j ++)
{
st[i][j] = get(st[i - 1][j],st[i - 1][j + (1 << (i - 1))]);
}
}
}
inline int Get(int l,int r)
{
int k = __lg(r - l + 1);
return get(st[k][l],st[k][r - (1 << k) + 1]);
}
inline void ins(int &id,int l,int r,int pos,int v)
{
if(!id) id = ++ tot;
if(l == r)
{
ma[id] = mn[id] = v;
return;
}
int mid = (l + r) >> 1;
if(pos <= mid) ins(lid,l,mid,pos,v);
else ins(rid,mid + 1,r,pos,v);
ma[id] = max(ma[lid],ma[rid]);
mn[id] = min(mn[lid],mn[rid]);
}
inline int gma(int id,int l,int r,int val)
{
if(l == r) return l;
int mid = (l + r) >> 1;
if(ma[lid] > val) return gma(lid,l,mid,val);
else return gma(rid,mid + 1,r,val);
}
inline int cma(int id,int l,int r,int L,int R,int v)
{
if(!id) return -1;
if(L == l && R == r) return ma[id] > v ? gma(id,l,r,v) : -1;
int mid = (l + r) >> 1;
if(R <= mid) return cma(lid,l,mid,L,R,v);
if(L > mid) return cma(rid,mid + 1,r,L,R,v);
else
{
int re = cma(lid,l,mid,L,mid,v);
return re == -1 ? cma(rid,mid + 1,r,mid + 1,R,v) : re;
}
}
inline int gmi(int id,int l,int r,int val)
{
if(l == r) return l;
int mid = (l + r) >> 1;
if(mn[rid] < val) return gmi(rid,mid + 1,r,val);
else return gmi(lid,l,mid,val);
}
inline int cmi(int id,int l,int r,int L,int R,int v)
{
if(!id) return -1;
if(L == l && R == r) return mn[id] < v ? gmi(id,l,r,v) : -1;
int mid = (l + r) >> 1;
if(R <= mid) return cmi(lid,l,mid,L,R,v);
if(L > mid) return cmi(rid,mid + 1,r,L,R,v);
else
{
int res = cmi(rid,mid + 1,r,mid + 1,R,v);
return res == -1 ? cmi(lid,l,mid,L,mid,v) : res;
}
}
inline void Cdq(int l,int r)
{
if(l > r) return;
int mid = Get(l,r); // 查询区间最小值作为 mid
if(mid - l < r - mid) // 枚举小的一边(like 启发式合并,保证时间复杂度)
{
for(int i = l,pos,p;i < mid;i ++) // 枚举左端点
{
pos = a[mid] / __gcd(a[mid],a[i]); // 查询其剩余因子所在线段树
p = mid + 1 > r ? -1 : cma(rt[pos],1,n,mid + 1,r,a[i]);
if(p != -1) ln[i].push_back(p); // 找到合法的点
}
}
else // 类比上边
{
for(int i = r,pos,p;i > mid;--i)
{
pos = a[mid] / __gcd(a[mid],a[i]);
p = l > mid - 1 ? -1 : cmi(rt[pos],1,n,l,mid - 1,a[i]);
if(p != -1) ln[p].push_back(i);
}
}
Cdq(l,mid - 1);
Cdq(mid + 1,r);
}
inline void add(int p)
{
for(int i = p;i <= n;i += lowbit(i))
{
tr[i] = 1;
}
}
inline bool query(int p)
{
bool s = 0;
for(int i = p;i;i -= lowbit(i))
{
s |= tr[i];
}
return s;
}
signed main()
{
freopen("jewelry.in","r",stdin);freopen("jewelry.out","w",stdout);
// freopen("data.in","r",stdin);freopen("ans.out","w",stdout);
memset(mn,0x3f,sizeof(mn));
n = read();
for(int i = 1;i <= n;i ++)
{
a[i] = read();
pos[a[i]] = i;
}
init();
for(int i = 1;i <= n;i ++) // 每个因数建一棵线段树
{
for(int j = i;j <= n;j += i)
{
ins(rt[i],1,n,pos[j],j);
}
}
q = read();
for(int i = 1,l,r;i <= q;i ++)
{
l = read();
r = read();
ask[l].push_back({i,r}); // 离线处理问题
}
Cdq(1,n); // 区间二分
for(int i = n;i;i --) // 转化为二维数点(二维偏序)(枚举左端点)
{
for(auto r : ln[i]) add(r);
for(auto op : ask[i]) ans[op.first] = query(op.second);
}
for(int i = 1;i <= q;i ++)
{
cout << (ans[i] ? "Yes" : "No") << endl;
}
Blue_Archive;
}
最后总结:本场是信心赛(雾)(挂大分)
[100 + 100 + 78 + 66] --> [100 + 100 + 72 + 0]
[344] --> [272]

浙公网安备 33010602011771号