NOIP2024 前集训:NOIP2024加赛 3(欢乐赛)
前言
再次考古,现在才写。
这场叫欢乐赛,搬的 CF,不知道 OJ 哪儿来的 RMJ。
就记得 T3 一直往数据结构上想浪费好多时间,结果发现是结论题 \(O(n)\) 可做。
T1 Sakurako and Water
虽然我之前不知道主对角线是什么东西,但是看完题目自动把他过滤成左上角到右下角了(不知道当时怎么想的,好像根本没想),然后发现一条对角线每次选都选了不全选是唐氏,就做完了。
T2 Binomial Coefficients, Kind Of
题目是给了一串(错误的)杨辉三角求组合数代码,人均读假一遍题,发现他写的是错的,实际是 \(2^k\)。
T3 QED's Favorite Permutation
不知道 QED 和这题啥关系。
发现 LR
会成为分割点使左右不再互通,因为他每次都是单点修改,所以根本不用维护这说直接判就可以了,判断这个 LR
是否有区间需要越过他,前缀和一下即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
int T,n,m,cant,a[N],suml[N],sumr[N]; string s;
signed main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
for(cin>>T;T;T--,cant=0)
{
cin>>n>>m,memset(suml,0,4*(n+1)),memset(sumr,0,4*(n+1));
for(int i=1,x;i<=n;i++)
cin>>x,(x!=i)&&(++suml[min(x,i)+1])&&(++sumr[max(x,i)]);
for(int i=1;i<=n;i++) suml[i]+=suml[i-1],sumr[i]+=sumr[i-1];
cin>>s,s=" "+s; for(int i=2,tmp;i<=n;i++)
cant+=(s[i]=='R'&&s[i-1]=='L'&&suml[i]-sumr[i-1]);
for(int i=1,x;i<=m;i++)
{
cin>>x;
cant-=(s[x]=='L'&&s[x+1]=='R'&&suml[x+1]-sumr[x]);
cant-=(s[x]=='R'&&s[x-1]=='L'&&suml[x]-sumr[x-1]);
cant+=(s[x]=='L'&&s[x-1]=='L'&&suml[x]-sumr[x-1]);
cant+=(s[x]=='R'&&s[x+1]=='R'&&suml[x+1]-sumr[x]);
puts(cant?"NO":"YES"),s[x]=s[x]=='R'?'L':'R';
}
}
}
T4 Card Game
是道不需要一点优化的朴素 DP,但赛时一直唐 T3 没有写出来。
发现对于 \(1\) 花色的牌只有 Alice
可以多拿,同理剩下的牌只有 Bob
可以多拿,且 Alice
多拿的数量等于 Bob
多拿的数量。
\(f_{i,j}\) 表示在花色为 \(1\) 的排中,目前为第 \(i\) 大的牌,Alice
多拿了 \(j\) 张的方案数,显然有转移:
\(g_{i,j}\) 表示在不包括 \(1\) 的前 \(i\) 种花色中 Bob
多拿了 \(j\) 张牌的贡献,发现对于每一种花色的转移和上面是一样的,所以有:
根据上面的结论,最后答案就是 \(\sum\limits_{i=0}^mf_{m,i}g_{n-1,i}\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=510,P=998244353;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') z=0;
for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,m,ans,f[N][N],g[N][N];
inline int mod(int x,int y) {return (x+=y)>=P?x-P:x;}
signed main()
{
read(n,m),n--,f[0][0]=1;
for(int i=1;i<=m;f[i][0]=f[i-1][1],i++) for(int j=1;j<=m;j++)
f[i][j]=mod(f[i-1][j-1],f[i-1][j+1]);
g[0][0]=1; for(int i=1;i<=n;i++) for(int j=0;j<=m;j++)
for(int k=0;k<=j;k++) g[i][j]=mod(g[i][j],1ll*f[m][k]*g[i-1][j-k]%P);
for(int i=0;i<=m;i++) ans=mod(ans,1ll*f[m][i]*g[n][i]%P);
write(ans);
}
T5 Long Way to be Non-decreasing
每次选一个集合就是说直接对每个点计算步数,最后取最大值就是答案,所以发现可以二分答案,表示是否能在 \(mid\) 步内走完,check 可以双指针跑。
考虑怎么计算距离,\(i\to b_i\) 连边,发现这是一棵基环树森林,所以不在一棵树上的距离为 \(\inf\),否则计算断边前后的距离取最小值即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') z=0;
for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int T,n,m,tot,ans=-1,a[N],b[N],f[N],in[N],out[N],del[N],dep[N]; vector<int>e[N];
inline int find(int x) {return x==f[x]?x:f[x]=find(f[x]);}
inline void dfs(int x)
{in[x]=++tot; for(int y:e[x]) dep[y]=dep[x]+1,dfs(y); out[x]=tot;}
inline bool insub(int x,int y) {return in[x]<=in[y]&&out[x]>=out[y];}
inline int getdis(int x,int y)
{
if(find(x)!=find(y)) return 1e9;
if(insub(y,x)) return dep[x]-dep[y];
if(insub(y,b[del[find(x)]])) return dep[x]+dep[b[del[find(x)]]]-dep[y]+1;
return 1e9;
}
inline bool check(int x)
{
for(int i=1,j=1;i<=n;i++)
{while(j<=m&&getdis(a[i],j)>x) j++; if(j>m) return 0;}
return 1;
}
signed main()
{
for(read(T);T;T--,tot=0,ans=-1)
{
read(n,m),memset(del,0,4*(m+1)),memset(dep,0,4*(n+1));
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=m;i++) read(b[i]),f[i]=i,vector<int>().swap(e[i]);
for(int i=1,x,y;i<=m;i++)
{
if((x=find(i))==(y=find(b[i]))) del[x]=i;
else f[x]=y,e[b[i]].push_back(i);
}
for(int i=1;i<=m;i++) if(f[i]==i) dfs(del[i]);
for(int l=0,r=m,mid;l<=r;)
{if(check(mid=l+r>>1)) ans=mid,r=mid-1; else l=mid+1;}
write(ans),puts("");
}
}
T6 Many Games
考虑如何 DP,这是简单的,设 \(f_{j}\) 表示和为 \(j\) 的概率,01 背包直接做,但是发现复杂度是错误的,现在需要考虑怎么证明其复杂度是正确的。
首先对于 \(p=100\) 的一定都选上,否则对于所有 \(p\) 相等的,设其选 \(k\) 个最优,则当然是选 \(w\) 最大的 \(k\) 个,设 \(s=\sum\limits_{i=1}^k w_i,mn=\min\limits_{i=1}^kw_i\),则有:
从而得到 \(k\le \lfloor\frac{1}{1-p\%}\rfloor=\lfloor\frac{100}{100-p}\rfloor\),从而选出的总数 \(\le \sum\limits_{i=1}^{99}\lfloor\frac{100}{100-p}\rfloor=481\)。
现在还有一维 \(\sum w\) 还是很大,设 \(s=\sum w,t=\prod p\),根据最优性,去掉一个 \((p,w)\) 一定不优,有:
又因为 \(w\times p\le 2\times 10^5\),所以有:
当 \(p=1\) 或 \(99\) 时取到最大,即 \(s\le 202020\),从而最初那个 DP 的复杂度便变成了正确的。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int mx=202020;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') z=0;
for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,sum; double ans,f[mx+5]; priority_queue<int>q[100];
signed main()
{
read(n),f[0]=1; for(int i=1,x,y;i<=n;i++)
{read(x,y); if(x==100) sum+=y; else q[x].push(y);}
for(int i=1;i<=99;i++) for(int j=100/(100-i),x;j&&q[i].size();j--)
{
x=q[i].top(),q[i].pop();
for(int k=mx;k>=x;k--) f[k]=max(f[k],f[k-x]*i/100.0);
}
for(int i=0;i<=mx;i++) ans=max(ans,(i+sum)*f[i]);
printf("%.8lf",ans);
}