[题解][test05]2024/11/21 模拟赛 / 2023牛客OI赛前集训营-提高组(第二场) A~B
整套都是牛客的原题所以就不设密码了(
原题页面:https://ac.nowcoder.com/acm/contest/65193
Statements & Solution:https://www.luogu.com.cn/problem/U507206
Solution:https://www.nowcoder.com/discuss/540225827162583040
\(60+30+20+20=130\)。
每日挂分之T2线段树不开\(4\)倍+\(10^6\)数量级输入不关同步流,\(\bf\colorbox{MidnightBlue}{\texttt{\color{White}{TLE}}}\ \ 100\to 30\)。
A
赛时光想着\(f[i][j]\)表示\(i\)个元素选\(j\)个的答案,磕了半天没想出来。
实际上这道题应该从值域为\(n\)这一特征入手,这子集之和最大是\(\frac{n(n+1)}{2}\)。
因此我们考虑枚举子集之和\(x\),用dp求出有多少种选法能达到这个子集和\(y\)。答案即为所有\(x^y\)相乘,用快速幂优化一下。
但是\(y\)可能非常大,但它作为指数不能直接取模。我们可以利用欧拉定理(其实就是费马小定理的推广)\(a^{\varphi(m)}\equiv1\pmod n\),将指数对\(\varphi(998244353)=998244352\)取模即可。
时间复杂度\(O(n^3)\)。
点击查看代码
#include<bits/stdc++.h> #define int long long #define N 202 #define mod 998244353 using namespace std; int n,f[N][N*N]={1},ans=1; int qp(int a,int b){ int koishi=1; while(b){ if(b&1) koishi=koishi*a%mod; a=a*a%mod,b>>=1; } return koishi; } signed main(){ cin>>n; for(int i=1;i<=n;i++){ for(int j=0;j<=n*(n+1)/2;j++){ f[i][j]=f[i-1][j]; if(j>=i) f[i][j]=(f[i][j]+f[i-1][j-i])%(mod-1); } } for(int i=1;i<=n*(n+1)/2;i++) ans=ans*qp(i,f[n][i])%mod; cout<<ans<<"\n"; return 0; }
B
原题:P3488 [POI2009] LYZ-Ice Skates
我?赛时切紫?真的假的?!
假的 因为这个或那个的原因传奇地挂了\(70\)分,本来是可以切的。。。学到的:用cin
一定要关同步流,线段树开\(4\)倍。
我们把每个住户选择的范围\([l,r]\)看作线段。
有解\(\iff\)对于所有区间,都有\(\bf x\le len\times k\)。其中\(\bf len\)是该区间的长度,\(\bf x\)是该区间覆盖的线段数。
- 充分性:\(len\times k\)就是这个区间内的房间数量,所以如果该区间内的住户数量\(>\)房间数量,那么房间是不够用的。
- 必要性:显然,无解\(\iff\)存在长度为\(d+1\)的区间不合法,那么有解\(\iff\)所有长度为\(d+1\)的区间都合法。
在有解时考虑最极端的情况,即最大化\(x\)。显然此时需要\(d=0\),且所有长度为\(d+1\)的区间都放满了\(k\)条线段,那么总共覆盖的线段数量\(x=(len-d)\times k=len\times k\)。在有解的情况下\(x\)最大取到\(len\times k\),所以\(x\le len\times k\Rightarrow\)有解。
于是我们只需要判断该结论是否成立即可。
由于区间等长,我们简化一下,只统计区间左端点,判断是否存在区间\([l,r]\)使得\(x\le (len+d)\times k\),其中\(len=r-l+1\),\(x\)是\([l,r]\)覆盖的左端点个数。
这样还是不太容易看,我们移一下项:\(x-len\times k\le d\times k\)。
其中\(d\times k\)是常数,所以我们把\((x-len\times k)\)看作一个整体扔进线段树维护即可,具体来说,将线段树叶子节点初值赋为\(-k\)即可。这种把位置相关量和值看作一个整体来维护的技巧之前洛谷比赛有过:P11157 【MX-X6-T3】さよならワンダーランド ~ 题解。
由于只要存在\((x-len\times k)>d\times k\)就是不合法的。所以我们需要想办法维护出那个\((x-len\times k)\)最大的区间来与\(d\times k\)进行比较。所以我们用线段树来维护一个最大连续子段和,模板题 P4513 小白逛公园,转移并不难理解,可以去看下洛谷的题解。
这样求出最大连续子段和,判定答案并输出即可。时间复杂度\(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h> #define lc(x) ((x)<<1) #define rc(x) ((x)<<1|1) #define int long long #define N 500010 using namespace std; int n,m,k,d,sum[N<<2],maxx[N<<2],lmax[N<<2],rmax[N<<2]; void update(int x){ sum[x]=sum[lc(x)]+sum[rc(x)]; maxx[x]=max({maxx[lc(x)],maxx[rc(x)],rmax[lc(x)]+lmax[rc(x)]}); lmax[x]=max({lmax[lc(x)],sum[lc(x)]+lmax[rc(x)]}); rmax[x]=max({rmax[rc(x)],sum[rc(x)]+rmax[lc(x)]}); } void build(int x,int l,int r){ if(l==r){ sum[x]=-k; return; } int mid=(l+r)>>1; build(lc(x),l,mid),build(rc(x),mid+1,r); update(x); } void chp(int a,int v,int x,int l,int r){ if(l==r){ sum[x]+=v; lmax[x]=rmax[x]=maxx[x]=max(0ll,sum[x]); return; } int mid=(l+r)>>1; if(a<=mid) chp(a,v,lc(x),l,mid); else chp(a,v,rc(x),mid+1,r); update(x); } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>m>>k>>d; build(1,1,n-d); int x,y; while(m--){ cin>>x>>y; chp(x,y,1,1,n-d); if(maxx[1]>d*k) cout<<"NO\n"; else cout<<"YES\n"; } return 0; }
C
目前全网只能找到原比赛页面的题解了,但是看不懂,std也挺玄学的,\(f\)的含义都没搞懂……
先把自己的暴力和照着std写的代码放上来,等弄懂了会更新。
暴力的做法就是二进制枚举每个元素是否被选,然后先判断是否连通,再看连通块是否满足\(m\)个约束条件。
暴力
#include<bits/stdc++.h> #define int long long #define N 2010 #define M 25 using namespace std; struct Limit{int u,v;}lim[M]; int n,m,v[N],ans,dep[N],koishi[N],idx,V; bitset<N> sel[N]; vector<int> G[N]; void satori(int u,int fa){ dep[u]=dep[fa]+1; for(int i:G[u]) satori(i,u); } void dfs(int u){ koishi[++idx]=u,V+=v[u]; for(int i:G[u]) if(sel[i]==1) dfs(i); } signed main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]; for(int i=1,s,v;i<=n;i++){ cin>>s; while(s--) cin>>v,G[i].emplace_back(v); } satori(1,0); for(int i=1;i<=m;i++) cin>>lim[i].u>>lim[i].v; for(int i=(1<<n)-1;i;i--){ int mindep=INT_MAX,p=-1,pc=0; for(int j=1;j<=n;j++) sel[j]=(i>>(j-1))&1,pc+=(sel[j]==1); for(int j=1;j<=n;j++) if(sel[j]==1&&dep[j]<mindep) mindep=dep[j],p=j; idx=V=0,dfs(p); if(idx!=pc) continue; bool f=1; for(int j=1;j<=m;j++){ for(int k=1;k<idx;k++){ if((koishi[k]==lim[j].u&&koishi[k+1]==lim[j].v)||(koishi[k]==lim[j].v&&koishi[k+1]==lim[j].u)){ f=0; break; } } if(f==0) break; } if(f) ans=max(ans,V); } cout<<ans<<"\n"; return 0; }
STD
#include<bits/stdc++.h> #define int long long #define N 100010 #define M 52//开2倍,因为无向 using namespace std; int n,m,a[N],num[N],idx,ans,f[N][M]; struct t_edge{int u,v;}dat[M]; vector<int> G[N]; bitset<N> vis; bitset<M> mp[M]; void dfs(int u){ f[u][num[u]]=a[u]; for(int i:G[u]){ dfs(i); int maxx=LLONG_MIN; for(int j=0;j<=idx;j++) if(!mp[j][num[i]]) maxx=max(maxx,f[u][j]); for(int j=0;j<=idx;j++) f[u][j]=max(f[u][j],f[i][j]+maxx); } for(int i=0;i<=idx;i++) ans=max(ans,f[u][i]); } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr),cout.tie(nullptr); cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1,s,v;i<=n;i++){ cin>>s; while(s--) cin>>v,G[i].push_back(v); } for(int i=1,u,v;i<=m;i++){ cin>>u>>v; dat[i]={u,v}; vis[u]=vis[v]=1; } for(int i=1;i<=n;i++) if(vis[i]) num[i]=++idx; for(int i=1;i<=m;i++){ int u=num[dat[i].u],v=num[dat[i].v]; mp[u][v]=mp[v][u]=1; } memset(f,-0x3f,sizeof f),dfs(1); for(int i=1;i<=n;i++){ for(int j=0;j<=idx;j++){ cout<<i<<" "<<j<<" : "<<f[i][j]<<"\n"; } } cout<<ans<<"\n"; return 0; }
D
这个有空再看。\(n\le 20\)的暴力思路就是将\(01\)串压成一个数,先二进制枚举问号处填什么,再对于每一种填法,跑DFS,记录能跑到的状态种数,累加答案。
暴力
#include<bits/stdc++.h> #include<ext/pb_ds/assoc_container.hpp> #include<ext/pb_ds/hash_policy.hpp> #define N 514//無意識 #define mod 1000000007 using namespace std; using namespace __gnu_pbds; int n,un[N],idx,num,ans; string s; gp_hash_table<int,bool> vis; bool getp(int x,int pos){return (x>>pos)&1;} void dfs(int x){ if(vis[x]) return; vis[x]=1; for(int i=2;i<n;i++){ if(!getp(x,i-2)&&getp(x,i-1)&&getp(x,i)) dfs((x|(1<<(i-2)))&(~(1<<i))); else if(getp(x,i-2)&&getp(x,i-1)&&!getp(x,i)) dfs((x|(1<<i))&(~(1<<(i-2)))); } } void solve(int x){ vis.clear(); dfs(x); ans=(ans+vis.size())%mod; } signed main(){ cin>>n>>s; for(int i=0;i<n;i++){ if(s[i]=='?') un[++idx]=i; else if(s[i]=='1') num|=(1<<i); } for(int i=(1<<idx)-1;~i;i--){ int tnum=num; for(int j=0;j<idx;j++) if(getp(i,j)) tnum|=(1<<un[j+1]); solve(tnum); } cout<<ans<<"\n"; return 0; }
STD
#include <bits/stdc++.h> using namespace std; #define lep(i, l, r) for(int i = (l); i <= (r); i ++) #define rep(i, l, r) for(int i = (l); i >= (r); i --) const int N = 500 + 5; const int P = 1e9 + 7; inline int mod(int x) { return x + (x >> 31 & P); } inline void pls(int &x, int y) { x = mod(x + y - P); } int power(int x, int k) { int res = 1; while(k) { if(k & 1) res = 1ll * res * x % P; x = 1ll * x * x % P; k >>= 1; } return res; } int n; int F[N][N][2], G[N][N][2]; char str[N]; int fac[N], ifac[N]; inline int C(int x, int y) { if(x < 0 || y < 0 || x < y) return 0; return 1ll * fac[x] * ifac[y] % P * ifac[x - y] % P; } int main() { ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; cin >> (str + 1); auto f = F; auto g = G; f[0][0][0] = 1; lep (i, 1, n) { memset(g, 0, sizeof(G)); lep (j, 0, i) lep (k, 0, i) lep (op, 0, 1) if(f[j][k][op]) { char ch = str[i]; if(ch == '0' || ch == '?') pls(g[j][k + 1][0], f[j][k][op]); if(ch == '1' || ch == '?') { if(op == 1) pls(g[j + 1][k][0], f[j][k][op]); else pls(g[j][k][1], f[j][k][op]); } } swap(f, g); } fac[0] = 1; lep (i, 1, n) fac[i] = 1ll * fac[i - 1] * i % P; ifac[n] = power(fac[n], P - 2); rep (i, n - 1, 0) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % P; int ans = 0; lep (i, 0, n) lep (j, 0, n) if(f[i][j][0] || f[i][j][1]) { int res = mod(f[i][j][0] + f[i][j][1] - P); ans = (ans + 1ll * res * C(i + j, i)) % P; } printf("%d\n", ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效