NOIP2024(欢乐)加赛 3
因为本部不让打 CF,所以拿最近几场 CF 的题组了一场 IOI 模拟赛,弥补一下大家是吧。
A CF2033B Sakurako and Water
直接做。
B CF2025B Binomial Coefficients, Kind Of
打表可知,我们来看一下,对于 \(k=0\),\(C_{n,k}=1\),那么 \(k=1\) 时,\(C_{n,k}\) 也全部相等,所以 \(C_{n,k}=2C_{n,k-1}\)。
C CF2030D QED's Favorite Permutation
太菜了,这个没有一眼。
简单观察发现,操作很强大,只有当出现 LR
时,才会将两个区域隔开,然后如果后缀 min
小于这个位置,那么这个 LR
就使实现不可能了,当且仅当这样的 LF
数量为 \(0\) 时,可以实现,于是直接维护一个后缀 min
,修改判断即可。前缀 max
同理,时间复杂度 \(\mathcal{O}(n)\)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int R(int n){return myrand()%n+1;}
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10,mod=998244353,inf=1e9;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,q,min[N],a[N];
char s[N];
bool vis[N];
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
int T;std::cin>>T;
while(T--){
std::cin>>n>>q;
int num=0;
for(int i=1;i<=n;++i)std::cin>>a[i],vis[i]=0;
min[n]=a[n];for(int i=n-1;i;--i)min[i]=std::min(min[i+1],a[i]);
std::cin>>s+1;
for(int i=n;i>1;--i){
if(s[i]=='R'&&s[i-1]=='L'){
if(min[i]<i)vis[i]=1,num++;
}
}
while(q--){
int x;std::cin>>x;
if(s[x]=='L')s[x]='R';
else s[x]='L';
if(vis[x]){
vis[x]=0;num--;
}
if(vis[x+1]){
vis[x+1]=0;num--;
}
if(s[x+1]=='R'&&s[x]=='L'){
if(min[x+1]<x+1)vis[x+1]=1,num++;
}
if(s[x]=='R'&&s[x-1]=='L'){
if(min[x]<x)vis[x]=1,num++;
}
if(num){
std::cout<<"NO\n";
}else{
std::cout<<"YES\n";
}
}
}
}
D CF2025E Card Game
一眼发现是卡特兰数相关,然后范围很小,直接全 DP 出来即可。
如果没有 1
的特殊牌,每行之间互相独立,直接相乘即可,但是 1
可以去帮助其他牌,不难想到枚举分出多少个 1
来帮助其他牌。
分析完上面之后,就可以自然地设计出 \(f_{i,j}\) 表示一共分了 \(i\) 张牌,需要 \(j\) 次帮助才能吃完,转移 \(f_{i,j}=f_{i-1,j-1}+f_{i-1,j+1}\),然后枚举分出 1
的数量 \(cnt\),现在问题就成了把 \(cnt\) 个糖分给 \(n-1\) 个小朋友类似的问题了,不过每一种方案都有一个系数。
枚举分给当前小朋友糖的个数,再给他乘个系数,得到转移 \(d_{i,j}=\sum_{k=0}^jd_{i-1,j-k}f_{m,k}\),最后答案就是 \(\sum_{cnt=0}^{m}f_{m,cnt}d_{n-1,cnt}\),前面的系数表示 1
牌富余 \(cnt\) 个的方案数(对称性),时间复杂度 \(\mathcal{O}(n^3)\),不知道前缀和可不可以优化 \(d\) 的转移,但是直接上 FFT
就做到了 \(n\log n\),然后 \(f\) 可以卡特兰数求,整体复杂度做到了 \(\mathcal{O}(n\log n)\),不知道推式子能不能更优秀。
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1000+10,mod=998244353,inf=1e9,P=501;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void W(int &x,int y){x=(x+y)%mod;}
int n,f[N][N],jc[N],ny[N],m,ans,d[N][N];
inline int R(int x){return x+P;}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read(),m=read();
f[0][0]=d[0][0]=1;
for(int i=1;i<=m;++i)for(int j=0;j<=m;++j)W(f[i][j],f[i-1][j-1]+f[i-1][j+1]);
for(int i=1;i<=n;++i)for(int j=0;j<=m;++j)for(int k=0;k<=j;++k)W(d[i][j],d[i-1][j-k]*f[m][k]);
for(int c=0;c<=m;++c)W(ans,f[m][c]*d[n-1][c]);
std::cout<<ans<<'\n';
}
E CF1967D Long Way to be Non-decreasing
简单分析后,就是求最小的最大操作次数,因为别的操作可以在它操作时一起带上,直接二分。
考虑怎么 check 这个序列的单调性,比较直接的方法是倍增,然后找到比上一位大的最小的数,但是这个根本不知道该怎么维护啊,并且也不太像 \(\mathcal{O}(n\log^2n)\) 的复杂度。
发现对于最后序列的值域是已知的,考虑直接用值域来刻画这个答案,直接从小到大枚举每一位的最后数字 \(x\),范围在 \([1,m]\),如果当前位可以在 \(mid\) 次操作内到达 \(x\),就可以,否则 \(x\gets x+1\)。
现在只剩求 \(dis(x,y)\) 了,给这个操作建个图,是内向基环树森林,内向不太好处理距离,考虑建个反边,这样就成了环上的每个节点挂了一棵向外的子树,此时既可以统计连通性,又可以统计距离了。
对于环上的距离,直接求即可,对于链上的距离,检查祖先关系后直接深度相减即可,时间复杂度 \(\mathcal{O}(n\log n)\)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int R(int n){return myrand()%n+1;}
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e6+10,mod=998244353,inf=1e9;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
bool vis[N];
int n,m,a[N],b[N],in[N],rt,fa[N],bl[N],man;
int dfn[N],dn,ri[N],dep[N],id[N],len[N];
std::vector<int> e[N],g[N];
inline void topo(){
std::queue<int> q;
for(int i=1;i<=m;++i)if(!in[i])q.push(i);
while(!q.empty()){
int x=q.front();q.pop();vis[x]=1;
for(int v:e[x]){
if(!--in[v])q.push(v);
}
}
}
inline void dfs(int x){
dfn[x]=++dn;fa[x]=rt;
for(int v:g[x]){
if(!vis[v]&&!dfn[v])dep[v]=dep[x]+1,dfs(v);
}
ri[x]=dn;
}
inline void search(int x){
id[x]=++len[man];bl[x]=man;
for(int v:e[x]){
if(vis[v]&&!bl[v])search(v);
}
}
inline int dis_huan(int x,int y){
if(bl[x]!=bl[y])return inf;
if(id[y]<id[x])return len[bl[x]]-(id[x]-id[y]);
return id[y]-id[x];
}
inline int dis_lian(int x,int y){
if(dfn[x]>=dfn[y]&&dfn[x]<=ri[y]){
return dep[x]-dep[y];
}return inf;
}
inline int dis(int x,int y){
if(vis[x]&&vis[y])return dis_huan(x,y);
if(vis[x]&&!vis[y])return inf;
if(!vis[x]&&vis[y])return dep[x]+dis_huan(fa[x],y);
if(!vis[x]&&!vis[y])return dis_lian(x,y);
}
inline bool check(int mid){
int now=1;
for(int i=1;i<=n;++i){
while(now<=m){
if(dis(a[i],now)<=mid)break;
now++;
}
if(now>m)return 0;
}
return true;
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
int T=read();
while(T--){
n=read(),m=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=m;++i)b[i]=read();
dn=0;rt=0;man=0;
for(int i=1;i<=m;++i)e[i].clear(),g[i].clear(),in[i]=0,dfn[i]=0,vis[i]=0,dep[i]=0,ri[i]=0,id[i]=0,fa[i]=0,len[i]=0,bl[i]=0;
for(int i=1;i<=n;++i)e[a[i]].eb(b[a[i]]),g[b[a[i]]].eb(a[i]),in[b[a[i]]]++;
for(int i=1;i<=m;++i)e[b[i]].eb(b[b[i]]),g[b[b[i]]].eb(b[i]),in[b[b[i]]]++;
topo();
for(int i=1;i<=m;++i)vis[i]=!vis[i];
for(int i=1;i<=m;++i)if(vis[i]&&!dfn[i])dfs(rt=i);
for(int i=1;i<=m;++i)if(vis[i]&&!bl[i]){man++;search(i);}
int l=0,r=m,ans=-1;
while(l<=r){
int mid=l+r>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
std::cout<<ans<<'\n';
}
}
F CF2023D Many Games
我去,没看见 \(p_i\) 是整数,这我做个集贸啊。
先把 \(p_i\) 转成真实的概率,每个物品选或不选,考虑背包来处理,想要得出转移的信息,必须要知道上一个的概率和价值,等价值概率越大越好,等概率价值越大越好。
概率肯定不能作为状态,所以设 \(f_{i,j}\) 表示前 \(i\) 个物品,选出的价值是 \(j\) 时的最大概率,转移 \(f_{i,j}=\max(f_{i-1,j},f_{i-1,j-w_i}p_i)\)。
这时直接暴力 DP 的复杂度是 \(\mathcal{O}(n\sum w_i)\) 的,现在来深挖一下题目的性质。
答案的形态很优美,具有单调性,无论少选一个还是多选一个都不优,形如单峰的样子。
首先,概率为 \(1\) 的一定选,否则,对于同等概率,肯定选价值大的更优,根据单调性,选的一定是一个前缀最大值,考虑得出最多能选多少个,如果价值都一样,那么选的一定最多,否则小的会影响大的贡献,停止选择更早,设选择 \(i\) 个的时候,第一次比上一次劣,概率是 \(p\),价值是 \(w\)。
所以最多选择 \(\frac{1}{1-p}\),所有的概率加和是一个调和级数,也就是说,真正有用的数不超过 \(700\) 左右,这样 DP 的复杂度上界为 \(\mathcal{O}(700\sum w_i)\),但是值域还是很大,题目还有一个性质没用 \(p_iw_i\le 2\times 10^5\),转成真实概率之后就成了 \(p_iw_i\le 2\times 10^3\)。
还是根据答案的单调性,设 \(i\) 答案的个数加一,设 \(sum\) 表示当前价值和,\(p\) 表示当前概率,假设删掉了 \(j\) 物品。
所以最大的价值和不大于 \(\frac{w_j}{1-p_j}\),这个最大是 \(2\times 10^5\),所以值域的范围也得出来了,这时复杂度已经完全正确了,实际上远远不到这个上界,直接 01 背包就行了。
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int R(int n){return myrand()%n+1;}
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10,mod=998244353,inf=1e9;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,num,a[N],sum;
double f[N],b[N],ans;
std::vector<int> v[105];
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();for(int i=1;i<=n;++i){
int p=read(),w=read();
if(p==100){sum+=w;continue;}
v[p].eb(w);
}
for(int i=1;i<100;++i){
if(v[i].size()==0)continue;
double la=0,p=1,sm=0,gai=i*1.0/100;
std::sort(v[i].begin(),v[i].end(),std::greater<int>());
for(int j=0;j<v[i].size();++j){
p*=gai;sm+=v[i][j];
if(sm*p<la)break;
la=sm*p;
a[++num]=v[i][j],b[num]=gai;
}
}
f[0]=1;
for(int i=1;i<=num;++i)for(int j=N-10;j>=a[i];--j)f[j]=std::max(f[j],f[j-a[i]]*b[i]);
for(int i=0;i<=N-10;++i)ans=std::max(ans,f[i]*(i+sum));
printf("%.15lf\n",ans);
}