NOIP2024加赛7
NOIP2024加赛7
题面来源: 2023NOIP A层联测15
\(T1\) HZTG5683. 镜的绮想 (mirror) \(85pts\)
-
从点对本身考虑,开个桶统计即可。
点击查看代码
int x[5010],y[5010],xx[5010],yy[5010],f[4000010]; int main() { #define Isaac #ifdef Isaac freopen("mirror.in","r",stdin); freopen("mirror.out","w",stdout); #endif int n,m,ans=0,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x[i]>>y[i]; } for(i=1;i<=m;i++) { cin>>xx[i]>>yy[i]; } for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(x[i]==xx[j]) { f[y[i]+yy[j]+2000000]++; } } } for(i=0;i<=4000000;i++) { ans=max(ans,f[i]); } cout<<ans<<endl; return 0; }
\(T2\) HZTG5684. 万物有灵 (animism) \(16pts\)
-
手摸最大独立集的式子后发现选与不选之间的大小关系隔一层就交换一次。
-
部分分
- \(16pts\) :\(O(n)\) 计算。
点击查看代码
ll a[500010],f[2][2]; ll qadd(ll a,ll b,ll p) { return a+b>=p?a+b-p:a+b; } int main() { #define Isaac #ifdef Isaac freopen("animism.in","r",stdin); freopen("animism.out","w",stdout); #endif ll n,k,p,i,j; scanf("%lld%lld%lld",&n,&k,&p); for(i=0;i<=k-1;i++) { scanf("%lld",&a[i]); } for(i=n,j=0;i>=0;i--,j^=1) { f[i&1][0]=a[i%k]*f[(i+1)&1][j]%p; f[i&1][1]=qadd(a[i%k]*f[(i+1)&1][0]%p,1,p); } printf("%lld\n",f[0][j]); return 0; }
-
正解
- 考虑暴力处理出 \(n \bmod k\) 层的贡献,剩下的周期 \(k\) 统一计算。
- 将式子暴力展开后发现是个等比数列求和的形式,因为不保证模数为质数,故倍增/矩阵快速幂加速维护即可。
- 倍增写法跟快速幂差不多。
- 矩阵快速幂形如 \(\begin{bmatrix} s_{n} & 1 \end{bmatrix}=\begin{bmatrix} s_{n-1} & 1 \end{bmatrix} \times \begin{bmatrix} q & 0 \\ 1 & 1 \end{bmatrix}\) 。
- 因为 \(k\) 为奇数时需要特判,不妨直接钦定 \(k\) 为偶数,将周期直接翻倍。
点击查看代码
ll a[1000010],mul[1000010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll qsum(ll q,ll n,ll p) { if(n<=1) { return (n==1); } ll tmp=qsum(q,n/2,p),sum=(tmp*qpow(q,n/2,p)%p+tmp)%p; return (n%2==0)?sum:(sum*q%p+1)%p; } int main() { #define Isaac #ifdef Isaac freopen("animism.in","r",stdin); freopen("animism.out","w",stdout); #endif ll n,k,p,sum,ans=0,tmp,i; scanf("%lld%lld%lld",&n,&k,&p); for(i=0;i<k;i++) { scanf("%lld",&a[i]); a[i+k]=a[i]; } k*=2; mul[0]=a[0]; for(i=1;i<k;i++) { mul[i]=mul[i-1]*a[i]%p; } for(i=(n+1)%2;i<k;i+=2) { ans=(ans+mul[i])%p; } ans=(ans*qsum(mul[k-1],n/k,p)%p+(n+1)%2)%p; tmp=qpow(mul[k-1],n/k,p); for(i=(n+1)%2;i<n%k;i+=2) { ans=(ans+mul[i]*tmp)%p; } printf("%lld\n",ans); return 0; }
\(T3\) HZTG5685. 白石溪 (creek) \(30pts\)
-
部分分
- 测试点 \(1 \sim 8\) : \(O(n^{2})\) 的 \(DP\) 。
- 测试点 \(9\) :搭配产生的美丽度无意义,直接取 \(\max(a_{i},b_{i})\) 。
点击查看代码
ll f[2][1000010],a[1000010],b[1000010]; int main() { #define Isaac #ifdef Isaac freopen("creek.in","r",stdin); freopen("creek.out","w",stdout); #endif ll n,c,d,ans=0,i,j; scanf("%lld%lld%lld",&n,&c,&d); for(i=1;i<=n;i++) { scanf("%lld%lld",&a[i],&b[i]); } if(c==0&&d==0) { for(i=1;i<=n;i++) { ans+=max(a[i],b[i]); } } else { memset(f,-0x3f,sizeof(f)); f[0][0]=0; for(i=1;i<=n;i++) { for(j=0;j<=i;j++) { f[i&1][j]=f[(i-1)&1][j]+b[i]+j*c; if(j-1>=0) { f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-1]+a[i]+((i-1)-(j-1))*d); } } } for(i=0;i<=n;i++) { ans=max(ans,f[n&1][i]); } } printf("%lld\n",ans); return 0; }
-
正解
- \(DP\) 已经难以优化,考虑贪心。
- 设 \(pre_{i}\) 表示 \([1,i]\) 中与 \(i\) 颜色相同的石子个数。若第 \(i\) 个石子为红色,则其贡献为 \(a_{i}+(i-pre_{i})d\) ,蓝色的贡献为 \(b_{i}+(i-pre_{i})c\) 。
- 以红色为例,将贡献拆成 \(a_{i}+id\) 和 \(-pre_{i}d\) 两部分。从全局来看,后者只与红色石子总数有关。
- 不妨先钦定全为蓝色石子,然后考虑枚举红色石子的总数只统计前者的贡献。用红色更换蓝色的影响为 \(a_{i}-b_{i}+i(d-c)\) ,排序后贪心选择即可。
点击查看代码
ll a[1000010],b[1000010],e[1000010]; int main() { #define Isaac #ifdef Isaac freopen("creek.in","r",stdin); freopen("creek.out","w",stdout); #endif ll n,c,d,ans=0,sum=0,i; scanf("%lld%lld%lld",&n,&c,&d); for(i=1;i<=n;i++) { scanf("%lld%lld",&a[i],&b[i]); e[i]=a[i]-b[i]+i*(d-c); sum+=b[i]+i*c; } sort(e+1,e+1+n,greater<ll>()); for(i=0;i<=n;i++) { sum+=e[i]; ans=max(ans,sum-i*(i+1)/2*d-(n-i)*(n-i+1)/2*c); } printf("%lld\n",ans); return 0; }
\(T4\) HZTG5686. 上山岗 (uphill) \(10pts\)
-
部分分
- \(10pts\) :枚举全排列。
点击查看代码
int c[500010],w[500010]; vector<int>ans,tmp; bool cmp() { for(int i=0;i<ans.size();i++) { if(ans[i]<tmp[i]) { return true; } if(ans[i]>tmp[i]) { return false; } } return false; } int main() { #define Isaac #ifdef Isaac freopen("uphill.in","r",stdin); freopen("uphill.out","w",stdout); #endif int n,sum=-1,num,i; cin>>n; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n;i++) { cin>>w[i]; tmp.push_back(w[i]); } sort(tmp.begin(),tmp.end()); do { num=0; for(i=1;i<=n;i++) { num+=(tmp[i-1]>c[i]); } if(num>sum) { sum=num; ans=tmp; } else { if(num==sum&&cmp()==true) { ans=tmp; } } }while(next_permutation(tmp.begin(),tmp.end())); for(i=0;i<ans.size();i++) { cout<<ans[i]<<" "; } return 0; }
-
正解
- 把 \(\{ c \},\{ w \}\) 放在一起升序排序后将 \(c_{i}\) 视为 \(1\) , \(w_{i}\) 视为 \(-1\) ,那么最多登顶次数就是前缀和的最小值 \(+n\) 。考虑无用的 \(-1\) 来源即可。
- 对于每个选手一定是尽可能往放后面放,顺序线段树上二分找到对应位置分配。依此保证先取到最多登顶次数。
- 接着倒序考虑没有分配到山的选手,优先选择靠前且没有重新分配到选手的山给它;否则进行重新分配并优先选择靠前的山。这个过程中登顶次数不会变少。
点击查看代码
int c[500010],w[500010],ans[500010],pos[500010],id[500010]; struct SMT { struct SegmentTree { int minn; }tree[2000010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn); } void build(int rt,int l,int r,int c[]) { if(l==r) { tree[rt].minn=c[l]; return; } int mid=(l+r)/2; build(lson(rt),l,mid,c); build(rson(rt),mid+1,r,c); pushup(rt); } void update(int rt,int l,int r,int pos,int val) { if(l==r) { tree[rt].minn=val; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int query1(int rt,int l,int r,int k) { if(l==r) { return tree[rt].minn<k?l:-1; } int mid=(l+r)/2; if(tree[lson(rt)].minn<k) { return query1(lson(rt),l,mid,k); } else { return query1(rson(rt),mid+1,r,k); } } int query2(int rt,int l,int r,int k) { if(l==r) { return tree[rt].minn<k?l:-1; } int mid=(l+r)/2; if(tree[rson(rt)].minn<k) { return query2(rson(rt),mid+1,r,k); } else { return query2(lson(rt),l,mid,k); } } }sec,fir; int main() { #define Isaac #ifdef Isaac freopen("uphill.in","r",stdin); freopen("uphill.out","w",stdout); #endif int n,tmp,i; cin>>n; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n;i++) { cin>>w[i]; } sort(w+1,w+1+n); fir.build(1,1,n,c); sec.build(1,1,n,ans); for(i=1;i<=n;i++) { tmp=fir.query2(1,1,n,w[i]); if(tmp!=-1) { pos[i]=tmp; id[tmp]=i; fir.update(1,1,n,tmp,0x7f7f7f7f); } } for(i=n;i>=1;i--) { if(pos[i]==0) { tmp=sec.query1(1,1,n,1); pos[id[tmp]]=0; } else { fir.update(1,1,n,pos[i],c[pos[i]]); tmp=fir.query1(1,1,n,w[i]); } ans[tmp]=w[i]; fir.update(1,1,n,tmp,0x7f7f7f7f); sec.update(1,1,n,tmp,1); } for(i=1;i<=n;i++) { printf("%d ",ans[i]); } return 0; }
总结
- \(T1\) 原用来下标移位的桶使用
map
代替了,没有在本地测试极限数据,挂了 \(15pts\) 。 - \(T2\) 以为矩阵有交换律,遂直接把同一个矩阵的指数合并了,口胡了一个 \(EXCRT\) 维护 \(\begin{bmatrix} a_{i} & a_{i} & 0 \\ 0 & 0 & 0 \\ 0 & 1 &1 \end{bmatrix}\) 和 \(\begin{bmatrix} 0 & a_{i} & 0 \\ a_{i} & 0 & 0 \\ 0 & 1 &1 \end{bmatrix}\) 的指数做法,调到最后也没意识到自己做法有问题。
- \(T3\) 初始化有问题,挂了 \(15pts\) 。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18563451,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。