2024牛客暑期多校训练营7
Preface
久违地打的像人的一场,在开局即红温的条件下后面还能放好心态打还是很不容易的
前期我被 I 单防红温了,还好有祁神救场上去帮我过了这个题,然后徐神秒出 D 的做法扔给我我爬上去实现了下很快过了
由于想不出 C 这个神秘构造的做法,只能经典地让我中期占机子写大模拟 H,想我这种没脑子的人就得去写这种赤石题啊
封榜后我把 H 的字符串比较部分换成了 Hash 然后跑了过去,祁神也是成功找到了 G 这个套路题并在结束前过掉,最后喜提六题罚时倒一
Array Sorting
喜不喜欢我并行排序算法啊
用任意一种 \(\log^2 n\) 的并行排序算法均可通过,如题解中用的双调排序,亦或是奇偶排序等做法均可
以下代码为双调排序的做法,即先将原序列处理变为双调的,再利用 Batcher 定理将其快速变为单调序列
注意当 \(n\) 不是 \(2\) 的幂次时,可以先将前面一段 \(2^k\) 的部分升序排列,然后将其 reverse
后和后面升序的部分拼成双调的
#include<cstdio>
#include<iostream>
#include<cmath>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=10005;
int n; vector <vector <pi>> ans;
inline void Build(CI st,CI lim)
{
for (RI i=1;i<lim;++i) for (RI j=i;j>=1;--j)
{
vector <pi> tmp;
for (RI l=0;l<(1<<lim);l+=(1<<j))
for (RI m=0;m<(1<<j-1);++m)
if (((l+m)/(1<<i))&1) tmp.push_back({st+l+m,st+l+m+(1<<j-1)});
else tmp.push_back({st+l+m+(1<<j-1),st+l+m});
ans.push_back(tmp);
}
}
inline void Sort(CI l,CI r)
{
for (RI i=13;i>=0;--i)
{
vector <int> used(r,0);
vector <pi> tmp;
for (RI j=l;j<r;++j)
{
if (used[j]) continue;
if (j+(1<<i)<r)
{
used[j+(1<<i)]=1;
tmp.push_back({j,j+(1<<i)});
}
}
if (!tmp.empty()) ans.push_back(tmp);
}
}
inline void Reverse(CI l,CI r)
{
vector <pi> tmp;
for (RI i=l,j=r-1;i<j;++i,--j)
tmp.push_back({j,i});
ans.push_back(tmp);
}
int main()
{
scanf("%d",&n);
int k=__lg(n);
if (n==(1<<k))
{
Build(0,k); Sort(0,n);
} else
{
Build(0,k); Sort(0,1<<k);
Reverse(0,1<<k);
Build(n-(1<<k),k); Sort(n-(1<<k),n);
Sort(0,n);
}
printf("%d\n",ans.size());
for (auto it:ans)
{
printf("%d",it.size());
for (auto [x,y]:it) printf(" %d %d",x,y);
putchar('\n');
}
return 0;
}
Interval Selection
经典队友秒出做法扔给我实现,只能说没有脑子的人就适合干这种活
考虑一个区间合法的充要条件,即所有数在区间中出现的次数为 \(0\) 次或 \(k\) 次
不妨用扫描线,固定右端点时,对于每一种数,其合法的左端点取值一定在两个不相交的区间中
用线段树维护贡献,考虑统计最大值以及最大值出现次数,当且仅当最大值等于数的总数时才更新答案
当右端点移动时,只有新加入的那个数对应的贡献区间会发生变化,简单讨论即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=200005;
typedef pair <int,int> pi;
int t,n,m,k,a[N],rst[N]; vector <int> pos[N];
inline pi operator + (const pi& A,const pi& B)
{
if (A.fi>B.fi) return A;
if (A.fi<B.fi) return B;
return {A.fi,A.se+B.se};
}
class Segment_Tree
{
private:
pi O[N<<2]; int tag[N<<2];
inline void apply(CI now,CI mv)
{
O[now].fi+=mv; tag[now]+=mv;
}
inline void pushdown(CI now)
{
if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void build(TN)
{
O[now]={0,r-l+1}; tag[now]=0; if (l==r) return;
int mid=l+r>>1; build(LS); build(RS);
}
inline void modify(CI beg,CI end,CI mv,TN)
{
if (beg<=l&&r<=end) return apply(now,mv);
int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify(beg,end,mv,LS);
if (end>mid) modify(beg,end,mv,RS);
O[now]=O[now<<1]+O[now<<1|1];
}
inline pi query(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return O[now];
int mid=l+r>>1; pushdown(now);
if (end<=mid) return query(beg,end,LS);
if (beg>mid) return query(beg,end,RS);
return query(beg,end,LS)+query(beg,end,RS);
}
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&k);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]),rst[i]=a[i];
sort(rst+1,rst+n+1); m=unique(rst+1,rst+n+1)-rst-1;
for (RI i=1;i<=n;++i) a[i]=lower_bound(rst+1,rst+m+1,a[i])-rst;
SEG.build(); long long ans=0;
for (RI i=1;i<=m;++i) pos[i]={0};
for (RI i=1;i<=n;++i)
{
SEG.modify(i,i,m);
SEG.modify(pos[a[i]].back()+1,i,-1);
if (pos[a[i]].size()>=k+1)
SEG.modify(pos[a[i]][pos[a[i]].size()-k-1]+1,pos[a[i]][pos[a[i]].size()-k],-1);
pos[a[i]].push_back(i);
if (pos[a[i]].size()>=k+1)
SEG.modify(pos[a[i]][pos[a[i]].size()-k-1]+1,pos[a[i]][pos[a[i]].size()-k],1);
auto it=SEG.query(1,i);
//printf("mx = %d, cnt = %d\n",it.fi,it.se);
if (it.fi==m) ans+=it.se;
}
printf("%lld\n",ans);
}
return 0;
}
Frog Crossing
很经典的平面最小割转对偶图最短路,最后给祁神当 Debugger 成功过了这个赛时只有 9 个队过的题
考虑朴素的做法,将一块木板拆成两个点,中间连上权值 \(v_i\) 的边;同时将两块相邻的木板对应的点之间连 \(\infty\) 的边
不难发现题目要求的是将每条边容量置为 \(0\) 后的最小割,这个东西肯定不能直接暴力搞,因此考虑转化
由于题目中的限制,可以发现这个图一定是个平面图,因此我们在其对偶图上做最短路问题后,就变为将一条边边权置 \(0\) 后的最短路问题,只需要简单讨论即可
具体实现时就是将每层的两块木板间的水域看作一个点,同层的点之间连对应木板的权值,相邻层的点用扫描线确定连边关系即可
注意一个易错点:相邻两行的水域在只有一个角相邻时也算作连通
#include<bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
const int N = 2e5+5;
const int INF = (int)1e18+5;
int n, m, totw=-1, S, T;
struct Block{
int x, yl, yr, v;
int lw, rw;
bool operator<(const Block &b)const{return yl<b.yl;}
}blks[N];
struct Event{
int pos, wid;
bool operator<(const Event &e)const{return pos!=e.pos ? pos<e.pos : wid>e.wid;}
};
vector<int> rows[N];
vector<Event> ev[N];
vector<pii> G[N*2];
int dis[2][N*2];
bool vis[N*2];
void addEdge(int a, int b, int w){
G[a].emplace_back(b, w);
G[b].emplace_back(a, w);
}
void Dij(int s, int d[]){
priority_queue<pii> Q;
for (int i=0; i<=totw; ++i) vis[i]=false, d[i]=INF;
d[s] = 0;
Q.push({0, s});
while (!Q.empty()){
auto [_, x] = Q.top(); Q.pop();
if (vis[x]) continue;
vis[x] = true;
for (auto [v, w] : G[x]){
if (d[x]+w < d[v]){
d[v] = d[x]+w;
Q.push({-d[v], v});
}
}
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int x, y, l, v; cin >> x >> y >> l >> v;
blks[i] = Block{x, y, y+l-1, v, -1, -1};
rows[x].push_back(i);
}
// puts("rows:");
for (int i=1; i<=n; ++i){
sort(rows[i].begin(), rows[i].end(), [&](int a, int b){return blks[a] < blks[b];});
// printf("i=%d:", i); for (auto id : rows[i]) printf("%d ", id); puts("");
}
S = ++totw; T = ++totw;
for (int i=1; i<=n; ++i){
int lstw=++totw;
addEdge(S, lstw, 0);
ev[i].push_back(Event{-INF, lstw});
for (int id : rows[i]){
auto &[x, yl, yr, v, lw, rw] = blks[id];
lw = lstw;
lstw = ++totw;
rw = lstw;
addEdge(lw, rw, v);
ev[i].push_back(Event{yl, -1});
ev[i].push_back(Event{yr+1, rw});
}
addEdge(lstw, T, 0);
ev[i].push_back(Event{INF, -1});
}
for (int i=1; i<n; ++i){
int up=-1, dn=-1;
int upos=0, dpos=0;
while (upos<ev[i+1].size() && dpos<ev[i].size()){
auto uev = ev[i+1][upos];
auto dev = ev[i][dpos];
if (uev < dev) up = uev.wid, ++upos;
else dn = dev.wid, ++dpos;
if (up!=-1 && dn!=-1) addEdge(up, dn, 0);
}
}
// for (int i=0; i<=totw; ++i){
// printf("i=%d:", i); for (auto [x, w] : G[i]) printf("(%d %d)", x, w); puts("");
// }
Dij(S, dis[0]);
Dij(T, dis[1]);
for (int i=1; i<=m; ++i){
int ans = min(dis[0][blks[i].lw]+dis[1][blks[i].rw], dis[1][blks[i].lw]+dis[0][blks[i].rw]);
ans = min(ans, dis[0][1]);
cout << ans << '\n';
}
return 0;
}
Database
赤石,启动!
乍一看很吓人,其实还好的大模拟,由于事务之间没有嵌套关系且查询/删除的嵌套层数只有 \(10\) 层,因此全部可以暴力处理
但实现时需要一些技巧,比如 abort
时撤销操作只需要维护一个标记表示这条记录是否可用,就可以避免反复删除增加的情况
并且由于字符串之间比较速度很慢,需要用 Hash 转为正整数后才能快速操作
最后理论复杂度 \(O(10\times nq)\),我的实现跑了 210ms
#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=1005;
int n,q,ext[N*3]; map <string,int> field,rst;
string key[N]; vector <string> rid;
int col[N][N*3]; vector <pi> rollback;
inline int ID(const string& s)
{
if (rst.count(s)) return rst[s];
rid.push_back(s); return rst[s]=rid.size()-1;
}
inline void redo(const string& s,CI id)
{
if (s[0]=='i') //insert
{
rollback.push_back({id,1});
ext[id]=1; int pos;
for (RI i=0;i<s.size();++i)
if (s[i]=='(') { pos=i; break; }
for (RI i=1;i<=n;++i)
{
string tmp;
for (RI j=pos+1;j<s.size();++j)
if (s[j]==','||s[j]==')') { pos=j; break; } else tmp+=s[j];
col[i][id]=ID(tmp);
}
}
if (s[0]=='s') //select/select_in
{
vector <pi> pmt; set <int> con;
int L=0,R=0;
for (RI i=s.size()-1;i>=0;--i)
if (s[i]==')') ++R; else break;
for (RI i=0;i<s.size();++i)
if (s[i]=='(')
{
++L; string A,B; int c=0;
for (RI j=i+1;j<s.size();++j)
{
if (s[j]!=','&&c==0) A+=s[j];
if (s[j]!=','&&c==1) B+=s[j];
if (s[j]==',') ++c;
if (c>1) { i=j; break; }
}
pmt.push_back({field[A],field[B]});
if (L==R)
{
string tmp;
for (RI j=i+1;j<s.size();++j)
if (s[j]!=')') tmp+=s[j]; else break;
con.insert(ID(tmp));
}
}
for (RI i=pmt.size()-1;i>=1;--i)
{
int x=pmt[i].fi,y=pmt[i].se;
set <int> new_con;
for (RI j=1;j<=id;++j)
if (ext[j]&&con.count(col[y][j])) new_con.insert(col[x][j]);
con=new_con;
}
vector <int> res;
int x=pmt[0].fi,y=pmt[0].se;
for (RI j=1;j<=id;++j)
if (ext[j]&&con.count(col[y][j])) res.push_back(col[x][j]);
cout<<res.size()<<'\n';
if (!res.empty())
{
cout<<rid[res[0]]<<'\n';
cout<<rid[res[(res.size()+1)/2-1]]<<'\n';
cout<<rid[res.back()]<<'\n';
}
}
if (s[0]=='d') //delete/delete_in
{
vector <pi> pmt; set <int> con;
int L=0,R=0;
for (RI i=s.size()-1;i>=0;--i)
if (s[i]==')') ++R; else break;
for (RI i=0;i<s.size();++i)
if (s[i]=='(')
{
++L; string A,B; int c=0;
for (RI j=i+1;j<s.size();++j)
{
if (s[j]!=','&&c==0) A+=s[j];
if (s[j]!=','&&c==1) B+=s[j];
if (s[j]==',') ++c;
if (L==1&&c>0) { i=j; break; }
if (c>1) { i=j; break; }
}
pmt.push_back({field[A],field[B]});
if (L==R)
{
string tmp;
for (RI j=i+1;j<s.size();++j)
if (s[j]!=')') tmp+=s[j]; else break;
con.insert(ID(tmp));
}
}
for (RI i=pmt.size()-1;i>=1;--i)
{
int x=pmt[i].fi,y=pmt[i].se;
set <int> new_con;
for (RI j=1;j<=id;++j)
if (ext[j]&&con.count(col[y][j])) new_con.insert(col[x][j]);
con=new_con;
}
vector <int> del;
int x=pmt[0].fi;
for (RI j=1;j<=id;++j)
if (ext[j]&&con.count(col[x][j])) del.push_back(j);
cout<<del.size()<<'\n';
for (auto id:del)
{
ext[id]=0; rollback.push_back({id,-1});
}
}
}
inline void undo(void)
{
reverse(rollback.begin(),rollback.end());
for (auto [id,tp]:rollback)
if (tp==1) ext[id]=0; else ext[id]=1;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>q;
for (RI i=1;i<=n;++i) cin>>key[i],field[key[i]]=i;
vector <pair <string,int>> transact;
for (RI i=1;i<=q;++i)
{
string s; cin>>s;
if (s=="begin()") continue;
if (s=="commit()")
{
rollback.clear();
for (auto [s,id]:transact) redo(s,id);
transact.clear(); continue;
}
if (s=="abort()")
{
rollback.clear();
for (auto [s,id]:transact) redo(s,id);
transact.clear(); undo(); continue;
}
transact.push_back({s,i});
}
return 0;
}
Fight Against the Monster
有铸币被签到腐乳了,还好有祁神救场
一眼二分答案,考虑如何检验初始时的 \(x\) 是否合法
一个核心的观察是发现每次创造都会导致没有用过创造功能的机器数量减小 \(m-k\),因此创造的总次数其实是固定的
另外要注意特判 \(k=m\) 的情况
#include<bits/stdc++.h>
using namespace std;
#define int long long
int m, k, h;
bool check(int x){
int t = (h-x+k-1)/k;
return m*t <= x+k*(t-1);
}
void solve(){
cin >> m >> k >> h;
if (m==k){
cout << min(m, h) << '\n';
return;
}
int L=0, R=h;
while (L<R){
int M = L+(R-L)/2;
if (check(M)) R=M;
else L=M+1;
}
cout << L << '\n';
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) solve();
return 0;
}
Ball
签到几何题,开场就被祁神秒了,我题目都没看
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve(){
int l, x, y; cin >> l >> x >> y;
if (x*x+y*y <= l*l) cout << "Yes\n0\n";
else if ((x-l)*(x-l)+y*y <= l*l) cout << "Yes\n" << l << '\n';
else cout << "No\n";
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) solve();
return 0;
}
Strings, Subsequences, Reversed Subsequences, Prefixes
遇到字符串怎么办,当然是扔给徐神然后白兰了,最后也是毫无悬念地拿下
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr llsi mod = 1'000'000'007;
int main() {
std::ios::sync_with_stdio(false);
int n, m; std::cin >> n >> m;
std::string s, t, v = "#"; std::cin >> s >> t;
s = std::string("#") + s;
t = std::string("#") + t;
for(int i = m; i; --i) v += t[i];
v += t;
std::vector<int> fail(2 * m + 2);
fail[0] = fail[1] = 0;
for(int i = 2, j = 0; i <= 2 * m + 1; ++i) {
while(j && v[j + 1] != v[i]) j = fail[j];
if(v[j + 1] == v[i]) j += 1;
fail[i] = j;
}
std::array<int, 26> nxt;
for(int i = 0; i < 26; ++i) nxt[i] = n + 1;
std::vector<int> hkr(n + 2, 0);
std::vector<std::array<int, 26>> go(n + 2);
for(int i = n + 1; i >= 0; --i) {
go[i] = nxt;
if(0 < i && i <= n) nxt[s[i] - 'a'] = i;
}
auto forward = go;
for(int i = 1, cur = 0; i <= m; ++i) {
cur = go[cur][t[i] - 'a'];
hkr[cur] = i;
}
for(int i = 0; i < 26; ++i) nxt[i] = 0;
std::vector<int> hbk(n + 2, 0);
for(int i = 0; i <= n + 1; ++i) {
go[i] = nxt;
if(0 < i && i <= n) nxt[s[i] - 'a'] = i;
}
for(int i = 1, cur = n + 1; i <= m; ++i) {
cur = go[cur][t[i] - 'a'];
hbk[cur] = i;
}
for(int i = 1; i <= n + 1; ++i) hkr[i] = std::max(hkr[i], hkr[i - 1]);
for(int i = n; i >= 0; --i) hbk[i] = std::max(hbk[i], hbk[i + 1]);
int ppp = -1, kkk = -1;
for(int i = 1; i <= n; ++i) if(hkr[i] == m) {
ppp = i; break;
}
for(int i = n; i >= 0; --i) if(hbk[i] == m) {
kkk = i; break;
}
if(ppp < 0) return std::cout << "0\n", 0;
llsi ans = 0;
//for(int i = 0; i <= n + 1; ++i) std::cout << hkr[i] << char(i == n + 1 ? 10 : 32);
//for(int i = 0; i <= n + 1; ++i) std::cout << hbk[i] << char(i == n + 1 ? 10 : 32);
for(int i = fail[2 * m + 1]; i; i = fail[i]) ans += (m - i <= hbk[ppp + 1]);
// std::cout << ppp << ' ' << kkk << char(10);
if(ppp < kkk) {
std::vector<llsi> dp(n + 2, 0);
for(int i = kkk - 1; i >= ppp; --i) {
dp[i] = 1;
// for(int j = 0; j < 26; ++j) std::cout << forward[i][j] << char(j == 25 ? 10 : 32);
for(int j = 0; j < 26; ++j) dp[i] += dp[forward[i][j]];
dp[i] %= mod;
}
// for(int i = 1; i <= n; ++i) std::cout << dp[i] << char(i == n ? 10 : 32);
ans += dp[ppp];
}
std::cout << ans % mod << char(10);
return 0;
}
Postscript
唉大模拟拯救了我们队,我愿封我为赤石大王