[54] (多校联训) A层冲刺NOIP2024模拟赛12

加把劲
                 加把劲
        5k       
            加把劲
   加把劲
             加把劲
加把劲
          快 CSP-S 了
    加把劲           加把劲

省流:

B 先生推行了一项措施,这项措施里有一部分 \(a\) 是对的,另一部分 \(b\) 是错的,钦定 \(|a|\lt|b|\)

如果 A 不满意 B 先生的措施,可以这样做:

  • 直接向 B 先生寻求探讨:A 向 B 先生理性阐述了 \(b\) 部分的危害,B 先生表示已经知道 \(b\) 部分的存在,同时反复强调 \(a\) 的重要性,A 被 B 先生告知不喜欢可以滚蛋,A 被 B 先生完爆
  • 向 B 先生跳脸:A 将遭受 B 先生强度更大的跳脸,A 被 B 先生完爆
  • 尝试独立:A 被 B 先生告知别人都能忍,为什么你忍不了,最终 B 先生以从众为由强制 A 服从,A 被 B 先生完爆

总结:B 先生一意孤行,意图在全社会推行帝国主义制度,复辟帝制

推论:无敌的傻逼是最可怕的

A.Alice 和璀璨花

\(n^3\) 的一个做法

\(f_{i,j}\) 表示选了 \(i\) 个,上一个选了 \(j\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename T>void ignored(T x){}
int n;
int a[1001];
int b[1001];
int f[1001][1001];
signed main(){
    ignored(freopen("alice.in","r",stdin));
    ignored(freopen("alice.out","w",stdout));
    ignored(scanf("%lld",&n));
    for(int i=1;i<=n;++i){
        ignored(scanf("%lld",&a[i]));
    }
    for(int i=1;i<=n;++i){
        ignored(scanf("%lld",&b[i]));
    }
    for(int i=0;i<=n;++i){
        for(int j=0;j<=n;++j){
            f[i][j]=-0x3f3f3f3f3f3f3f3f;
        }
    }
    int ans=1;
    for(int i=1;i<=n;++i) f[0][i]=0;
    for(int i=1;i<=n;++i) f[1][i]=1;
    for(int i=2;i<=n;++i){
        for(int j=i;j<=n;++j){
            for(int k=i-1;k<j;++k){
                if(a[k]*b[i-1]<a[j]){
                    f[i][j]=max(f[i][j],f[i-1][k]+1);
                    ans=max(ans,f[i][j]);
                }
            }
        }
    }
    cout<<ans<<'\n';
}

题解 \(n^2\) 做法

\(f_{i,j}\) 表示考虑到第 \(i\) 个数,当前选择的长度为 \(j\) 的最小值

这个状态设计和最长上升子序列很像,这么做的正确性可以考虑贪心证明

那么 \(f_{i,j}\) 的转移有两种来源:

  • 不选 \(a_i\),从 \(f_{i-1,j}\) 转移来
  • \(a_i\),从 \(a_i\) 转移来
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename T>void ignored(T x){}
int n;
int a[1001],b[1001];
int f[1001][1001];
signed main(){
	freopen("alice.in","r",stdin);
	freopen("alice.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
	}	
	for(int i=1;i<=n;++i){
		scanf("%lld",&b[i]);
	}
	memset(f,0x3f,sizeof f);
	for(int i=0;i<=n;++i) f[i][0]=0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=i;++j){
			f[i][j]=f[i-1][j];
			if(f[i-1][j-1]<0x3f3f3f3f3f3f3f3f and a[i]>f[i-1][j-1]*b[j-1]){
				f[i][j]=min({f[i][j],a[i]});
			}
		}
	}
	for(int i=n;i>=1;--i){
		for(int j=1;j<=n;++j){
			if(f[j][i]<0x3f3f3f3f3f3f3f3f){
				cout<<i<<endl;
				return 0;
			}
		}
	}
}

同理,可以压到一维

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename T>void ignored(T x){}
int n;
int a[1000001],b[1000001];
int f[1000001];
signed main(){
	freopen("alice.in","r",stdin);
	freopen("alice.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
	}	
	for(int i=1;i<=n;++i){
		scanf("%lld",&b[i]);
	}
	memset(f,0x3f,sizeof f);
	f[0]=0;
	for(int i=1;i<=n;++i){
		for(int j=i;j>=1;--j){
			if(f[j-1]<0x3f3f3f3f3f3f3f3f and a[i]>f[j-1]*b[j-1]){
				f[j]=min(f[j],a[i]);
			}
		}
	}
	for(int i=n;i>=1;--i){
		if(f[i]<0x3f3f3f3f3f3f3f3f){
			cout<<i<<endl;
			return 0;
		}
	}
}

压到一维之后我们发现,\(f\) 数组始终是单调的,这意味着我们可以直接在 \(f\) 上二分找值,\(n\log n\) 就做完了

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename T>void ignored(T x){}
int n;
int a[1000001],b[1000001];
int f[1000001];
set<int>s;
signed main(){
	freopen("alice.in","r",stdin);
	freopen("alice.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
	}	
	for(int i=1;i<=n;++i){
		scanf("%lld",&b[i]);
	}
	memset(f,0x3f,sizeof f);
	f[0]=0;
	for(int i=1;i<=n;++i){
		int tmp=upper_bound(f+1,f+i+1,a[i])-f-1;
		if(f[tmp]<0x3f3f3f3f3f3f3f3f and a[i]>f[tmp]*b[tmp]){
			f[tmp+1]=min(f[tmp+1],a[i]);
		}
	}
	for(int i=n;i>=1;--i){
		if(f[i]<0x3f3f3f3f3f3f3f3f){
			cout<<i<<endl;
			return 0;
		}
	}
}

B.Bob 与幸运日

D.David 与和谐号

赛时想到迭代加深了,但是不会剪枝

赛时裸的迭代加深贴一下

点击查看代码
#include<bits/stdc++.h>
using namespace std;
template<typename T>void ignored(T x){}
int n;
int a[30],b[30];
bool check(){//判断当前状态是否合法
    for(int i=1;i<=n;++i){
        if(a[i]!=i) return false;
    }
    return true;
}
bool dfs(int now,int lim){//当前旋转了 now 次,旋转上限是 lim
    if(now>lim) return false;
    if(check()) return true;
    for(int i=2;i<=n;++i){
            std::reverse(a+1,a+i+1);
            if(dfs(now+1,lim)) return true;
            std::reverse(a+1,a+i+1);
    }
    return false;
}
bool check(int ans){
    return dfs(0,ans);
}
int main(){
    ignored(freopen("david.in","r",stdin));
    ignored(freopen("david.out","w",stdout));
    int t;cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>b[i];
        }
        int ans=0;
        while(1){
            for(int i=1;i<=n;++i){
                a[i]=b[i];
            }
            if(check(ans)){//从小到大枚举旋转次数,判断是否合法
                cout<<ans<<'\n';
                break;
            }
            ans++;
        }
    }
}

迭代加深有点像广搜,为什么你深搜做不了最短路径的题,是因为深搜第一次碰到的合法状态不一定是最小的,只是最先碰到的,这个最先碰到的状态可能是一个比较深的节点

我们为了能让深搜做出这种最短路径题,可以对值域 \(V\)\(V\) 遍深搜(有单调性亦可二分答案),每次限值搜索的深度,找出什么时候合法,这就是迭代加深

迭代加深效率显然是不如广搜的,但是它具有深搜那种可以直接在搜索中修改的优点,因此用的也比较多

这道题的剪枝

  • 可行性剪枝:题解的大力剪枝:每次翻转只会改变一对相邻数对,也就是说如果两个相邻的值在值域上不相邻,那么就一定要通过至少一次旋转来解决(值域相邻的值不一定需要对每个断点应用一次旋转,比如 321 可以只旋转一次到位,这是因为其满足了旋转后有序的必要条件,即旋转前也是有序的,但如果存在值域不相邻的数,如 2413 则一定需要对每个断点至少一次旋转)
  • 启发式剪枝:发现转一次之后再马上转回来一定不优,因此判掉这个枝
#include<bits/stdc++.h>
using namespace std;
template<typename T>void ignored(T x){}
int n;
int a[30],b[30];
inline int check(){
	int res=0;
    for(int i=1;i<=n-1;++i){
        if(abs(a[i]-a[i+1])!=1) res++;
    }
    return res+(a[n]!=n);  //这里一定要注意 n 是不是在自己位置上,可能导致下面 tmp==0 错判
}
bool dfs(int now,int lim,int last){
    int tmp=check();
    if(now+tmp>lim) return false;
	if(now==lim) return tmp==0;
    for(int i=2;i<=n;++i){
        if(i!=last){
            std::reverse(a+1,a+i+1);
            if(dfs(now+1,lim,i)) return true;
            std::reverse(a+1,a+i+1);
        }
    }
    return false;
}
inline bool check(int ans){
    return dfs(0,ans,0);
}
int main(){
    ignored(freopen("david.in","r",stdin));
    ignored(freopen("david.out","w",stdout));
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
           scanf("%d",&b[i]);
        }
        int ans=0;
        while(1){
            for(int i=1;i<=n;++i){
                a[i]=b[i];
            }
            if(check(ans)){
                cout<<ans<<'\n';
                break;
            }
            ans++;
        }
    }
}
这是什么

posted @ 2024-10-24 19:26  HaneDaniko  阅读(27)  评论(3编辑  收藏  举报