Preface
徐神是我们的红太阳,最后 2min 切了一道极难的 string 使得在这场前期爆炸的局面最后没有崩得太难看
这场前期的开题顺序有点问题导致前 5 题出的很慢,中后期开始三人一人写一题,然后经典三开三卡
好在最后我在 WA 了五发后写对拍把 B 过了,徐神又压线过了 string,但比较可惜的是最后给祁神当小黄鸭找到了他 C 题的几个问题但没时间改了遗憾收场
深度自同构
神秘签到题,我题意都不知道
#include <bits/stdc++.h>
int f[1000001] = {1};
int main() {
std::ios::sync_with_stdio(false);
int n; std::cin >> n;
for(int i = 1; i <= n; ++i) for(int j = i; j <= n; j += i) f[j] = (f[j] + f[i - 1]) % 998244353;
for(int i = 1; i <= n; ++i) std::cout << f[i] << char(i == n ? 10 : 32);
return 0;
}
旅行
很典的线段树分治/启发式合并优化树上 DP,但有人初值设错了瞪了 1h 最后对拍才发现问题,怎么回事呢
考虑一个 trivial 的 DP 做法,令 \(f_{i,j}\) 表示处理了 \(i\) 的子树,且上传一条端点类型为 \(j\) 的路径时最大的权值和;\(g_i\) 表示在 \(i\) 处合并路径,不上传的最大权值和
转移的时候先默认点 \(x\) 的所有儿子 \(y\) 都不上传路径,得到 \(sum=\sum_{y\in son(x)} g_y\)
对于 \(f_{x,c}\) 的更新,显然有 \(f_{x,c}=sum+\max_{y\in son(x)} f_{y,c}-g_y\),即选择从一个子树内继承路径,这样就不能获得在该子树处合并的贡献
对于 \(g_x\) 的更新,就是类似树上背包的找到两个子树的 \(f_{y,c}-g_y\) 合并即可,实现时可以对每种不同的第二位记录最大次大值进行处理
考虑优化该做法,不难发现第二维的端点类型不用显式的设出来,而是可以用 map
存储然后合并子树的时候启发式合并
同时再给每个点维护一个增量标记用来处理加减信息即可,总复杂度 \(O(n\log^2 n)\),当然也可以写 \(O(n\log n)\) 的线段树合并
#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
int t,n,c[N],w[N],x,y,tag[N],g[N],ans; vector <int> v[N]; map <int,int> f[N];
inline void DFS(CI now=1,CI fa=0)
{
int sum=0; tag[now]=g[now]=0;
for (auto to:v[now]) if (to!=fa) DFS(to,now),sum+=g[to];
int ret=sum;
auto merge=[&](CI x,CI y)
{
if (f[x].size()<f[y].size()) swap(f[x],f[y]),swap(tag[x],tag[y]);
for (auto [col,val]:f[y])
{
if (f[x].count(col)) ret=max(ret,sum+(f[x][col]+tag[x])+(val+tag[y]));
if (!f[x].count(col)||(f[x][col]+tag[x])<(val+tag[y])) f[x][col]=val+tag[y]-tag[x];
}
f[y].clear();
};
for (auto to:v[now]) if (to!=fa) tag[to]-=g[to],merge(now,to);
if (f[now].count(c[now]))
{
ret=max(ret,sum+(f[now][c[now]]+tag[now])+w[now]);
if ((f[now][c[now]]+tag[now])<w[now]) f[now][c[now]]=w[now]-tag[now];
} else f[now][c[now]]=w[now]-tag[now];
tag[now]+=sum; g[now]=ret; ans=max(ans,g[now]);
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i) v[i].clear(),f[i].clear();
for (i=1;i<=n;++i) scanf("%lld",&c[i]);
for (i=1;i<=n;++i) scanf("%lld",&w[i]);
for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
ans=-INF; DFS(); printf("%lld\n",ans);
//for (i=1;i<=n;++i) printf("%lld%c",g[i]," \n"[i==n]);
}
return 0;
}
游走
神秘分讨题,奖励祁神吃一坨大的
很容易发现除了 \(p_i=1\) 的信息外,其余的信息已经唯一地确定了酒鬼的移动,只要检查两次目击间的时间能否 cover 距离,以及信息间 \(p_i+q_i\) 的奇偶性相同即可
但现在问题是对于 \(p_i=1\) 的信息需要特别处理,可以不用保证奇偶性必须一样,分多种情况讨论即可
#include<bits/stdc++.h>
using namespace std;
void solve(){
map<int, int> ifo;
int n, m; cin >> n >> m;
bool bad=false;
int tnz = 1e9+5;
int mov1 = -1;
int parity = -1;
int odd1=-1, even1=0;
for (int i=1; i<=m; ++i){
int opt; cin >> opt;
if (0==opt){
int x, t; cin >> x >> t;
if (bad) continue;
if (ifo.count(t)){
if (ifo[t]!=x) bad=true;
continue;
}
ifo[t] = x;
if (-1==parity){
if (1==x){
if (t%2==1) odd1 = max(odd1, t);
else even1 = max(even1, t);
continue;
}
parity = (t+x)%2;
tnz = t;
bool meet=false;
for (auto [t1, x1] : ifo){
if (t1==t){
meet=true;
}
if ((t1+x1)%2!=parity){
if (!meet) mov1 = t1;
else{bad=true; break;}
}
}
if (bad) continue;
}
auto it = ifo.find(t);
auto checkback = [&]() -> bool {
auto it1 = it; ++it1;
if (it1!=ifo.end()){
auto [t1, x1] = *it1;
if (abs(t-t1) < abs(x-x1)) return false;
}
return true;
};
auto checkfront = [&]() -> bool {
if (t > tnz){
auto it1 = it; --it1;
auto [t1, x1] = *it1;
if (abs(t-t1) < abs(x-x1)) return false;
}else if (t > mov1){
if (t-mov1 < x) return false;
}else if (t < mov1) return false;
return true;
};
if (t<tnz){
if (1==x){
if ((t+x)%2!=parity) mov1 = max(t, mov1);
if (mov1 > tnz){ bad=true; continue;}
int xnz = ifo[tnz];
int x1=1, t1 = ((t+x)%2==parity ? t : t+1);
if (abs(t1-tnz) < abs(x1-xnz)){ bad=true; continue;}
}else{
if ((t+x)%2!=parity){ bad=true; continue;}
if (!checkback() || !checkfront()){ bad=true; continue;}
tnz = t;
}
}else{
if ((t+x)%2!=parity){ bad=true; continue;}
if (!checkback() || !checkfront()){ bad=true; continue;}
}
}else if (2==opt){
if (bad){cout << "bad\n"; continue;}
if (-1==parity) cout << "inf\n";
else{
int xnz = ifo[tnz];
cout << tnz-xnz+1 << '\n';
}
}else if (1==opt){
if (bad){cout << "bad\n"; continue;}
if (-1==parity) cout << (odd1 > even1 ? even1+1 : odd1+1) << '\n';
else cout << ((mov1+2)%2==parity ? mov1+1 : mov1+2) << '\n';
}
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) solve();
return 0;
}
游戏
神秘概率题,前面的转化都能想到,然后一转换成随机游走模型就歇逼了,鉴定为弃疗
数论
好像是个神秘复杂度分析题,感觉不太可做,弃疗
字符串
徐神伟大,无需多言,这题我题都没看,一个劲地模徐神就完事了
#include <bits/stdc++.h>
constexpr int $n = 1000005;
using llsi = long long signed int;
class SAM {
void dfs(int cur) {
loop[cur][0] = fa[cur];
for(int i = 1; i <= 20; ++i) loop[cur][i] = loop[loop[cur][i - 1]][i - 1];
for(auto ch: ch[cur]) dfs(ch);
}
public:
int loop[$n][21];
std::vector<int> ch[$n];
int go[$n][2], fa[$n], len[$n], occ[$n], las, newcnt;
llsi dp[$n];
int pos[$n], pre[$n][21];
void init(int n) {
las = newcnt = 1;
for(int i = 0; i < 2; ++i) go[1][i] = 0;
}
void build_loop() {
for(int i = 1; i <= newcnt; ++i) ch[i].clear();
for(int i = 2; i <= newcnt; ++i) ch[fa[i]].push_back(i);
dfs(1);
}
void calc_occ(const std::string &s) {
memset(occ, 0, sizeof(int) * (newcnt + 1));
for(int i = 0, cur = 1; i < s.size(); ++i)
assert(cur),
occ[cur = go[cur][s[i] - 'a']] ++;
auto d = [&](auto self, int cur) -> void {
for(auto ch: ch[cur]) if(ch) self(self, ch), occ[cur] += occ[ch];
};
d(d, 1);
std::vector<bool> locked(newcnt + 1, false);
auto fetch_next = [&](int i) {
for(int j = 0; j < 2; ++j) if(go[i][j] && occ[go[i][j]] == occ[i]) return go[i][j];
return 0;
};
for(int i = 1; i <= newcnt; ++i) for(int j = 0; j < 2; ++j) {
int n; if((n = fetch_next(i))) locked[n] = true;
}
for(int i = 1; i <= newcnt; ++i) if(!locked[i]) {
dp[i] = len[fa[i]]; pos[i] = 0;
memset(pre[i], 0, sizeof(pre[i]));
for(int cur = i, nxt = fetch_next(cur); nxt; cur = nxt, nxt = fetch_next(cur)) {
dp[nxt] = dp[cur] + len[fa[nxt]];
pos[nxt] = pos[cur] + 1;
pre[nxt][0] = cur;
for(int i = 1; i <= 20; ++i) pre[nxt][i] = pre[pre[nxt][i - 1]][i - 1];
}
}
return ;
}
int pop(int start, int l) {
for(int i = 20; ~i; --i) if(len[loop[start][i]] >= l) start = loop[start][i];
return start;
}
std::pair<int, int> pop_range(int start, int i) {
int res = pop(start, i);
return {len[res], len[res] + 1};
}
void insert(char a) {
int c = a - 'a';
int p = las;
int np = las = ++newcnt;
len[np] = len[p] + 1;
for(int i = 0; i < 2; ++i) go[np][i] = 0;
for(; p && !go[p][c]; p = fa[p]) go[p][c] = np;
if(!p) {
fa[np] = 1;
return ;
}
int q = go[p][c];
if(len[q] == len[p] + 1) {
fa[np] = q;
return ;
}
int nq = ++newcnt;
len[nq] = len[p] + 1;
for(int i = 0; i < 2; ++i) go[nq][i] = go[q][i];
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for(; p && go[p][c] == q; p = fa[p]) go[p][c] = nq;
return ;
}
} S1, S2;
int main() {
std::ios::sync_with_stdio(false);
int t; std::cin >> t; while(t--) {
std::string s; std::cin >> s;
int n = s.size();
S1.init(n), S2.init(n);
for(int i = 0; i < n; ++i) S1.insert(s[i]);
for(int i = n - 1; i >= 0; --i) S2.insert(s[i]);
std::vector<int> e1(n), e2(n);
for(int i = 0, cur = 1; i < n; ++i)
e1[i] = cur = S1.go[cur][s[i] - 'a'];
for(int i = n - 1, cur = 1; i >= 0; --i)
e2[i] = cur = S2.go[cur][s[i] - 'a'];
S1.build_loop(), S2.build_loop();
S1.calc_occ(s);
llsi lastans = 0;
int q; std::cin >> q; while(q--) {
llsi op, l, r;
std::cin >> op >> l >> r;
l = (l + lastans - 1) % n;
r = (r + lastans - 1) % n;
// std::cerr << "l, r = " << l << ", " << r << char(10);
llsi L = r - l + 1;
if(op == 1) {
auto [l1, l2] = S1.pop_range(e1[r], L);
auto [r1, r2] = S2.pop_range(e2[l], L);
lastans = (l1 - L + 1) * (r1 - L + 1);
} else {
int hkr = S1.pop(e1[r], L);
// std::cerr << "dp = " << S1.dp[hkr] << ", pos = " << S1.pos[hkr] << ", occ = " << S1.occ[hkr] << char(10);
auto pre = S1.pre;
auto fa = S1.fa;
auto len = S1.len;
int d = L, cur = hkr, offset = 0;
for(int i = 20; ~i; --i) {
int nxt = pre[cur][i];
if(nxt == 0) continue;
if(len[fa[nxt]] >= d - (1 << i)) continue;
d -= (1 << i);
offset |= 1 << i;
cur = pre[cur][i];
}
// std::cerr << "dp[" << cur << "] = " << S1.dp[cur] << char(10);
lastans = L * (offset + 1) - (S1.dp[hkr] - S1.dp[pre[cur][0]]) - (llsi(offset + 1) * (offset) / 2);
}
std::cout << lastans << char(10);
}
}
return 0;
}
单峰数列
把原数组上的信息转化到差分数组上,不难发现每个条件的检验都很 trivial 了
判断单峰时可以线段树上二分找到第一个差分值 \(\le 0\) 的位置,然后检验后面一段是否单减即可
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e18;
int n,a[N],d[N],q,opt,l,r,x;
class Segment_Tree
{
private:
int mx[N<<2],mn[N<<2];
inline void pushup(CI now)
{
mx[now]=max(mx[now<<1],mx[now<<1|1]);
mn[now]=min(mn[now<<1],mn[now<<1|1]);
}
public:
#define TN CI now=1,CI l=1,CI r=n+1
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void build(TN)
{
if (l==r) return (void)(mx[now]=mn[now]=d[l]);
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline void update(CI pos,CI mv,TN)
{
if (l==r) return (void)(mx[now]+=mv,mn[now]+=mv); int mid=l+r>>1;
if (pos<=mid) update(pos,mv,LS); else update(pos,mv,RS); pushup(now);
}
inline int query_min(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF;
if (beg<=mid) ret=min(ret,query_min(beg,end,LS));
if (end>mid) ret=min(ret,query_min(beg,end,RS));
return ret;
}
inline int query_max(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return mx[now]; int mid=l+r>>1,ret=-INF;
if (beg<=mid) ret=max(ret,query_max(beg,end,LS));
if (end>mid) ret=max(ret,query_max(beg,end,RS));
return ret;
}
inline int query_pos(CI beg,CI end,TN)
{
if (r<beg||l>end) return -1;
if (mn[now]>0) return -1;
if (l==r) return l;
int mid=l+r>>1,res=query_pos(beg,end,LS);
if (res!=-1) return res;
return query_pos(beg,end,RS);
}
#undef TN
#undef LS
#undef RS
}SEG;
signed main()
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
for (i=1;i<=n+1;++i) d[i]=a[i]-a[i-1];
for (SEG.build(),scanf("%lld",&q);q;--q)
{
scanf("%lld%lld%lld",&opt,&l,&r); int pos;
switch (opt)
{
case 1:
scanf("%lld",&x); SEG.update(l,x); SEG.update(r+1,-x); break;
case 2:
if (l==r) puts("1"); else
puts(SEG.query_min(l+1,r)==0&&SEG.query_max(l+1,r)==0?"1":"0");
break;
case 3:
if (l==r) puts("1"); else
puts(SEG.query_min(l+1,r)>0?"1":"0");
break;
case 4:
if (l==r) puts("1"); else
puts(SEG.query_max(l+1,r)<0?"1":"0");
break;
case 5:
pos=SEG.query_pos(l+1,r);
if (pos==-1||pos==l+1) puts("0"); else
puts(SEG.query_max(pos,r)<0?"1":"0");
break;
}
}
return 0;
}
比特跳跃
徐神写的神秘优化建图题,我题意都不知道
#include <bits/stdc++.h>
using llsi = long long signed int;
void work() {
int n, m; llsi k;
std::cin >> n >> m >> k;
int nn = n;
for(int i = 0; i <= 20; ++i) n |= n >> i;
std::vector<std::vector<std::pair<int, llsi>>> out(2 * n + 1);
for(int i = 1, u, v, t; i <= m; ++i) {
std::cin >> u >> v >> t;
out[u].emplace_back(v, t);
out[v].emplace_back(u, t);
}
for(int i = 1; i <= n; ++i) {
out[i].emplace_back(i + n, i * k);
out[i + n].emplace_back(i, 0);
for(int j = 0; j < 20; ++j) {
int nxt = i ^ (1 << j);
if(nxt < i) {
out[i + n].emplace_back(nxt + n, 0);
} else if(nxt <= n) {
out[i + n].emplace_back(nxt + n, k << j);
}
}
}
std::vector<llsi> dis(2 * n + 1, 0x3f3f3f3f3f3f3f3fll);
std::vector<bool> vis(2 * n + 1, false);
std::priority_queue<std::pair<llsi, int>> pq;
pq.push({0, 1}), dis[1] = 0;
while(!pq.empty()) {
auto [_dis, cur] = pq.top(); pq.pop();
if(vis[cur]) continue;
vis[cur] = true;
for(auto [out, len]: out[cur]) if(dis[cur] + len < dis[out]) {
dis[out] = dis[cur] + len;
pq.push({-dis[out], out});
}
}
for(int i = 2; i <= nn; ++i) std::cout << dis[i] << char(i == n ? 10 : 32);
}
int main() {
std::ios::sync_with_stdio(false);
int t; std::cin >> t; while(t--) work();
return 0;
}
圣芙蕾雅
不可做题,弃疗
绘世之卷
开场看的就是这两个中二感爆棚的题,结果都是不可做题
抓拍
挺有意思的一个题
考虑把四个边界关于时间 \(t\) 的式子找出来,以 \(x\) 的最小值为例,我们先找出往西走的人的横坐标的最小值
如果存在横坐标更小的往其它方向走的人,那么 \(x\) 的最小值会在某个时刻变更表达式,因此可以得到一个分段函数
由于题目要求的是周长,所以任何时刻每条边长都是关于时间 \(t\) 的一次函数,因此找到分段函数所有分界点代回去暴力检验即可
#include<cstdio>
#include<iostream>
#include<set>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
const int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1};
int n,x[N],y[N],mnx[4],mxx[4],mny[4],mxy[4]; char s[10],ch[N];
signed main()
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
scanf("%lld%lld%s",&x[i],&y[i],s),ch[i]=s[0];
auto trs=[&](const char& ch)
{
switch (ch)
{
case 'E': return 0;
case 'W': return 1;
case 'S': return 2;
case 'N': return 3;
default: return -1;
}
};
for (i=0;i<4;++i) mnx[i]=mny[i]=INF,mxx[i]=mxy[i]=-INF;
for (i=1;i<=n;++i)
{
int tp=trs(ch[i]);
mnx[tp]=min(mnx[tp],x[i]);
mxx[tp]=max(mxx[tp],x[i]);
mny[tp]=min(mny[tp],y[i]);
mxy[tp]=max(mxy[tp],y[i]);
}
set <int> key; key.insert(0);
if (mxx[0]!=-INF)
{
int mx=max(mxx[2],mxx[3]);
if (mxx[0]<mx) key.insert(mx-mxx[0]);
mx=mxx[1];
if (mxx[0]<mx) key.insert((mx-mxx[0])/2),key.insert((mx-mxx[0]+1)/2);
}
if (mnx[1]!=INF)
{
int mn=min(mnx[2],mnx[3]);
if (mnx[1]>mn) key.insert(mnx[1]-mn);
mn=mnx[0];
if (mnx[1]>mn) key.insert((mnx[1]-mn)/2),key.insert((mnx[1]-mn+1)/2);
}
if (mny[2]!=INF)
{
int mn=min(mny[0],mny[1]);
if (mny[2]>mn) key.insert(mny[2]-mn);
mn=mny[3];
if (mny[2]>mn) key.insert((mny[2]-mn)/2),key.insert((mny[2]-mn+1)/2);
}
if (mxy[3]!=INF)
{
int mx=max(mxy[0],mxy[1]);
if (mxy[3]<mx) key.insert(mx-mxy[3]);
mx=mxy[2];
if (mxy[3]<mx) key.insert((mx-mxy[3])/2),key.insert((mx-mxy[3]+1)/2);
}
int ans=INF;
for (auto t:key)
{
int res=0,xl=INF,xr=-INF,yl=INF,yr=-INF;
for (i=1;i<=n;++i)
{
int tp=trs(ch[i]),nx=x[i]+t*dx[tp],ny=y[i]+t*dy[tp];
xl=min(xl,nx); xr=max(xr,nx);
yl=min(yl,ny); yr=max(yr,ny);
}
ans=min(ans,(xr-xl)+(yr-yl));
}
return printf("%lld",2LL*ans),0;
}
死亡之组
签到题,简单分类讨论即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int t, n, L, D, A[N], id[N];
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
cin >> n >> L >> D;
for (int i=1; i<=n; ++i) cin >> A[i], id[i]=i;
sort(id+2, id+n+1, [&](int a, int b){return A[a]<A[b];});
if (A[1]>=L){
if (A[id[4]]<L && A[1]-A[id[2]]>D){
cout << "Yes\n";
}else cout << "No\n";
}else{
if (A[id[3]]<L && A[id[n]]-min({A[id[1]], A[id[2]], A[id[3]]})>D){
cout << "Yes\n";
}else cout << "No\n";
}
}
return 0;
}
Postscript
一周四场多校还是有点爽歪歪的说
标签:钉耙,std,mn,return,cur,int,编程,2024,go From: https://www.cnblogs.com/cjjsb/p/18326139