隐藏页面特效

The 1st Universal Cup. Stage 12: Ōokayama

1|0Preface


久违地训练,因为昨天 ICPC 网络赛打的太唐不得不上点强度了

回到这场本身,由于中途发现了两个题被搬到去年暑假前集训队内赛了,导致经典提前没事干

2h15min 过了六个题后(有两个是原)就开始对着 L,M 发呆,虽然最后 4h45min 的时候把 M 开出来了,但还是说明做难题的水平不够(评价是霓虹场是这样的,好多 Counting 没办法发力)

由于这场题很多因此就只写过了的题/计划补的题了


2|0A. XOR Tree Path


签到,读完题就发现可以设状态 fx,0/1 表示在点 x 的子树内,修改的叶子数量为偶数/奇数时最多有几个黑点,转移显然

#include<cstdio> #include<iostream> #include<vector> #define RI register int #define CI const int& using namespace std; const int N=100005; int n,a[N],x,y,f[N][2]; vector <int> v[N]; inline void DFS(CI now=1,CI fa=0) { bool flag=1; for (auto to:v[now]) if (to!=fa) { DFS(to,now); if (flag) { for (RI i=0;i<2;++i) f[now][i]=f[to][i]; flag=0; } else { int g[2]={0,0}; for (RI p=0;p<2;++p) for (RI q=0;q<2;++q) g[p^q]=max(g[p^q],f[now][p]+f[to][q]); for (RI i=0;i<2;++i) f[now][i]=g[i]; } } ++f[now][a[now]^1]; } int main() { scanf("%d",&n); for (RI i=1;i<=n;++i) scanf("%d",&a[i]); for (RI i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x); DFS(),printf("%d",max(f[1][0],f[1][1])); //for (RI i=1;i<=n;++i) printf("f[%d][0] = %d; f[%d][1] = %d\n",i,f[i][0],i,f[i][1]); return 0; }

3|0B. Magical Wallet


祁神开场写的神秘模拟+ DP,我题目都没看

#include<bits/stdc++.h> using namespace std; const int INF = 1e9+5; const int N = 1e4+5; int n, x, A[105]; int dp[105][N]; void chmax(int &x, int a) { x = max(x, a); } int strToInt(const string &str) { int sz = str.size(); int res = 0, bas=1; for (int i=0; i<sz; ++i, bas*=10) { res += (str[i]-'0')*bas; } return res; } string intToStr(int x) { string res; while (x>0) res += (char)('0'+x%10), x/=10; return res; } signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >> n >> x; for (int i=1; i<=n; ++i) cin >> A[i]; auto makeSet = [&](int val) { vector<int> res; string X = intToStr(val); sort(X.begin(), X.end()); do { res.push_back(strToInt(X)); } while (next_permutation(X.begin(), X.end())); return res; }; vector<int> vec = makeSet(x); // printf("vec:"); for (int u : vec) printf("%d ", u); puts(""); for (int i=0; i<=n; ++i) for (int j=0; j<10000; ++j) dp[i][j] = -INF; for (int u : vec) dp[0][u]=0; for (int i=1; i<=n; ++i) { for (int j=0; j<10000; ++j) { chmax(dp[i][j], dp[i-1][j]); int val = j-A[i]; if (val < 0) continue; vec = makeSet(val); // if (dp[i-1][j]>=0) {printf("val=%d vec:", val); for (int u : vec) printf("%d ", u); puts("");} for (int u : vec) chmax(dp[i][u], dp[i-1][j]+1); } } int ans = 0; for (int j=0; j<10000; ++j) chmax(ans, dp[n][j]); cout << ans << '\n'; return 0; }

4|0E. Five Med Sum


就是 这个题,做法直接看当时写的吧

#include<cstdio> #include<iostream> #include<algorithm> #define int long long #define RI register int #define CI const int& using namespace std; const int N=100005,mod=998244353; int n,a[5][N],id[5],ans; inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; } signed main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j,k,s; for (scanf("%lld",&n),i=0;i<5;++i) for (j=1;j<=n;++j) scanf("%lld",&a[i][j]),a[i][j]=5LL*a[i][j]+i; for (i=0;i<5;++i) sort(a[i]+1,a[i]+n+1); for (i=0;i<5;++i) for (s=0;s<(1<<4);++s) { int cur=0; for (j=0;j<4;++j) if ((s>>j)&1) ++cur; if (cur!=2) continue; cur=0; for (j=0;j<5;++j) if (i!=j) id[j]=(s>>cur)&1,++cur; for (j=1;j<=n;++j) { int ret=1; for (k=0;k<5;++k) if (i!=k) { int coef=lower_bound(a[k]+1,a[k]+n+1,a[i][j])-a[k]-1; if (id[k]) coef=n-coef; ret=1LL*ret*coef%mod; } inc(ans,1LL*ret*((a[i][j]-i)/5LL)%mod); } } return printf("%lld",ans),0; }

5|0G. Range NEQ


虽然算是 Counting,但恰好是牢闪勉强会一点的简单容斥和简单多项式,推了会也就会了

有禁区的排列计数直接上棋盘多项式,不难发现就是在一个边长为 n×m 的棋盘上放 n×m 个棋子,其中禁区为按主对角线排布的 n 个单元,每个单元为 m×m 的矩形

考虑容斥计数,枚举有 i 个棋子放在禁区中,同时令 f(i) 表示放 i 个棋子在禁区中的方案数,则答案为:

i=0n×m(1)i×f(i)×(n×mi)!

现在考虑计算 f(i),不难想到一个朴素的 DP,令 gi,j 表示处理了前 i 个单元,此时一共放了 j 个棋子的方案数

考虑在一个单元里放 k[0,m] 个棋子的方案数为 (Cmk)2×k! ,以此为系数进行转移,即得到复杂度为 O(n2×m2) 的 DP

但这样做显然太劣了,考虑生成函数,对于一个单元格的生成函数 G(x)=k=0m(Cmk)2×k!×xk,不难发现 Gn(x) 就是我们想要的 f(i)

由于 G(x) 的次数为 n×m,因此可以大力转化为点值后快速幂后转回去,只用写 NTT 即可

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=1<<21,mod=998244353; int n,m,f[N],fact[N],ifac[N]; inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; } inline void dec(int& x,CI y) { if ((x-=y)<0) x+=mod; } 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; } namespace Poly { int rev[N],lim,p; inline void init(CI n) { for (lim=1,p=0;lim<=n;lim<<=1,++p); for (RI i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1); } inline void NTT(int *f,CI opt) { for (RI i=0;i<lim;++i) if (i<rev[i]) swap(f[i],f[rev[i]]); for (RI i=1;i<lim;i<<=1) { int m=i<<1,D=quick_pow(3,opt==1?(mod-1)/m:mod-1-(mod-1)/m); for (RI j=0;j<lim;j+=m) { int W=1; for (RI k=0;k<i;++k,W=1LL*W*D%mod) { int x=f[j+k],y=1LL*f[i+j+k]*W%mod; f[j+k]=f[i+j+k]=x; inc(f[j+k],y); dec(f[i+j+k],y); } } } if (!~opt) { int Inv=quick_pow(lim); for (RI i=0;i<lim;++i) f[i]=1LL*f[i]*Inv%mod; } } } int main() { scanf("%d%d",&n,&m); init(n*m); for (RI i=0;i<=m;++i) f[i]=1LL*C(m,i)*C(m,i)%mod*fact[i]%mod; Poly::init(n*m); Poly::NTT(f,1); for (RI i=0;i<Poly::lim;++i) f[i]=quick_pow(f[i],n); Poly::NTT(f,-1); int ans=0; //for (RI i=0;i<=n*m;++i) printf("%d%c",f[i]," \n"[i==n*m]); for (RI i=0;i<=n*m;++i) { int tmp=1LL*f[i]*fact[n*m-i]%mod; if (i&1) dec(ans,tmp); else inc(ans,tmp); } return printf("%d",ans),0; }

6|0J. Make Convex Sequence


直接把做法写在题目名里了

首先不难发现题目中给的限制等价于找到的所有点 (i,Ai) 形成一个下凸壳

手玩发现我们对所有 (i,Ri) 求下凸壳后,检验它是否和每一个线段有交即可,这样一定是最优的

#include<bits/stdc++.h> using namespace std; #define int long long using pii = pair<int, int>; const int N = 3e5+5; int n, L[N], R[N]; int stk[N], tp=-1; signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >> n; for (int i=1; i<=n; ++i) cin >> L[i]; for (int i=1; i<=n; ++i) cin >> R[i]; auto check = [&](int p, int a, int b) {return (R[a]-R[p])*(b-p) < (R[b]-R[p])*(a-p);}; auto check2 = [&](int p, int a, int b) {return (L[a]-R[p])*(b-p) < (R[b]-R[p])*(a-p);}; for (int i=1; i<=n; ++i) { while (tp>0 && !check(stk[tp-1], stk[tp], i)) --tp; stk[++tp] = i; } bool ok=true; int cur=1; for (int i=1; i<=n; ++i) { while (cur<=tp && stk[cur]<i) ++cur; if (!check2(stk[cur-1], i, stk[cur])) {ok=false; break;} } cout << (ok ? "Yes\n" : "No\n"); return 0; }

7|0L. Many Products


看过题人数是个应该要会的 Counting,等过两天有时间再补吧


8|0M. Colorful Graph


只能说这题上来就发现是个经典的 DAG 最小可重复路径覆盖,然后偷懒了想去网上找标准的做法,结果发现会连出 O(n2) 条边被卡空间

后面只能自己想结果发现是个很 trivial 的建图,首先还是把原 DAG 用拆分二分图的模型来建,具体地:

  • Si 连容量为 1 的边,且 n+iT 连容量为 1 的边;
  • 若原图中存在 xy 的边,则 xn+y 连容量为 的边;
  • n+ii 连容量为 的边;

这样建图后跑出的最大流上的边一定在原 DAG 的路径覆盖上,求方案数的话就沿着选中的边 DFS 一下,用并查集把同色的点合并一下即可

#include<cstdio> #include<iostream> #include<vector> #include<queue> #include<cstring> #define RI register int #define CI const int& using namespace std; const int N=7005,INF=1e9; int n,m,x,y,dfn[N],low[N],idx,stk[N],top,vis[N],bel[N],tot,fa[N],col[N]; vector <int> v[N]; inline void Tarjan(CI now) { dfn[now]=low[now]=++idx; stk[++top]=now; vis[now]=1; for (auto to:v[now]) if (!dfn[to]) Tarjan(to),low[now]=min(low[now],low[to]); else if (vis[to]) low[now]=min(low[now],dfn[to]); if (low[now]==dfn[now]) { bel[now]=++tot; vis[now]=0; while (stk[top]!=now) bel[stk[top]]=tot,vis[stk[top--]]=0; --top; } } namespace Network_Flow { const int NN=15005,MM=30005; struct edge { int to,nxt,v,tp; }e[MM<<1]; int cnt=1,head[NN],cur[NN],dep[NN],s,t; inline void addedge(CI x,CI y,CI z) { //printf("%d -> %d (%d)\n",x,y,z); e[++cnt]=(edge){y,head[x],z,z}; head[x]=cnt; e[++cnt]=(edge){x,head[y],0,-z}; head[y]=cnt; } #define to e[i].to inline bool BFS(void) { memset(dep,0,(t+1)*sizeof(int)); dep[s]=1; queue <int> q; q.push(s); while (!q.empty()) { int now=q.front(); q.pop(); for (RI i=head[now];i;i=e[i].nxt) if (e[i].v&&!dep[to]) dep[to]=dep[now]+1,q.push(to); } return dep[t]; } inline int DFS(CI now,CI tar,int dis) { if (now==tar) return dis; int ret=0; for (RI& i=cur[now];i&&dis;i=e[i].nxt) if (e[i].v&&dep[to]==dep[now]+1) { int dist=DFS(to,tar,min(dis,e[i].v)); if (!dist) dep[to]=INF; dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist; if (!dis) return ret; } if (!ret) dep[now]=INF; return ret; } inline bool travel(CI now,int& tar) { //printf("%d\n",now); if (now>tot) { for (RI i=head[now];i;i=e[i].nxt) if (to==t&&e[i].tp>0&&e[i].v!=e[i].tp) { ++e[i].v; tar=now; return 1; } } for (RI i=head[now];i;i=e[i].nxt) { //printf("to = %d; v = %d; tp = %d\n",to,e[i].v,e[i].tp); if (e[i].tp>0&&e[i].v!=e[i].tp) { ++e[i].v; if (travel(to,tar)) return 1; } } return 0; } #undef to inline int Dinic(int ret=0) { while (BFS()) memcpy(cur,head,(t+1)*sizeof(int)),ret+=DFS(s,t,INF); return ret; } }; using namespace Network_Flow; inline int getfa(CI x) { return fa[x]!=x?fa[x]=getfa(fa[x]):x; } int main() { scanf("%d%d",&n,&m); for (RI i=1;i<=m;++i) scanf("%d%d",&x,&y),v[x].push_back(y); for (RI i=1;i<=n;++i) if (!dfn[i]) Tarjan(i); for (RI x=1;x<=n;++x) for (auto y:v[x]) if (bel[x]!=bel[y]) addedge(bel[x],tot+bel[y],INF); //for (RI i=1;i<=n;++i) printf("%d%c",bel[i]," \n"[i==n]); s=0; t=2*tot+1; for (RI i=1;i<=tot;++i) addedge(s,i,1),addedge(tot+i,t,1),addedge(tot+i,i,INF); for (RI i=1;i<=tot;++i) fa[i]=i; Dinic(); int num=0; for (RI i=head[s];i;i=e[i].nxt) if (e[i].tp>0&&e[i].v!=e[i].tp) { int st=e[i].to,tar; ++e[i].v; travel(st,tar); //printf("%d %d\n",st,tar); fa[getfa(st)]=getfa(tar-tot); } for (RI i=1;i<=tot;++i) if (getfa(i)==i) col[i]=++num; for (RI i=1;i<=n;++i) printf("%d ",col[getfa(bel[i])]); return 0; }

9|0N. XOR Reachable


就是 这个题,做法直接看当时写的吧


10|0Postscript


犹记得去年这个时候一周 VP 四场还外加 CF/ATC 的补题,现在我一周啥也不干每天就是白兰

只能说该进入备战状态了,不出意外的话今年应该是个人算竞生涯的最后一舞了,希望能不留遗憾吧


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/18416735.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2023-09-16 CF677D Vanya and Treasure
点击右上角即可分享
微信分享提示