多校A层冲刺NOIP2024模拟赛24
多校A层冲刺NOIP2024模拟赛24
\(T1\) A. 选取字符串 \(100pts/100pts\)
-
考虑建出失配树,然后等价于询问 \(\sum\limits_{S \subseteq \{ 0,1,2, \dots ,n \},|S|=k}dep_{\operatorname{LCA}\{ S \}}^{2}\) 。
-
不妨从 \(\operatorname{LCA}\) 的角度考虑,统计 \(x\) 能作为多少个 \(|S|\) 的 \(\operatorname{LCA}\) 。但是这也不可做,考虑 \(x\) 对答案的贡献。
-
同 luogu P5305 [GXOI/GZOI2019] 旧词 ,将单个点深度平方的贡献 \(dep_{x}^{2}\) 差分成路径上所有点深度的贡献 \(\sum\limits_{i \in (0 \to x)}(dep_{i}^{2}-dep_{fa_{i}}^{2})=\sum\limits_{i \in (0 \to x)}(2dep_{i}-1)\) ,再乘以 \(\dbinom{siz_{x}}{k}\) 即可。
-
故 \(\sum\limits_{i=0}^{n}(2dep_{i}-1)\dbinom{siz_{i}}{k}\) 即为所求。
点击查看代码
const ll p=998244353; ll siz[1000010],dep[1000010],nxt[1000010],jc[1000010],inv[1000010],jc_inv[1000010],ans=0; char s[1000010]; vector<ll>e[1000010]; void add(ll u,ll v) { e[u].push_back(v); } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m])%p*jc_inv[m]%p:0; } void dfs(ll x,ll fa,ll k) { siz[x]=1; dep[x]=dep[fa]+1; for(ll i=0;i<e[x].size();i++) { dfs(e[x][i],x,k); siz[x]+=siz[e[x][i]]; } ans=(ans+(2*dep[x]%p-1+p)%p*C(siz[x],k,p)%p)%p; } int main() { #define Isaac #ifdef Isaac freopen("string.in","r",stdin); freopen("string.out","w",stdout); #endif ll n,k,i,j; scanf("%lld%s",&k,s+1); n=strlen(s+1); for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; } inv[1]=jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=n+1;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } for(i=1;i<=n;i++) { add(nxt[i],i); } dfs(0,n+1,k); printf("%lld\n",ans); return 0; }
\(T2\) B. 取石子 \(20pts/20pts\)
-
部分分
- \(20pts\) :暴力建博弈树。
点击查看代码
int a[50010]; vector<pair<int,int> >ans; bool dfs(int last,int n) { for(int i=1;i<=n;i++) { for(int j=1;j<=min(a[i],last);j++) { a[i]-=j; bool tmp=dfs(j,n); a[i]+=j; if(tmp==false) { return true; } } } return false; } bool work(int k,int n) { for(int i=1;i<=n;i++) { for(int j=1;j<=min(a[i],k);j++) { a[i]-=j; bool tmp=dfs(j,n); a[i]+=j; if(tmp==false) { ans.push_back(make_pair(i,j)); } } } return ans.size(); } int main() { #define Isaac #ifdef Isaac freopen("nim.in","r",stdin); freopen("nim.out","w",stdout); #endif int n,k,i; scanf("%d%d",&n,&k); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } sort(a+1,a+1+n); if(work(k,n)==true) { printf("1\n"); sort(ans.begin(),ans.end()); for(i=0;i<ans.size();i++) { printf("%d %d\n",ans[i].first,ans[i].second); } } else { printf("0\n"); } return 0; }
-
正解
- 容易得到的博弈策略:
- 当 \(\sum\limits_{i=1}^{n}a_{i}\) 是奇数时,先手必胜,每次只取 \(1\) 个即可。
- 否则,先手最优一定取偶数个且后面每个人能取偶数个就只能取偶数个,可以递归至 \(k \gets \left\lfloor \frac{k}{2} \right\rfloor,a_{i} \gets \left\lfloor \frac{a_{i}}{2} \right\rfloor\) 。
- 解得先手必胜当且仅当存在 \(t \in [0,\left\lfloor \log_{2}k \right\rfloor]\) 使得 \((\sum\limits_{i=1}^{n}\left\lfloor \frac{a_{i}}{2^{t}} \right\rfloor) \equiv 1 \pmod{2}\) ,即 \((\bigoplus\limits_{i=1}^{n}a_{i}) \not\equiv {0} \pmod{2^{t}}\) ,可进一步规约至 \((\bigoplus\limits_{i=1}^{n}a_{i}) \not\equiv {0} \pmod{2^{\left\lfloor \log_{2}k \right\rfloor}}\) 。
- 先手第一步必胜的策略一定是取 \((2x+1) \times \nu_{2}(\bigoplus\limits_{i=1}^{n}a_{i})=(2x+1) \times \operatorname{lowbit}(\bigoplus\limits_{i=1}^{n}a_{i})\) 个石子。枚举第一次取的堆 \(i \in [1,n]\) ,同时枚举先手能取的二进制各位(能取的一定是将 \(a_{i}\) 取出部分石子后与其他石子的异或和二进制表示下 \(1\) 的位置),当 \(>a_{i} \lor >k\) 时及时
break
。
点击查看代码
int a[50010]; int lowbit(int x) { return (x&(-x)); } int main() { #define Isaac #ifdef Isaac freopen("nim.in","r",stdin); freopen("nim.out","w",stdout); #endif int n,k,sum=0,tmp,ans,i,j; scanf("%d%d",&n,&k); for(i=1;i<=n;i++) { scanf("%d",&a[i]); sum^=a[i]; } if(sum==0||lowbit(sum)>k) { printf("0\n"); } else { printf("1\n"); for(i=1;i<=n;i++) { ans=0; tmp=sum; for(j=0;j<=30;j++) { if((tmp>>j)&1) { tmp^=(a[i]-ans); ans+=(1<<j); if(ans>a[i]||ans>k) { break; } tmp^=(a[i]-ans); printf("%d %d\n",i,ans); } } } } return 0; }
- 容易得到的博弈策略:
\(T3\) C. 均衡区间 \(25pts/25pts\)
-
部分分
-
测试点 \(1 \sim 2,4 \sim 6\):模拟。
点击查看代码
int a[1000010],ans[1000010]; void work(int n) { memset(ans,0,sizeof(ans)); for(int i=1;i<=n;i++) { int minn=0x7f7f7f7f,maxx=0; for(int j=i;j<=n;j++) { minn=min(minn,a[j]); maxx=max(maxx,a[j]); if(minn!=min(a[i],a[j])&&maxx!=max(a[i],a[j])) { ans[i]++; } } } } int main() { #define Isaac #ifdef Isaac freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); #endif int n,id,i; scanf("%d%d",&n,&id); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } work(n); for(i=1;i<=n;i++) { printf("%d ",ans[i]); } printf("\n"); reverse(a+1,a+1+n); work(n); reverse(ans+1,ans+1+n); for(i=1;i<=n;i++) { printf("%d ",ans[i]); } printf("\n"); return 0; }
-
测试点 \(3\) : 不横跨 \(i\) 时端点处一定同时为最值,横跨 \(i\) 时端点处一定有至少一个取到最小值,故输出 \(0\) 。
-
-
正解
- 以求解左端点为例。
- 设 \(xl_{i},nl_{i},xr_{i},nr_{i}\) 分别表示 \(i\) 左侧第一个比它大的数,左侧第一个比它小的数,右侧第一个比它大的数,右侧第一个比它小的数,单调栈预处理即可。
- 当 \(i\) 不为最值时,右端点 \(j\) 需满足 \((i,j]\) 中出现了 \(>a_{i}\) 和 \(<a_{i}\) 的数,即 \(j \ge \max(xr_{i},nr_{i})\) ;但又需要保证 \(a_{j}\) 不为最值,类似地有 \(i \le \min(xl_{j},nl_{j})\) 。
- 因空间略卡,使用扫描线加树状数组维护二维数点即可。
点击查看代码
struct BIT { int c[1000010]; int lowbit(int x) { return (x&(-x)); } void clear() { memset(c,0,sizeof(c)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; struct node { int pos,x,val,id; }q[2000010]; int a[1000010],L[1000010],R[1000010],ans[1000010],cnt; stack<int>s1,s2; bool cmp(node a,node b) { return a.pos<b.pos; } void add(int pos,int x,int val,int id) { cnt++; q[cnt].pos=pos; q[cnt].x=x; q[cnt].val=val; q[cnt].id=id; } void work(int n) { cnt=0; memset(q,0,sizeof(q)); memset(ans,0,sizeof(ans)); while(s1.empty()==0) { s1.pop(); } while(s2.empty()==0) { s2.pop(); } for(int i=1;i<=n;i++) { while(s1.empty()==0&&a[s1.top()]<=a[i]) { s1.pop(); } while(s2.empty()==0&&a[s2.top()]>=a[i]) { s2.pop(); } L[i]=min((s1.empty()==0)?s1.top():0,(s2.empty()==0)?s2.top():0); s1.push(i); s2.push(i); } while(s1.empty()==0) { s1.pop(); } while(s2.empty()==0) { s2.pop(); } for(int i=n;i>=1;i--) { while(s1.empty()==0&&a[s1.top()]<=a[i]) { s1.pop(); } while(s2.empty()==0&&a[s2.top()]>=a[i]) { s2.pop(); } R[i]=max((s1.empty()==0)?s1.top():n+1,(s2.empty()==0)?s2.top():n+1); s1.push(i); s2.push(i); if(R[i]<=n) { add(R[i]-1,i,-1,i); add(n,i,1,i); } } sort(q+1,q+1+cnt,cmp); for(int i=1,j=1;i<=cnt;i++) { for(;j<=q[i].pos;j++) { if(L[j]>=1) { T.add(n,L[j],1); } } ans[q[i].id]+=q[i].val*(T.getsum(n)-T.getsum(q[i].x-1)); } } int main() { #define Isaac #ifdef Isaac freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); #endif int n,id,i; scanf("%d%d",&n,&id); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } work(n); for(i=1;i<=n;i++) { printf("%d ",ans[i]); } printf("\n"); reverse(a+1,a+1+n); work(n); reverse(ans+1,ans+1+n); for(i=1;i<=n;i++) { printf("%d ",ans[i]); } printf("\n"); return 0; }
\(T4\) D. 禁止套娃 \(10pts/10pts\)
-
部分分
-
\(10pts\) :爆搜求本质不同子序列个数。
点击查看代码
const ll p=1000000007; int a[5010],ans=0; vector<int>state,tmp; map<vector<int>,bool>s1,s2; void dfs2(int pos) { if(pos==state.size()) { if(s2.find(tmp)==s2.end()) { s2[tmp]=1; ans=(ans+1)%p; } } else { dfs2(pos+1); tmp.push_back(state[pos]); dfs2(pos+1); tmp.pop_back(); } } void dfs(int pos,int n) { if(pos==n+1) { if(s1.find(state)==s1.end()) { s2.clear(); dfs2(0); s1[state]=1; } } else { dfs(pos+1,n); state.push_back(a[pos]); dfs(pos+1,n); state.pop_back(); } } int main() { #define Isaac #ifdef Isaac freopen("nest.in","r",stdin); freopen("nest.out","w",stdout); #endif int n,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } dfs(1,n); cout<<ans<<endl; return 0; }
-
-
正解
- 一个元素的贡献会来自从属关系的内外两层,考虑枚举内层的选择情况,记录选择的两个相邻内层点间的外层方案数量。
- 内外层的限制条件均为设相邻两个数分别为 \(x,y\) ,则 \(a_{x+1 \sim y-1}\) 中不存在 \(=a_{y}\) 的值。
- 设 \(f_{i}\) 表示处理到 \(i\) 时内外层末尾均选择 \(i\) 的方案数,考虑从 \(f_{j}(j \in [0,i-1])\) 转移时,考虑 \(a_{j+1 \sim i-1}\) 如何选择外层,设选择的下标集合为 \(S\) (不妨钦定内部元素单调递增),则 \(S\) 需满足以下限制条件。
- \(S\) 中相邻两个数 \(x,y\) 满足 \(a_{x+1 \sim y-1}\) 中不存在 \(=a_{y}\) 的数。
- \(a_{\max\{ S \}+1 \sim i-1}\) 中不存在 \(=a_{i}\) 的数。
- \(\forall x \in S,a_{x} \ne a_{i}\) .
- 限制条件 \(1,3\) 是容易进行状态设计并转移的,但限制条件 \(2\) 难以处理,考虑计算出总方案数再减去不合法方案数。具体地,设 \(g_{i,j}\) 表示在 \([i+1,j-1]\) 中选取外层的合法方案数,则有 \(f_{i}=\sum\limits_{j=0}^{i-1}(g_{j,i}-g_{j,pre_{i}})f_{j}\) 。
- 边界为 \(f_{0}=1\) ,最后钦定一个必选点 \(n+1\) 用于统计答案即可。
- 难点来到了怎么处理 \(\{ g \}\) ,仍考虑前缀和优化。不妨从右往左回退处理,沿途设 \(h_{j}\) 表示 \([j+1,i-1]\) 的合法本质不同子序列数量,转移同 \(30pts\) 中第一种写法。
点击查看代码
const int p=1000000007; int a[5010],f[5010],g[5010][5010],h[5010],last[5010],pre[5010],suf[5010]; int main() { #define Isaac #ifdef Isaac freopen("nest.in","r",stdin); freopen("nest.out","w",stdout); #endif int n,sum,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; pre[i]=last[a[i]]; last[a[i]]=i; } fill(last+1,last+1+n,n+1); for(i=n;i>=1;i--) { suf[i]=last[a[i]]; last[a[i]]=i; } f[0]=1; for(i=1;i<=n+1;i++) { sum=1; for(j=i-1;j>=0;j--) { g[j][i]=sum; if(a[i]!=a[j]) { h[j]=sum; sum=(2*sum%p-h[suf[j]]+p)%p;//对应原 30pts 中的减去 pre 的贡献 } } for(j=0;j<=i-1;j++) { f[i]=(f[i]+1ll*(g[j][i]-g[j][pre[i]]+p)%p*f[j]%p)%p; } } cout<<f[n+1]<<endl; return 0; }
总结
- \(T3\) 性质场上没推出来。
后记
- 下发的题面 \(PDF\) 和原题解 \(PDF\) 的 \(\LaTeX\) 炸了。
- \(T1\) 题面 \(i,j\) 写反了。
- \(T3\) 题解 \(i \le L_{j}\) 打成了 \(i \ge L_{j}\) 。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18555355,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。