牛客 周赛102 20250806

牛客 周赛102 20250806

https://ac.nowcoder.com/acm/contest/114111

A:
题目大意:

void solve(){
	int n;
	cin>>n;
	cout<<2<<endl;
}

签到

B:
题目大意:

给定包含三个 \(1\)\(01\) 字符串,判断两个相邻 \(1\) 之间的距离是否相同

void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	int st=0;
	int cnt1=0,cnt2=0;
	for (auto c:s){
		if (c=='1'&&st==0)
			st=1;
		else if (c=='1'&&st==1)
			st=2;
		else if (c=='1'&&st==2)
			break;
		if (c=='0'&&st==1) cnt1++;
		if (c=='0'&&st==2) cnt2++;
	}
	if (cnt1==cnt2) cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
}

模拟

C:
题目大意:

image-20250805213059378

void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	vector<int> a,b;
	for (auto c:s) a.push_back(c-'0'),b.push_back(c-'0');
	int cnt1=0,cnt2=0;
	for (int i=0;i<n-1;i++){
		if (i%2==0&&a[i]!=1){
			a[i]^=1;
			a[i+1]^=1;
			cnt1++;
		}
		if (i%2==1&&a[i]!=0){
			a[i]^=1;
			a[i+1]^=1;
			cnt1++;
		}
	}
	bool f1=1;
	for (int i=1;i<n;i++){
		if (a[i]==a[i-1]) f1=0;
	}
	for (int i=0;i<n-1;i++){
		if (i%2==0&&b[i]!=0){
			b[i]^=1;
			b[i+1]^=1;
			cnt2++;
		}
		if (i%2==1&&b[i]!=1){
			b[i]^=1;
			b[i+1]^=1;
			cnt2++;
		}
	}
	bool f2=1;
	for (int i=1;i<n;i++){
		if (b[i]==b[i-1]) f2=0;
	}
	if(f1&&f2) cout<<min(cnt1,cnt2)<<endl;
	else if (f1) cout<<cnt1<<endl;
	else if (f2) cout<<cnt2<<endl;
	else cout<<-1<<endl;	
}

有且仅有两种可能 \(0101\cdots,1010\cdots\),分别判断这两种情况是否能合法得到

贪心的,如果存在一个前缀串符合这样的形式,那么修改这个前缀串一定劣

D:
题目大意:

image-20250805213314712

void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	s=' '+s;
	vector<int> cnt(n+1,0);
	for (int i=1;i<=n;i++) cnt[i]=cnt[i-1]+(s[i]=='1');
	int ans=1e9;
	for (int i=1;i<n;i++){
		for (int j=i+1;j<n;j++){
			for (int k=j+1;k<n;k++){
				int A1=cnt[i]-cnt[0];
				int B1=cnt[j]-cnt[i];
				int C1=cnt[k]-cnt[j];
				int D1=cnt[n]-cnt[k];
				int A0=i-A1;
				int B0=(j-i)-B1;
				int C0=(k-j)-C1;
				int D0=(n-k)-D1;
				int res=min(A1+B0+C1+D0,A0+B1+C0+D1);
				ans=min(ans,res);
			}
		}
	}
	cout<<ans;
}

考虑符合要求的字符串只有 \(0101,1010\) 形式的符合要求,连续的 \(1\)\(0\) 对答案的的贡献可以看作只有一个 \(1\)\(0\)

所以枚举三个分割点,计算总代价最小的方案,时间复杂度 \(O(n^3)\)

E:
题目大意:

image-20250805214143597

const int N=2e5+10;

int dp[N];

void init(){
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for (int i=1;i<=sqrt(2*N)+1;i++)
		for (int j=1;j<=N;j++)
			if (i*(i+1)/2<=j)
				dp[j]=min(dp[j],dp[j-i*(i+1)/2]+i+1);
}

void solve(){
	int k;
	cin>>k;
	cout<<dp[k]-1<<endl;
}

类似背包问题,在限定容量为 \(k\) 的背包中选取体积最小的方案

\(dp_{i,j}\) 表示从前 \(i\) 个物品选出权值为 \(j\) 的物品组合的最小体积,和 01 背包一样,可以优化掉第一维度

长为 \(n\) 的连续 \(1\) 字符串产生的贡献为 \(n(n+1)/2\) ,所以至多有 \(\sqrt n\) 个物品可以选择,时间复杂度为 \(O(n\sqrt n)\)

注意需要加上隔开连续 \(1\) 字符串的 \(0\) 字符的数量

F:
题目大意:

image-20250805214610951

const int N=2e5+10;

int dp[N];
int last[N];

void init(){
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for (int i=1;i<=sqrt(2*N)+1;i++){
		for (int j=1;j<=N;j++){
			if (i*(i+1)/2<=j){
				if (dp[j]>dp[j-i*(i+1)/2]+i+1){
					dp[j]=dp[j-i*(i+1)/2]+i+1;
					last[j]=i;
				}
			}
		}
	}
}

void solve(){
	int k;
	cin>>k;
	string s;
	while (k){
		for (int i=1;i<=last[k];i++) s+='1';
		s+='0';
		k-=last[k]*(last[k]+1)/2;
	}
	s.erase(s.end()-1);
	cout<<s<<endl;
}

\(last\) 数组记录状态转移的方向,不改变时间复杂度,输出方案时从后往前递推状态

G:
题目大意:

image-20250805214829752

std::mt19937_64 rng;

LL getrand(int l,int r){
	std::uniform_int_distribution<LL> distribution(l,r);
	return distribution(rng);
}

const int N=2e5+10;

struct que{
	int l,r,id;
};

void solve(){
	int n,m;
	cin>>n>>m;
	vector<int> a(n+1);
	vector<LL> h(N);
	vector<LL> prexor(n+1);
	for (int i=1;i<=n;i++) cin>>a[i];
	
	vector<__int128> sum(n+1), ssum(n+1), sssum(n+1);
	for(int i=1;i<=n;i++){
	    sum[i] = sum[i-1] + a[i];
	    ssum[i] = ssum[i-1] + (__int128)a[i]*a[i];
	    sssum[i] = sssum[i-1] + (__int128)a[i]*a[i]*a[i];
	}
	
	for (int i=1;i<N;i++) h[i]=getrand(1,1e18);
	for (int i=1;i<=n;i++) prexor[i]=h[a[i]]^prexor[i-1];
	for (int i=1;i<=m;i++){
		int l,r;
		cin>>l>>r;
		int d=r-l+1;
		int dd=d/2;
		if (d%2){
			cout<<"No"<<endl;
			continue;
		}
		if (dd<=500){
			vector<int> cnt(N);
			bool f=1;
			for (int j=l;j<=r;j++) cnt[a[j]]++;;
			for (int j=1;j<=dd;j++){
				if (cnt[j]!=2){
					f=0;
					break;
				}
			}
			if (f) cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}else{
			if ((prexor[r]^prexor[l-1])!=0){
				cout<<"No"<<endl;
				continue;
			}
			if ((__int128)dd*(dd+1)!=sum[r]-sum[l-1]){
				cout<<"No"<<endl;
				continue;
			}
			if ((__int128)dd*(dd+1)*(2*dd+1)/3!=ssum[r]-ssum[l-1]){
				cout<<"No"<<endl;
				continue;
			}
			if ((__int128)dd*dd*(dd+1)*(dd+1)/2!=sssum[r]-sssum[l-1]){
				cout<<"No"<<endl;
				continue;
			}
			cout<<"Yes"<<endl;
		}
	}
}

哈希做法,一段区间构成双排列的必要条件是:

\[2n=l-r+1,n\in N^+\\ \sum_{i=l}^r a_i=\frac{n(n+1)}{2}\\\ \sum_{i=l}^r a_i^2=\frac{n(n+1)(2n+1)}{6}\\ \sum_{i=l}^r a_i^3=\frac{n^2(n+1)^2}{4}\\ \cdots \]

维护区间的前缀和可以判断是否满足哈希函数,哈希函数发生冲突的概率与区间的大小成负相关

所以可以采取分治的方式将 \(n\le500\) 的数据进行暴力计算,较大的区间利用哈希值检验是否正确

这样发生哈希冲突的概率极低,可以视作充分必要条件

posted @ 2025-08-08 20:52  才瓯  阅读(6)  评论(0)    收藏  举报