NOIP2024加赛5
NOIP2024加赛5
题目来源: 2023NOIP A层联测31
\(T1\) HZTG5777. 暴力操作(opt) \(40pts\)
-
先将 \(\{ a \}\) 升序排序。
-
因为 \(\left\lfloor \dfrac{\left\lfloor \frac{a}{b} \right\rfloor}{c} \right\rfloor=\left\lfloor \dfrac{a}{bc} \right\rfloor\) ,先钦定 \(\forall i,j \in \mathbb{N}^{*},c_{i,j} \le c_{i}+c_{j}\) ,并让每个数仅被操作一遍。
-
考虑二分答案,并只对 \([1,\left\lceil \frac{n}{2} \right\rceil]\) 进行操作。
-
此时需要解形如 \(\left\lfloor \frac{a_{i}}{x} \right\rfloor \le mid\) 的不等式,可以使用二分求解,也可以观察到 \(\frac{a_{i}}{x} < mid+1\) 从而得到 \(x \ge \left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+1\) 。然后取 \(\min\limits_{k \in [\left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+ \infty)}\{ c_{x} \}\) 加入代价。
-
对于 \(\min\limits_{k \in [\left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+ \infty)}\{ c_{x} \}\) 初始时仅通过调和级数处理到 \(m\) 是不够的。
-
设 \(\{ suf \}\) 表示 \(\{ c \}\) 的后缀 \(\min\) 。不妨先处理出 \([1,m]\) 的后缀 \(\min\) ,此时有 \(suf_{m+1}=\min\limits_{i=1}^{m}\{ c_{i}+suf_{\left\lceil \frac{m+1}{i} \right\rceil} \}\) ,再次倒着更新 \([1,m+1]\) 的后缀 \(\min\) 即可。
点击查看代码
ll a[500010],c[500010],suf[500010]; bool check(ll n,ll m,ll k,ll mid) { ll sum=0; for(ll i=1;i<=n/2+1;i++) { if(a[i]>mid) { sum+=suf[a[i]/(mid+1)+1]; } } return sum<=k; } int main() { #define Isaac #ifdef Isaac freopen("opt.in","r",stdin); freopen("opt.out","w",stdout); #endif ll n,m,k,l=0,r,mid,ans=-1,i,j; scanf("%lld%lld%lld",&n,&m,&k); r=m; for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=1;i<=m;i++) { scanf("%lld",&c[i]); } sort(a+1,a+1+n); c[1]=0; for(i=1;i<=m;i++) { for(j=1;i*j<=m;j++) { c[i*j]=min(c[i*j],c[i]+c[j]); } } suf[m]=c[m]; for(i=m-1;i>=1;i--) { suf[i]=min(suf[i+1],c[i]); } suf[m+1]=0x3f3f3f3f; for(i=1;i<=m;i++) { j=ceil(1.0*(m+1)/i); if(j<=m) { suf[m+1]=min(suf[m+1],c[i]+suf[j]); } } for(i=m;i>=1;i--) { suf[i]=min(suf[i+1],c[i]); } while(l<=r) { mid=(l+r)/2; if(check(n,m,k,mid)==true) { ans=mid; r=mid-1; } else { l=mid+1; } } printf("%lld\n",ans); return 0; }
\(T2\) HZTG5778. 异或连通(xor) \(50pts\)
-
部分分
- 子任务 \(1\) :\(DFS\) 找连通块。
- 子任务 \(2\) :加个记忆化即可。
- 子任务 \(3\) :使用并查集找连通块。
点击查看代码
ll u[200010],v[200010],w[200010]; unordered_map<ll,ll>f; struct DSU { ll fa[200010],siz[200010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(ll x,ll y) { x=find(x); y=find(y); if(x!=y) { fa[y]=x; siz[x]+=siz[y]; } } }D; int main() { #define Isaac #ifdef Isaac freopen("xor.in","r",stdin); freopen("xor.out","w",stdout); #endif ll n,m,q,k,x,ans=0,i,j; scanf("%lld%lld%lld%lld",&n,&m,&q,&k); for(i=1;i<=m;i++) { scanf("%lld%lld%lld",&u[i],&v[i],&w[i]); } for(j=1;j<=q;j++) { scanf("%lld",&x); if(f.find(x)==f.end()) { ans=0; D.init(n); for(i=1;i<=m;i++) { if((w[i]^x)<k) { D.merge(u[i],v[i]); } } for(i=1;i<=n;i++) { if(D.fa[i]==i) { ans+=D.siz[i]*(D.siz[i]-1)/2; } } f[x]=ans; } printf("%lld\n",f[x]); } return 0; }
-
正解
- 观察到每条边存在时对应的 \(k\) 至多形成 \((\log V)\) 段极长连续区间,使用 \(01Trie\) 求出区间并离散化后跑线段树分治即可。
点击查看代码
ll u[200010],v[200010],w[200010],a[200010],b[200010],ans[200010]; struct quality { ll id,fa,siz; }; struct DSU { ll fa[200010],siz[200010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } ll find(ll x) { return fa[x]==x?x:find(fa[x]); } void merge(ll x,ll y,stack<quality>&s,ll &sum) { x=find(x); y=find(y); if(x!=y) { s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); if(siz[x]<siz[y]) { swap(x,y); } fa[y]=x; sum-=siz[y]*(siz[y]-1)/2; sum-=siz[x]*(siz[x]-1)/2; siz[x]+=siz[y]; sum+=siz[x]*(siz[x]-1)/2; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().id]=s.top().fa; siz[s.top().id]=s.top().siz; s.pop(); } } }D; struct SMT { struct SegmentTree { vector<ll>info; }tree[800010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void update(ll rt,ll l,ll r,ll x,ll y,ll id) { if(l>y||r<x) { return; } if(x<=l&&r<=y) { tree[rt].info.push_back(id); return; } ll mid=(l+r)/2; update(lson(rt),l,mid,x,y,id); update(rson(rt),mid+1,r,x,y,id); } void solve(ll rt,ll l,ll r,ll sum) { stack<quality>s; ll mid=(l+r)/2; for(ll i=0;i<tree[rt].info.size();i++) { D.merge(u[tree[rt].info[i]],v[tree[rt].info[i]],s,sum); } if(l==r) { ans[l]=sum; } else { solve(lson(rt),l,mid,sum); solve(rson(rt),mid+1,r,sum); } D.split(s); } }S; struct Trie { ll son[6000010][2],st[6000010],ed[6000010],rt_sum=0; void insert(ll s,ll id,ll k) { ll x=0; for(ll i=30;i>=0;i--) { if((k>>i)&1) { S.update(1,1,b[0],st[son[x][(s>>i)&1]],ed[son[x][(s>>i)&1]],id); x=son[x][((s>>i)&1)^1]; } else { x=son[x][(s>>i)&1]; } if(x==0) { break; } } } void query(ll s,ll id) { ll x=0; for(ll i=30;i>=0;i--) { if(son[x][(s>>i)&1]==0) { rt_sum++; son[x][(s>>i)&1]=rt_sum; st[son[x][(s>>i)&1]]=id; } x=son[x][(s>>i)&1]; ed[x]=id; } } }T; int main() { #define Isaac #ifdef Isaac freopen("xor.in","r",stdin); freopen("xor.out","w",stdout); #endif ll n,m,q,k,i; scanf("%lld%lld%lld%lld",&n,&m,&q,&k); for(i=1;i<=m;i++) { scanf("%lld%lld%lld",&u[i],&v[i],&w[i]); } for(i=1;i<=q;i++) { scanf("%lld",&a[i]); b[i]=a[i]; } sort(b+1,b+1+q); b[0]=unique(b+1,b+1+q)-(b+1); for(i=1;i<=b[0];i++) { T.query(b[i],i); } for(i=1;i<=m;i++) { T.insert(w[i],i,k); } D.init(n); S.solve(1,1,b[0],0); for(i=1;i<=q;i++) { cout<<ans[lower_bound(b+1,b+1+b[0],a[i])-b]<<endl; } return 0; }
\(T3\) HZTG5779. 诡异键盘(keyboard) \(20pts\)
-
部分分
- 子任务 \(1\) :哈希优化字符串匹配后跑 \(O(n^{3})\) 的 \(DP\) 。
点击查看代码
const ll mod=1000003579,base=13331; ll f[5010],hshs[5010],lent[5010],jc[5010]; vector<ll>hsht[5010]; char s[1000010]; void sx_hash(char s[],ll len) { for(ll i=0;i<=len;i++) { hshs[i]=(i==0)?0:(hshs[i-1]*base%mod+s[i])%mod; } } void sx_hash_t(char s[],ll len,ll id) { for(ll i=0;i<=len;i++) { hsht[id][i]=(i==0)?0:(hsht[id][i-1]*base%mod+s[i])%mod; } } ll ask_hash(ll l,ll r) { return (hshs[r]-hshs[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { #define Isaac #ifdef Isaac freopen("keyboard.in","r",stdin); freopen("keyboard.out","w",stdout); #endif int t,n,m,len,i,j,k,h; cin>>t; for(i=0;i<=5000;i++) { jc[i]=(i==0)?1:jc[i-1]*base%mod; } for(h=1;h<=t;h++) { memset(f,0x3f,sizeof(f)); cin>>n>>m; for(i=1;i<=n;i++) { cin>>(s+1); lent[i]=strlen(s+1); hsht[i].clear(); hsht[i].resize(lent[i]+10); sx_hash_t(s,lent[i],i); } cin>>(s+1); len=strlen(s+1); sx_hash(s,len); f[0]=0; for(i=1;i<=len;i++) { for(j=0;j<=i-1;j++) { for(k=1;k<=n;k++) { if(i-j<=lent[k]&&ask_hash(j+1,i)==hsht[k][i-j]) { f[i]=min(f[i],f[j]+lent[k]-(i-j)+1); } } } } cout<<((f[len]==0x3f3f3f3f3f3f3f3f)?1:f[len])<<endl; } return 0; }
-
正解
\(T4\) HZTG5780. 民主投票(election) \(5pts\)
-
部分分
- 子任务 \(1\) :模拟。
点击查看代码
vector<int>e[1000010]; int siz[1000010],dfn[1000010],out[1000010],pos[1000010],fa[1000010],sum[1000010],tot; void add(int u,int v) { e[u].push_back(v); } void dfs(int x) { siz[x]=1; tot++; dfn[x]=tot; pos[tot]=x; for(int i=0;i<e[x].size();i++) { dfs(e[x][i]); siz[x]+=siz[e[x][i]]; } out[x]=tot; } bool work(int x,int maxx) { if(x==1) { return true; } x=fa[x]; while(x!=0) { if(sum[x]+1<maxx) { sum[x]++; return true; } x=fa[x]; } return false; } int check(int x,int n) { memset(sum,0,sizeof(sum)); int maxx=siz[x]-1; for(int i=1;i<=n;i++) { if((i<=dfn[x]||i>out[x])&&work(pos[i],maxx)==false) { return 0; } } return 1; } int main() { #define Isaac #ifdef Isaac freopen("election.in","r",stdin); freopen("election.out","w",stdout); #endif int t,n,i,j,k; scanf("%d",&t); for(k=1;k<=t;k++) { scanf("%d",&n); tot=0; for(i=1;i<=n;i++) { e[i].clear(); } for(i=2;i<=n;i++) { scanf("%d",&fa[i]); add(fa[i],i); } dfs(1); for(i=1;i<=n;i++) { printf("%d",check(i,n)); } printf("\n"); } return 0; }
-
正解
- 考虑先二分出树上最少的最多次数 \(k\) 。具体地,设当前二分出的答案为 \(mid\) , \(check\) 时设 \(f_{x,mid}\) 表示以 \(x\) 为根的子树内除去 \(x\) 在满足每个人最多被投 \(mid\) 票时,需要向上投的最少票数,状态转移方程为 \(f_{x,mid}=\max(0,\sum\limits_{y \in Son(x)}(f_{y,mid}+1)-mid)\) 。最后答案合法当且仅当 \(f_{1,mid}=0\) 。
- 难点在于最后询问满足 \(siz_{x}-1=k\) 的 \(x\) 时其他节点是否能满足最多 \(siz_{x}-2=k-1\) 张票的限制条件。不难发现 \(f_{x,k-1} \le 1\) ,若 \(x_{x,k-1}=0\) 因为 \(f_{1,mid}>0\) 故 \(x\) 不会获胜,否则若 \(\forall y \in (1 \to x),f_{y,k-1}=1\) 在 \(x\) 获得 \(k\) 张票时有 \(\forall y \in (1 \to fa_{x}),f_{y,k-1}=0\) 故 \(x\) 会获胜。
- 略带卡常。
点击查看代码
vector<int>e[1000010]; int fa[1000010],siz[1000010],ans[1000010],f[1000010],dfn[1000010],pos[1000010],tot; void add(int u,int v) { e[u].push_back(v); } void dfs1(int x) { siz[x]=1; tot++; dfn[x]=tot; pos[tot]=x; for(int i=0;i<e[x].size();i++) { dfs1(e[x][i]); siz[x]+=siz[e[x][i]]; } } void dfs2(int n,int mid) { for(int i=1;i<=n;i++) { f[i]=0; } for(int i=n;i>=1;i--) { f[pos[i]]=max(0,f[pos[i]]-mid); f[fa[pos[i]]]+=f[pos[i]]+1; } } void dfs3(int x) { if((f[x]==0)||(x==1&&f[x]!=1)) { return; } ans[x]=1; for(int i=0;i<e[x].size();i++) { dfs3(e[x][i]); } } int main() { #define Isaac #ifdef Isaac freopen("election.in","r",stdin); freopen("election.out","w",stdout); #endif int t,n,l,r,k,mid,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d",&n); tot=0; for(i=1;i<=n;i++) { e[i].clear(); ans[i]=0; } for(i=2;i<=n;i++) { scanf("%d",&fa[i]); add(fa[i],i); } dfs1(1); l=1; r=n; k=-1; while(l<=r) { mid=(l+r)/2; dfs2(n,mid); if(f[1]==0) { k=mid; r=mid-1; } else { l=mid+1; } } dfs2(n,k-1); dfs3(1); for(i=1;i<=n;i++) { printf("%d",((siz[i]-1==k)?ans[i]:(siz[i]-1>k))); } printf("\n"); } return 0; }
总结
- \(T1\) 调和级数仅处理到了 \(m\) ,挂了 \(60pts\) 。
- \(T4\) 链的部分分写假了,挂了 \(20pts\) 。
后记
- \(T1\) 题面从 \(tgHZOJ\) 搬过来的时候忘把更改后的题面(将原 \(x \in [1,m]\) 改成了 \(c_{x \in [1,m]}\))搬过来了,赛时 \(feifei\) 用画图更改了题面重新下发了。
- \(T3\) 未注明 \(\sum|S_{i}|\) 为每个数据点的总数据规模,赛时 \(feifei\) 还称是单组数据的数据规模。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18548407,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。