noip模拟30[毛毛毛探探探]

noip模拟30 solutions

所以说,这次被初中的大神给爆了?????

其实真的不甘心,这次考场上的遗憾太多,浪费的时间过多,心情非常不好

用这篇题解来结束这场让人伤心的考试吧

T1 毛一探

其实这个题我本来是考场上就能AC的

不得不说这个\(meet \;in\;the\;middle\)思想真的没谁了。

我在考场上一分钟想出来如何用一个复杂度不确定的办法来搞定他

(这个复杂度最劣是\(\mathcal{O(2^{n+1})}\),但是数据比较善良,给了我75pts,但是其实我这个方法就是正解,就是折半的思想)

想完之后我就觉得有点不太好,就开始优化我的暴力,\(1h20min\;later\),我醒悟了

发现自己根本优化不了,所以打了个我的算法,又打了一个更暴力的暴力,拍了拍没错。。。

我的暴力是根据两个人的分值的和相同来搞得,用了map和vector,存储能够达到每个可能的和的方案(也就是一个20位的二进制数)

每次找到一个和,就遍历相同的和的其他方案,如果没有重复的位,那么就标记,最后统计答案

75pts(暴力)
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define pa pair<int,int>
#define mpa(x,y) make_pair(x,y)
#define fi first
#define se second
const int N=25;
int n,m[N];
int dp[1<<20],ans,cnt;
bool vis[1<<20];
unordered_map<int,int> mp;
vector<int> vec[1<<20];
ll jc[N];
ll C(int x,int y){
	return jc[x]/jc[y]/jc[x-y];
}
signed main(){
	int a=scanf("%d",&n);
	bool flag=0;
	for(re i=1;i<=n;i++){
		a=scanf("%d",&m[i]);
		if(m[i]!=m[i-1]&&i!=1)flag=true;
	}
	/*if(!flag){
		jc[0]=1;for(re i=1;i<=n;i++)jc[i]=jc[i-1]*i;
		for(re i=2;i<=n;i+=2)ans+=C(n,i);
		printf("%d",ans);
		return 0;
	}*/
	dp[0]=0;
	for(re i=1;i<=n;i++)dp[1<<(i-1)]=m[i];
	for(re i=1;i<(1<<n);i++){
		dp[i]=dp[i-(i&(-i))]+dp[i&(-i)];
		if(mp.find(dp[i])==mp.end())
			mp.insert(mpa(dp[i],++cnt));
		int who=mp.find(dp[i])->se;
		for(re j=0;j<vec[who].size();j++){
			if(i&vec[who][j])continue;
			vis[i|vec[who][j]]=true;
		}
		vec[who].push_back(i);
	}
	for(re i=1;i<(1<<n);i++)
		if(vis[i])ans++;
	printf("%d",ans);
	return 0;
}

所以正解其实跟我的枚举方式差不多,不过是把原序列拆成两部分,

这一次我们无法枚举两个人分别的和,所以此时枚举状态的复杂度是\(\mathcal{O(3^{\frac{n}{2}})}\)

因为一道题可以不选,第一个人,第二个人。。

那我们统计答案的时候就需要利用两侧的选出来的两个人的元素之和的差值,

因为此时只要两测的两个人的差值相同,那么就会产生一个合法的答案

还是利用map和vector,只不过这次存的是两侧的差值,

还是最后直接统计答案,虽然我不会手写hash表,但是我跑的飞快

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define mpa(x,y) make_pair(x,y)
#define se second
#define fi first
const int N=25;
int n,n1,n2,w[N];
map<int,int> mp;
vector<int> vec[1<<20];
int cnt,ans;
bool vis[1<<20];
void dfs1(int now,int mx,int sum1,int sum2,int s){
	if(now==mx+1){
		int tmp=abs(sum1-sum2);
		if(mp.find(tmp)==mp.end())
			mp.insert(mpa(tmp,++cnt));
		int who=mp.find(tmp)->se;
		vec[who].push_back(s);
		return ;
	}
	dfs1(now+1,mx,sum1+w[now],sum2,s|(1<<now-1));
	dfs1(now+1,mx,sum1,sum2+w[now],s|(1<<now-1));
	dfs1(now+1,mx,sum1,sum2,s);
}
void dfs2(int now,int mx,int sum1,int sum2,int s){
	if(now==mx+1){
		int tmp=abs(sum1-sum2);
		if(mp.find(tmp)==mp.end())return ;
		int who=mp.find(tmp)->se;
		for(re i=0;i<vec[who].size();i++)
			vis[s<<n1|vec[who][i]]=true;
		return ;
	}
	dfs2(now+1,mx,sum1+w[now+n1],sum2,s|(1<<now-1));
	dfs2(now+1,mx,sum1,sum2+w[now+n1],s|(1<<now-1));
	dfs2(now+1,mx,sum1,sum2,s);
}
signed main(){
	scanf("%d",&n);
	n1=n/2;n2=n-n1;
	for(re i=1;i<=n;i++)scanf("%d",&w[i]);
	dfs1(1,n1,0,0,0);
	dfs2(1,n2,0,0,0);
	for(re i=1;i<(1<<n);i++)
		if(vis[i])ans++;
	printf("%d",ans);
}

说实话,这么多map的用法都是之前看一个std看到的

T2 毛二探

哈哈哈,这个题是我头一次想到dp题的一点点思路(考场上的时候)

然后考完试改这个题的时候只改了半个小时就过了,不得不说前缀和优化也是个好东西

就你先根据每个数的移动情况判断一下每个移动的优先级顺序

要是有一个顺序被赋值成两个不同的优先级,那么就直接输出零,因为一个排列不可能有两种优先级

如果知道了这些,那转移方程也就极其的好想了。

\(f[i][j]\)表示我们目前处理q(就是那个优美序列,用来规划转移的)的第i个数,放在第j个位置

那么转移就是直接从上一层转移,如果当前交换需要比上一个早的话,那就插在上一个位置的前面,如果晚就放在后面。。。。这不用我说也知道吧

所以有了我们当前的\(\mathcal{O(n^3)}\)的转移,可以拿到70pts

O(n^3)暴力转移
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=5005;
const ll mod=1e9+7;
int n,w[N],mov[N];
int typ[N];
ll f[N][N],ans;
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++){
		scanf("%d",&w[i]);
		w[i]++;
	}
	for(re i=1;i<=n;i++)
		mov[w[i]]=i-w[i];
	int flag=0;
	for(re i=1;i<=n;i++){
		if(mov[i]==0)flag=1;
		if(mov[i]>0){
			for(re j=i+1;j<i+mov[i];j++){
				if(typ[j])flag=1;
				typ[j]=2;
			}
		}
		if(mov[i]<0){
			for(re j=i-1;j>i+mov[i];j--){
				if(typ[j])flag=1;
				typ[j]=1;
			}
		}
	}
	if(flag){
		printf("0");
		return 0;
	}
	f[1][1]=1;
	for(re i=2;i<n;i++){
		for(re j=1;j<=i-1;j++){
			if(typ[i]==1){
				for(re k=1;k<=j;k++)
					f[i][k]=(f[i][k]+f[i-1][j])%mod;
			}
			else{
				for(re k=j+1;k<=i;k++)
					f[i][k]=(f[i][k]+f[i-1][j])%mod;
			}
		}
	}
	for(re i=1;i<n;i++)ans=(ans+f[n-1][i])%mod;
	printf("%lld",ans);
}

所以你打完这个暴力转移之后,你发现每一个状态都是从前一个的状态的一个连续区间转移来的

所以就可以直接前缀和优化,\(\mathcal{O(1)}\)转移了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=5005;
const ll mod=1e9+7;
int n,w[N],mov[N];
int typ[N];
ll f[N][N],ans,sum[N][N];
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++){
		scanf("%d",&w[i]);
		w[i]++;
	}
	for(re i=1;i<=n;i++)
		mov[w[i]]=i-w[i];
	int flag=0;
	for(re i=1;i<=n;i++){
		if(mov[i]==0)flag=1;
		if(mov[i]>0){
			for(re j=i+1;j<i+mov[i];j++){
				if(typ[j])flag=1;
				typ[j]=2;
			}
		}
		if(mov[i]<0){
			for(re j=i-1;j>i+mov[i];j--){
				if(typ[j])flag=1;
				typ[j]=1;
			}
		}
	}
	if(flag){
		printf("0");
		return 0;
	}
	f[1][1]=1;
	sum[1][1]=1;
	for(re i=2;i<n;i++){
		for(re j=1;j<=i-1;j++){
			if(typ[i]==1){
				f[i][j]=(f[i][j]+sum[i-1][i-1]+mod-sum[i-1][j-1])%mod;
			}
			else{
				f[i][j+1]=(f[i][j+1]+sum[i-1][j]+mod)%mod;
			}
		}
		for(re j=1;j<=i;j++){
			sum[i][j]=(sum[i][j-1]+f[i][j])%mod;
		}
	}
	for(re i=1;i<n;i++)ans=(ans+f[n-1][i])%mod;
	printf("%lld",ans);
}

T3 毛三探

这个题就非常神奇的卡过了

直接枚举x,二分此时x能达到的最大重量的最小值,因为必须连续,判断直接\(\mathcal{O(n)}\)

如果只是这样那你就过不了了,

注意判断一下之前的x中的最有答案能不能合法,合法再去二分,不合法直接进行下一个x

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=10005;
int n,p,k,w[N],no[10005];
int ans=0x3f3f3f3f,sum;
bool check(int x){
	int j=1;
	for(re i=1;i<=k;i++){
		int now=0;
		for(;j<=n;j++){
			if(now+no[j]>x)break;
			now+=no[j];
		}
		if(j==n+1)return true;
	}
	return false;
}
signed main(){
	scanf("%d%d%d",&n,&p,&k);
	for(re i=1;i<=n;i++)scanf("%d",&w[i]),sum+=w[i];
	for(re x=0;x<p;x++){
		int l=0,r=sum,mid;
		for(re i=1;i<=n;i++)no[i]=(w[i]+x)%p;
		if(!check(ans))continue;
		while(l<r){
			mid=l+r>>1;
			if(check(mid))r=mid;
			else l=mid+1;
		}
		ans=min(ans,r);
	}
	printf("%d",ans);
}
posted @ 2021-08-04 17:36  fengwu2005  阅读(111)  评论(0编辑  收藏  举报