NOIP2024(欢乐)加赛 3

因为本部不让打 CF,所以拿最近几场 CF 的题组了一场 IOI 模拟赛,弥补一下大家是吧。
image

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\)

\[\begin{aligned} &p^iwi<p^{i-1}w(i-1)\\ &pi<i-1\\ &(1-p)i>1\\ &i>\frac{1}{1-p} \end{aligned} \]

所以最多选择 \(\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\) 物品。

\[\begin{aligned} &psum<\frac{p}{p_j}(sum-w_j)\\ &p_jsum<sum-w_j\\ &(1-p_j)sum>w_j\\ &sum>\frac{w_j}{1-p_j}\\ \end{aligned} \]

所以最大的价值和不大于 \(\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);
}
posted @ 2024-11-10 17:33  Ishar-zdl  阅读(70)  评论(4编辑  收藏  举报