Petrozavodsk Summer 2024. Day 1. Welcome Contest
Preface
整个国庆就训两天,摆烂这一块
这场感觉前期题里计数偏多,只能说毛子场是这样的;然后有人写 D 的时候猪脑过载,占了 90 min 机时导致队友早就会了的 B 没时间写,我不说是谁
A. Adding Integers
把组合式子拆开发现对原序列求差分 \(d_i=a_i-a_{i-1}\) 后,组合意义为:把 \(n\) 个球放到 \(q+1\) 个盒子里,其中第 \(i\) 个盒子里放 \(d_i\) 个球
对所有的 \(\{d\}\) 形态求和后就是 \(n\) 个带标号球放 \(q+1\) 个带标号盒子,方案数为 \((q+1)^n\),数据范围允许暴力快速幂通过
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int mod=998244353;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
int t,n,k;
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&k);
int ans=0;
for (RI i=1;i<=k;++i)
(ans+=quick_pow(i+1,n))%=mod;
printf("%d\n",ans);
}
return 0;
}
B. Bottles
把所有东西都看成有标号的,然后考虑枚举第一次喝解药的位置 \(fir\)
首先把剩下的 \(e-1\) 解药填入后面 \(e+p+w-fir\) 个位置里,然后考虑从发作时间短的毒药开始考虑
令 \(pos=\max(0,fir-t-1)\),则每个会致死的毒药都恰有 \(e+p+w-e-pos=p+w-pos\) 种选法
对于剩下的毒药和水,放哪里都无所谓,贡献自然很好维护
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,mod=998244353;
int e,p,w,t,fact[N],ifac[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
fact[0]=1;
for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
ifac[n]=quick_pow(fact[n]);
for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<0||m<0||n<m) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
scanf("%d%d%d%d",&e,&p,&w,&t);
init(e+p+w); int ans=0;
for (RI fir=1;fir<=e+p+w;++fir)
{
int cur=1LL*e*C(e+p+w-fir,e-1)%mod*fact[e-1]%mod;
int pos=max(0,fir-t-1);
cur=1LL*cur*quick_pow(max(0,p+w-pos),min(pos,p))%mod;
cur=1LL*cur*C(p+w-pos,max(0,p-pos))%mod*fact[max(0,p-pos)]%mod;
cur=1LL*cur*fact[w]%mod;
(ans+=cur)%=mod;
}
return printf("%d",1LL*ans*ifac[e+p+w]%mod),0;
}
C. Counting Orthogonal Pairs
队友开局推的
#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL solve() {
int x; cin >> x;
if (x % 2 == 1) return 0;
else return 1LL* (x/2-1) * x;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) cout << solve() << '\n';
return 0;
}
D. Divine Tree
随便选一个点当根,考虑最后如果是把所有的 G
扔到子树 \(x\) 里(移动 B
进去的方案同理)
这个代价等价于把 \(x\) 子树里所有 B
移动到点 \(x\) 的距离和,再加上把子树 \(x\) 外部的所有 B
移动到 \(x\) 的距离和
由于修改不改变每个点的信息,因此答案只能在一部分点出取得,可以预处理出这些位置
考虑对一条边 \(x\to y\) 的修改会影响三部分的答案,这个需要画图手动推导一下
最终要实现的就是子树加,树上路径加,以及抛出掉上述两种情况的部分再整体加
写题解的时候想到一个好的实现方法时先全局加上第三种情况的值,然后前两种情况的增量减去第三种情况的增量即可
但当时实现的时候没管那么多了,直接暴力把拆分出的区间(个数是 \(\log\) 级别的)拎出来把中间的补暴力跑一遍修改,复杂度也没啥区别
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<assert.h>
#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],anc[N],u[N],v[N],w[N],key[N],seq[N],q,e[N],d[N],ans[N];
char s[N]; vector <int> E[N];
class Segment_Tree
{
private:
int mn[N<<2],tag[N<<2];
inline void apply(CI now,CI mv)
{
mn[now]+=mv; tag[now]+=mv;
}
inline void pushup(CI now)
{
mn[now]=min(mn[now<<1],mn[now<<1|1]);
}
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)
{
tag[now]=0; if (l==r) return (void)(mn[now]=(key[seq[l]]?0:INF));
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
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);
pushup(now);
}
inline int query(void)
{
return mn[1];
}
#undef TN
#undef LS
#undef RS
}SEG;
vector <pair <int,int>> itv;
namespace Heavy_Division
{
int dep[N],dfn[N],top[N],idx,sz[N],son[N];
inline void DFS1(CI now=1,CI fa=0)
{
sz[now]=1; dep[now]=dep[fa]+1; anc[now]=fa;
for (auto to:E[now])
{
if (to==fa) continue;
DFS1(to,now); sz[now]+=sz[to];
if (sz[to]>sz[son[now]]) son[now]=to;
}
}
inline void DFS2(CI now=1,CI tf=1)
{
seq[dfn[now]=++idx]=now; top[now]=tf;
if (son[now]) DFS2(son[now],tf);
for (auto to:E[now])
{
if (to==anc[now]||to==son[now]) continue;
DFS2(to,to);
}
}
inline void modify_path(int x,CI mv)
{
// printf("x = %lld\n",x);
while (top[x]!=top[1])
{
itv.emplace_back(dfn[top[x]],dfn[x]);
// printf("(%lld, %lld) + %lld\n",dfn[top[x]],dfn[x],mv);
SEG.modify(dfn[top[x]],dfn[x],mv);
x=anc[top[x]];
}
// printf("(%lld, %lld) + %lld\n",dfn[1],dfn[x],mv);
itv.emplace_back(dfn[1],dfn[x]);
SEG.modify(dfn[1],dfn[x],mv);
}
inline void modify_sbt(CI x,CI mv)
{
itv.emplace_back(dfn[x],dfn[x]+sz[x]-1);
SEG.modify(dfn[x],dfn[x]+sz[x]-1,mv);
}
};
using namespace Heavy_Division;
int szb[N],szg[N];
inline void DFS3(CI G,CI now=1,CI fa=0)
{
szg[now]=szb[now]=0;
if (a[now]) ++szg[now]; else ++szb[now];
for (auto to:E[now])
{
if (to==fa) continue;
DFS3(G,to,now);
szg[now]+=szg[to];
szb[now]+=szb[to];
}
}
inline void solve(CI G)
{
for (RI i=1;i<=n;++i)
key[i]=(sz[i]==G);
DFS3(G); SEG.build();
// printf("key: ");
// for (RI i=1;i<=n;++i)
// if (key[i]) printf("%d ",i);
// putchar('\n');
auto update=[&](CI x,CI y,CI d)
{
itv.clear();
modify_path(x,szb[y]*d);
modify_sbt(y,(G-szg[y])*d);
sort(itv.begin(),itv.end());
for (RI i=0;i+1<(int)itv.size();++i)
assert(itv[i].second<=itv[i+1].first);
int lst=1;
for (auto [l,r]:itv)
{
assert(l<=r);
if (lst<=l-1)
{
// printf("(%lld, %lld) + %lld\n",lst,l-1,szg[y]*d);
SEG.modify(lst,l-1,szg[y]*d);
}
lst=r+1;
}
if (lst<=n) SEG.modify(lst,n,szg[y]*d);
};
for (RI i=1;i<n;++i)
{
int x=u[i],y=v[i];
if (dep[x]>dep[y]) swap(x,y);
// printf("x = %lld, y = %lld, d = %lld\n",x,y,d[i]);
update(x,y,w[i]);
}
for (RI i=1;i<=q;++i)
{
int x=u[e[i]],y=v[e[i]];
if (dep[x]>dep[y]) swap(x,y);
// printf("x = %lld, y = %lld, d = %lld\n",x,y,d[i]);
update(x,y,d[i]);
ans[i]=min(ans[i],SEG.query());
}
}
signed main()
{
scanf("%lld%s",&n,s+1);
int G=0,B=0;
for (RI i=1;i<=n;++i)
if (s[i]=='G') ++G; else ++B;
for (RI i=1;i<n;++i)
{
scanf("%lld%lld%lld",&u[i],&v[i],&w[i]);
E[u[i]].emplace_back(v[i]);
E[v[i]].emplace_back(u[i]);
}
scanf("%lld",&q);
for (RI i=1;i<=q;++i)
scanf("%lld%lld",&e[i],&d[i]),ans[i]=INF;
DFS1(); DFS2();
for (RI i=1;i<=n;++i) a[i]=(s[i]=='G');
solve(G);
for (RI i=1;i<=n;++i) a[i]=(s[i]=='B');
solve(B);
for (RI i=1;i<=q;++i) printf("%lld\n",ans[i]);
return 0;
}
E. Experiments With Divine Trees
这个题队友好像也会了,同样没时间写;感觉这场我的博弈和 DS 出的慢要背大锅啊
H. Heroes and Illusions
这题纯队友讨论出来的,我也不知道做法
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5+5;
const int MOD = 998244353;
void mul(int &x, int a) { x=1LL*x*a%MOD; }
LL sqr(int x) {return 1LL*x*x;}
int n, fac[N], invf[N];
LL k;
int powe(int x, int p) {
int res = 1;
while (p>0) {if (p&1) mul(res, x); mul(x, x); p>>=1;}
return res;
}
int inv(int x) {return powe(x, MOD-2);}
int C(int n, int m) {
return 1LL*fac[n]*invf[m]%MOD*invf[n-m]%MOD;
}
int solve() {
cin >> n >> k;
if (0 == k) return 1;
if (1LL*(n+1)*(n+1) < 4*k) return 0;
LL det = sqr(n+1)-4*k;
if (sqr(int(sqrtl(det))) != det) return 0;
det = int(sqrtl(det));
if (n+1 < det || (n+1-det)%2 != 0) return 0;
int i = (n+1-det)/2;
if (i == k/i) return C(n, i)%MOD;
else return (C(n, i) + C(n, k/i))%MOD;
return 0;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
fac[0] = fac[1] = invf[0] = invf[1] = 1;
for (int i=2; i<N; ++i) fac[i] = 1LL*fac[i-1]*i%MOD;
invf[N-1] = inv(fac[N-1]);
for (int i=N-1; i>1; --i) invf[i-1] = 1LL*invf[i]*i%MOD;
int T; cin >> T; while (T--) cout << solve() << '\n';
return 0;
}
J. Jumping Game
首先根据经典的图上博弈模型,考虑将图黑白染色并把能走到的点连边
先手必败的条件等价去掉起始点时剩下的图有完美匹配
不难发现当 \(r,c\) 都比较大时,偶数个点一定先手必胜(因为剩下点个数为奇数),奇数个点时也可以认为剩下部分能完美匹配
但是 \(r,c\) 较小的时候就是会有需要讨论的 Corner Case,然后我们只需要暴力打个表找出这些情况即可,\(\min(r,c)\le 2\) 的情况是 trivial 的
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,r,c,a,b;
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d%d%d",&r,&c,&a,&b);
if (r>c) swap(r,c),swap(a,b);
if (r==1) { puts("Brahma"); continue; }
if (r==2)
{
int left=(b-1)/2,right=(c-b)/2;
if (left%2==1||right%2==1) puts("Annapurna");
else puts("Brahma");
continue;
}
if (r==3&&c==3)
{
puts(a==2&&b==2?"Brahma":"Annapurna");
continue;
}
if (r==3&&c==5)
{
if ((a+b)%2==1) { puts("Annapurna"); continue; }
if ((a==1&&b==3)||(a==3&&b==3)) puts("Annapurna");
else puts("Brahma");
continue;
}
if (r%2==0||c%2==0) puts("Annapurna");
else puts((a+b)%2==0?"Brahma":"Annapurna");
}
return 0;
}
K. Kangaroo On Graph
由于图是个 DAG,因此可以拓扑排序求最短路
对于每个点 \(x\),把所有能到达它的路径权值 \(val\) 和前驱点 \(pre\) 看作二元组并排序存下来
对于其所有出边我们暴力在里面找个最优的更新即可,分析一波复杂度是线性的
#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#include<queue>
#include<array>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
typedef pair <int,int> pi;
int n,m,k,deg[N]; vector <pi> f[N];
vector <pi> v[N]; set <array <int,3>> ban;
signed main()
{
scanf("%lld%lld",&n,&m);
for (RI i=1;i<=m;++i)
{
int x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
v[x].push_back({y,z}); ++deg[y];
}
scanf("%lld",&k);
for (RI i=1;i<=k;++i)
{
int a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
ban.insert({a,b,c});
}
queue <int> q; f[1].push_back({0,0});
for (RI i=1;i<=n;++i) if (deg[i]==0) q.push(i);
while (!q.empty())
{
int now=q.front(); q.pop();
sort(f[now].begin(),f[now].end());
for (auto [to,w]:v[now])
{
int val=INF;
for (auto [dis,lst]:f[now])
{
if (ban.count({lst,now,to})) continue;
val=dis; break;
}
if (val!=INF) f[to].push_back({val+w,now});
if (--deg[to]==0) q.push(to);
}
}
if (f[n].empty()||f[n][0].first>=INF) puts("-1"); else printf("%lld",f[n][0].first);
return 0;
}
L. Low Cost Set
首先不难发现把所有的区间都扔到集合里一定是个合法解
考虑如果存在两个区间 \([a,b),[c,d)\),满足 \(a<c<b<d\)(即它们有交),那么我们可以把它们拆成 \([a,c),[c,b),[b,d)\),以此减少 \([b,c)\) 的贡献
不难发现由于拆分只能进行一次,因此两个区间配对拆分后就不能再进行操作了
把能配对拆分的区间之间连对应权值的边,转化为一般图的最大权匹配,然后去 UOJ 上抄个老哥的板子跑一跑就行
#include<bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(),(x).end()
namespace weighted_blossom_tree
{
#define d(x) (lab[x.u]+lab[x.v]-e[x.u][x.v].w*2)
const int N=105*2;//两倍点数
typedef long long ll;//总和大小
typedef ll T;//权值大小
//均不允许无符号
const T inf=numeric_limits<int>::max()>>1;
struct Q
{
int u,v;
T w;
} e[N][N];
T lab[N];
int n,m=0,id,h,t,lk[N],sl[N],st[N],f[N],b[N][N],s[N],ed[N],q[N];
vector<int> p[N];
void upd(int u,int v) {if (!sl[v]||d(e[u][v])<d(e[sl[v]][v])) sl[v]=u;}
void ss(int v)
{
sl[v]=0;
for (int u=1;u<=n;u++) if (e[u][v].w>0&&st[u]!=v&&!s[st[u]]) upd(u,v);
}
void ins(int u) {if (u<=n) q[++t]=u; else for (int v:p[u]) ins(v);}
void mdf(int u,int w)
{
st[u]=w;
if (u>n) for (int v:p[u]) mdf(v,w);
}
int gr(int u,int v)
{
if ((v=find(all(p[u]),v)-p[u].begin())&1)
{
reverse(1+all(p[u]));
return (int)p[u].size()-v;
}
return v;
}
void stm(int u,int v)
{
lk[u]=e[u][v].v;
if (u<=n) return;
Q w=e[u][v];
int x=b[u][w.u],y=gr(u,x),i;
for (i=0;i<y;i++) stm(p[u][i],p[u][i^1]);
stm(x,v);
rotate(p[u].begin(),y+all(p[u]));
}
void aug(int u,int v)
{
int w=st[lk[u]];
stm(u,v);
if (!w) return;
stm(w,st[f[w]]);
aug(st[f[w]],w);
}
int lca(int u,int v)
{
for (++id;u|v;swap(u,v))
{
if (!u) continue;
if (ed[u]==id) return u;
ed[u]=id;//??????????v?? 这是原作者的注释,我也不知道是啥
if (u=st[lk[u]]) u=st[f[u]];
}
return 0;
}
void add(int u,int a,int v)
{
int x=n+1,i,j;
while (x<=m&&st[x]) ++x;
if (x>m) ++m;
lab[x]=s[x]=st[x]=0;lk[x]=lk[a];
p[x].clear();p[x].push_back(a);
for (i=u;i!=a;i=st[f[j]]) p[x].push_back(i),p[x].push_back(j=st[lk[i]]),ins(j);//复制,改一处
reverse(1+all(p[x]));
for (i=v;i!=a;i=st[f[j]]) p[x].push_back(i),p[x].push_back(j=st[lk[i]]),ins(j);
mdf(x,x);
for (i=1;i<=m;i++) e[x][i].w=e[i][x].w=0;
memset(b[x]+1,0,n*sizeof b[0][0]);
for (int u:p[x])
{
for (v=1;v<=m;v++) if (!e[x][v].w||d(e[u][v])<d(e[x][v])) e[x][v]=e[u][v],e[v][x]=e[v][u];
for (v=1;v<=n;v++) if (b[u][v]) b[x][v]=u;
}
ss(x);
}
void ex(int u) // s[u] == 1
{
for (int x:p[u]) mdf(x,x);
int a=b[u][e[u][f[u]].u],r=gr(u,a),i;
for (i=0;i<r;i+=2)
{
int x=p[u][i],y=p[u][i+1];
f[x]=e[y][x].u;
s[x]=1;s[y]=0;
sl[x]=0;ss(y);
ins(y);
}
s[a]=1;f[a]=f[u];
for (i=r+1;i<p[u].size();i++) s[p[u][i]]=-1,ss(p[u][i]);
st[u]=0;
}
bool on(const Q &e)
{
int u=st[e.u],v=st[e.v],a;
if(s[v]==-1)
{
f[v]=e.u;s[v]=1;
a=st[lk[v]];
sl[v]=sl[a]=s[a]=0;
ins(a);
}
else if(!s[v])
{
a=lca(u,v);
if (!a) return aug(u,v),aug(v,u),1;
else add(u,a,v);
}
return 0;
}
bool bfs()
{
memset(s+1,-1,m*sizeof s[0]);
memset(sl+1,0,m*sizeof sl[0]);
h=1;t=0;
int i,j;
for (i=1;i<=m;i++) if (st[i]==i&&!lk[i]) f[i]=s[i]=0,ins(i);
if (h>t) return 0;
while (1)
{
while (h<=t)
{
int u=q[h++],v;
if (s[st[u]]!=1) for (v=1; v<=n;v++) if (e[u][v].w>0&&st[u]!=st[v])
{
if (d(e[u][v])) upd(u,st[v]); else if (on(e[u][v])) return 1;
}
}
T x=inf;
for (i=n+1;i<=m;i++) if (st[i]==i&&s[i]==1) x=min(x,lab[i]>>1);
for (i=1;i<=m;i++) if (st[i]==i&&sl[i]&&s[i]!=1) x=min(x,d(e[sl[i]][i])>>s[i]+1);
for (i=1;i<=n;i++) if (~s[st[i]]) if ((lab[i]+=(s[st[i]]*2-1)*x)<=0) return 0;
for (i=n+1;i<=m;i++) if (st[i]==i&&~s[st[i]]) lab[i]+=(2-s[st[i]]*4)*x;
h=1;t=0;
for (i=1;i<=m;i++) if (st[i]==i&&sl[i]&&st[sl[i]]!=i&&!d(e[sl[i]][i])&&on(e[sl[i]][i])) return 1;
for (i=n+1;i<=m;i++) if (st[i]==i&&s[i]==1&&!lab[i]) ex(i);
}
return 0;
}
template<typename TT> ll max_weighted_general_match(int N,const vector<tuple<int,int,TT>> &edges)//[1,n],返回权值
{
memset(ed+1,0,m*sizeof ed[0]);
memset(lk+1,0,m*sizeof lk[0]);
n=m=N;id=0;
iota(st+1,st+n+1,1);
int i,j;
T wm=0;
ll r=0;
for (i=1;i<=n;i++) for (j=1;j<=n;j++) e[i][j]={i,j,0};
for (auto [u,v,w]:edges) wm=max(wm,e[v][u].w=e[u][v].w=max(e[u][v].w,(T)w));
for (i=1;i<=n;i++) p[i].clear();
for (i=1;i<=n;i++) for (j=1;j<=n;j++) b[i][j]=i*(i==j);
fill_n(lab+1,n,wm);
while (bfs());
for (i=1;i<=n;i++) if (lk[i]) r+=e[i][lk[i]].w;
return r/2;
}
#undef d
}
using weighted_blossom_tree::max_weighted_general_match,weighted_blossom_tree::lk;
#define RI register int
const int N=105;
int k,l[N],r[N],A[N<<1];
int main()
{
scanf("%d",&k);
for (RI i=1;i<=k;++i)
scanf("%d%d",&l[i],&r[i]);
for (RI i=1;i<2*k;++i)
scanf("%d",&A[i]);
long long ans=0;
for (RI i=1;i<=k;++i)
for (RI j=l[i];j<r[i];++j) ans+=A[j];
vector<tuple<int,int,long long>> edges;
for (RI i=1;i<=k;++i)
for (RI j=1;j<=k;++j)
{
if (i==j) continue;
int a=l[i],b=r[i],c=l[j],d=r[j];
if (a<c&&c<b&&b<d)
{
long long sum=0;
for (RI s=c;s<b;++s) sum+=A[s];
edges.emplace_back(i,j,sum);
}
}
printf("%lld",ans-max_weighted_general_match(k,edges));
return 0;
}
Postscript
争取 Regional 前保持一周两训吧,能做到吗(还剩两次!)