牛客练习赛113

题目链接


c

考虑到 \(x\)\(1\) , 我们可以枚举 \(y\) 减了多少次 , 那么根据同余方程 $ sum + i * y + j * x \equiv 0 \space (mod \ n)$ , 注意 \(y\) 次数 (\(i\))枚举范围就是 \([0 , n - 1]\) , 超过 \(n\) ,根据乘法取模的性质 ,超过 \(n\) 会取模回 \([0 , n - 1]\)

#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n,p,x,q,y;
	std::cin >> n >> p >> x >> q >> y;
	
	int s = 0;
	for(int i = 0 ; i < n ; ++i) {
		int x ; std::cin >> x;
		(s += x) %= n;
	}
	
	ll mn = 1E18;
	for(int i = 0 ; i < n ; ++i) {
		int j = ( n - ( (s - 1ll * i * y % n) % n + n ) % n ) % n;
		mn = std::min(mn , 1ll * i * q + 1ll * j * p);
	}
	
	std::cout << mn << '\n';
	
	return 0;
}

D

同理上一问的做法 , 我们去枚举 \(y\) 的减的次数 \(i\) , 但此时 \(x\) 为任意数

\(j * x \equiv -(sum + i * y) \ (mod \ n)\) , 令 \(-(sum + i * y)\)\(mod \ n\) 意义下为 \(b\)
问题转换为 \(j * x \equiv b \ (mod \ n)\) , 求 \(j\) 的最小值

\(① : 方法一\)
考虑到 \(j\) 的范围也是\([0 , n - 1]\) , 直接预处理出所有的 $ k*i \ (mod \ n)$的值对应最小值 \(k\) 即可 , \(O(1)\) 直接查询 \(b\) 即可

\(② : 方法二\)
对于 \(j * x \equiv b \ (mod \ n)\)\(j\) 的做法 , 在乘法逆元中就有提及 , \(j * x \equiv b \ (mod \ n)\) 等价于 \(j * x + t * n \equiv b \ (mod \ n)\) , 求 \((x , n , b)\) 的不定方程再把 \(j\)最小非负数求出来即可 ( 注意不关心 \(t\) 是多少 , 并不会限制 \(j\) , 直接取模求最小值即可 )

//方法一
#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	ll n,p,x,q,y;
	std::cin >> n >> p >> x >> q >> y;
	
	ll s = 0;
	for(ll i = 0 ; i < n ; ++i){
		ll w ; std::cin >> w;
		s += w , s %= n;
	}
	
	std::vector<ll> mp(n , n);
	for(ll j = 0 ; j < n ; ++j) {
		ll t = j * x % n;
		mp[t] = std::min(mp[t] , j);
	}
	
	ll mn = 1E18;
	for(ll i = 0 ; i < n ; ++i) {
		ll t = 	( (s - i * y) % n + n ) % n;
		t = (n - t) % n;
		if(mp[t] < n) {
			mn = std::min(mn , i * q + mp[t] * p);
		}
	}
	
	if(mn == 1E18)
		std::cout << -1 << '\n';
	else 
		std::cout << mn << '\n';
		
	return 0;
}

//方法二
#include <bits/stdc++.h>
using ll = long long;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	
	ll n,p,x,q,y;
	std::cin >> n >> p >> x >> q >> y;
	
	ll s = 0;
	for(ll i = 0 ; i < n ; ++i) {
		ll w ; std::cin >> w;
		( s += w ) %= n; 
	}
	
	std::function<ll(ll,ll,ll&,ll&)> exgcd = [&](ll a,ll b,ll &x1,ll &y1) {
		if(!b) {
			x1 = 1 , y1 = 0;
			return a;
		}	
		
		ll d = exgcd(b , a % b , x1 , y1);
		ll x2 = x1 , y2 = y1;
		x1 = y2;
		y1 = x2 - a / b * y2;
		return d;
		
	};
	
	ll mn = 1E18,a,b;
	ll d = exgcd(x , n , a , b);
	ll mod = n / d;
	
	for (ll j = 0 ; j < n ; ++j) {
		
		ll t =  ( (s - j * y) % n + n ) % n;
		t = (n - t) % n;
		
		ll _a = a;
		if(t % d != 0) continue;
		_a = _a * t / d;	
		_a =( _a % mod + mod ) % mod;
		mn = std::min(mn , j * q + _a * p); 
	
	}
	
	if(mn == 1E18)
		std::cout << -1 << '\n';
	else
		std::cout << mn << '\n';
		
	return 0;
}

想到了杭州站\(A\)江西省赛 B


G

代码注释了用到的枚举和去重技巧

#include <bits/stdc++.h>
using ll = long long;
const ll mod = 1E9 + 7;

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	int n;
	std::cin >> n;
	
	std::map<int,int> mp;
	for (int i = 0 ; i < n ; ++i) {
		int x ; std::cin >> x;
		mp[x] += 1;
	}
	
	std::vector<int> p;
	for (auto x : mp) {
		p.push_back(x.second);
	}
	
	std::sort(p.begin(),p.end(),std::greater<int>());
	
	std::vector<ll> fac(n + 1) , inv(n + 1) , invFac(n + 1);
	
	fac[0] = 1 , fac[1] = 1 ;
	inv[1] = 1 ;
	invFac[0] = 1 , invFac[1] = 1;
	
	for (int i = 2 ; i <= n ; ++i) {
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
		invFac[i] = invFac[i - 1] * inv[i] % mod;	
	} 
	
	auto C = [&] (int n , int m) {
		if (m > n) {
			return 0ll;
		}
		return fac[n] * invFac[m] % mod * invFac[n - m] % mod; //求逆元下的组合数 , 记得处理 invFac[0] = 0 
	};
	
	//注意这种枚举思想 , easy 中我们去确定总长 , 在枚举代表元素的长度
	//这里直接反过来 , j+k 去拼凑总长 
	ll ans = 0;
	for (auto i : p) {
		ll cnt = 1; // 注意到内循环下标从 0 开始 , 所以有 1 的初始值 
		for (int j = 1 ; j <= i ; ++j) {
			cnt = ( cnt + C(n - i , j) ) % mod;
			ans = ( ans % mod + C(i , j) * cnt % mod ) % mod;
		}
	}
	
	//枚举的 j 为 类[1,1,2,2]情况下数组长度的一半 
	//可以想想这种去重枚举的时间复杂度
	//p的最坏构造 : 1+2+3+4+5+.. = n , p[0]最大为 sqrt(n) , 此时数组的个数为 sqrt(n)
	//sqrt(n) * sqrt(n) = n;
	 
	for(int j = 1 ; j <= p[0] ; ++j) {
		ll cnt = 0;
		for (auto i : p) {
			if (i < j) {
				break;
			}
			ans -= C(i , j) * cnt % mod; //i小的放前面枚举 , 重复算两次中的一种 
			cnt = ( cnt + C(i , j) ) % mod; 
		}
	}	
	
	ans = ( (ans % mod) + mod ) % mod;
	
	std::cout << ans << '\n';
	
	return 0;
}

posted @ 2023-07-09 19:14  xqy2003  阅读(31)  评论(0)    收藏  举报