22牛客多校 - NIO‘s Sword (思维,取模,推公式),Jobs1 (二维前缀min,去劣),Particle Arts (思维,方差)

NIO’s Sword

题意
一共有 n 个怪兽,有一把初始能量为 0 的宝剑。
每次可以在某一时刻将宝剑的能量升级:如果原来能量为 a a a,升级之后能量变为 a ∗ 10 + x a*10 + x a10+x,x 为 0 − 9 0-9 09 中任选的一个数。
如果想要击败第 i   ( 2 ≤ i ≤ n ) i\ (2\le i\le n) i (2in) 只怪兽,需要保证第 i − 1 i-1 i1 只怪兽已经被击败。
在击败第 i 只怪兽时,宝剑的能量 a 需满足 A ≡ i   (   m o d     n ) . A≡i\ (\bmod\ n). Ai (mod n).
问,击败所有怪兽最少需要升级多少次?

1 ≤ n ≤ 1 0 6 1\le n\le 10^{6} 1n106

思路
如果击败第 i 个怪兽时,宝剑的能量值为 a,并且 a%n = i,而要击败第 i+1 个怪兽是由 a 继续升级的,也就是由 i 继续升级,所以就是看将值 i 升级到 t 满足 t%n = (i+1)%n 最少需要升级多少次。

升级 1 次后的能量为: a ∗ 10 + x a*10 + x a10+x,x 属于 [ 0 , 9 ] [0, 9] [0,9]
升级 2 次后的能量为: a ∗ 1 0 2 + x a*10^2 + x a102+x,x 属于 [ 0 , 99 ] [0, 99] [0,99]

升级 k 次的能量为: a ∗ 1 0 k + x a*10^k + x a10k+x,x 属于 [ 0 , 1 0 k − 1 ] [0, 10^k-1] [0,10k1]

要使得: ( a ∗ 1 0 k + x )   m o d   n = b   m o d   n (a*10^k + x)\bmod n = b \bmod n (a10k+x)modn=bmodn

  • 如果 a ∗ 1 0 k (   m o d   n ) ≤ b (   m o d   n ) a*10^k(\bmod n) \le b (\bmod n) a10k(modn)b(modn) 时, x = b (   m o d   n ) − a ∗ 1 0 k (   m o d   n ) x = b(\bmod n)-a*10^k(\bmod n) x=b(modn)a10k(modn)
  • 否则, ( a ∗ 1 0 k + x )   m o d   n (a*10^k + x)\bmod n (a10k+x)modn 就应该变为 b (   m o d   n ) + n b(\bmod n)+n b(modn)+n,要使得 x 尽可能小,那么 x = n + b (   m o d   n ) − a ∗ 1 0 k (   m o d   n ) x = n+b (\bmod n)-a*10^k(\bmod n) x=n+b(modn)a10k(modn)

如果 x 在这次升级所能达到的范围内的话,说明就是这次升级就是满足的。

而 n 最大到 1e6,那么 x 最多 6 次就可以构造出任意小于 n 的值,所以最多升级 6 次,暴力枚举每次升级。

最后注意当 n 为 1 时,任意能量值 a 都同余于 1%1,所以能量值为0也行,不需要升级,注意特判。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

int pd(int a, int b)
{
	int k = 0;
	while(1)
	{
		k ++;
		a = a*10 % n;
		
		int x;
		if(a <= b) x = b - a;
		else x = n + b - a;
		if(x <= pow(10, k)-1) break;
	}
	return k;
}

signed main(){
	Ios;
	cin >> n;
	
	if(n == 1){
		cout << 0; return 0;
	}
	
	int ans = 0;
	for(int i=0;i<n;i++)
	{
		ans += pd(i, (i+1)%n);
	}
	cout << ans;
	
	return 0;
}

经验
这样的取模的式子一直不会化,总觉得取模之后原来的数就变的面目全非了,然后就不敢往下化,怕不等。
其实取模在乘法和加法运算时和原式是毫无影响的,该咋化就咋化,不用怕。

感谢队友大半夜教会我!!


Jobs (Easy Version)

题意
给定 n 个集合,每个集合有 mi 个三元组 { x j , y j , z j x_j, y_j, z_j xj,yj,zj}。
一共 q 次询问,每次询问给定一个三元组 {a, b, c},问一共有多少个集合满足,其中存在一个三元组,其三个元素都不超过 {a, b, c}?

1 ≤ n ≤ 10 ,   1 ≤ m i ≤ 1 0 5 ,   1 ≤ q ≤ 2 × 1 0 6 ,   1 ≤ x j , y j , z j ≤ 400 1 \le n \le 10,\ 1≤m_i≤10 ^5,\ 1\le q \le 2\times 10^6,\ 1 \le x_j,y_j,z_j \le 400 1n10, 1mi105, 1q2×106, 1xj,yj,zj400

思路
一共 1e6 次询问,每次询问要处理 10 个集合,那么留给每个集合的计算时间就很少了,暴力枚举不行。

一种非常巧妙的思路是,构造二维前缀最小值。
对于三元组{x, y, z},可以将矩阵 (x, y) 位置上的值 f[x, y] 置为 z。
最后将 f[][] 数组更新成前缀最小值,即 (1,1) 位置到 (x, y) 位置的所有位置中,z 的最小值。
后面对于每次询问 {a, b, c},肯定要从 (1, 1) 到 (a, b) 位置中找到最小值,即 f[a, b],判断其和 c 的大小关系。如果最小值不超过 c,则说明存在某个位置 (x, y),其值 z = f[x, y] 不超过 c,即找到满足三元组。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 998244353;
int T, n, m;
int a[N];
int f[11][410][410];

int solve(int x, int y, int z)
{
	int cnt = 0;
	for(int i=1;i<=n;i++)
	{
		if(f[i][x][y] <= z) cnt++;
	}
	return cnt;
}

int qmi(int x, int y)
{
	int ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

signed main(){
	Ios;
	cin >> n >> m;
	
	for(int i=0;i<=n;i++)
		for(int j=0;j<=400;j++)
			for(int k=0;k<=400;k++)
				f[i][j][k] = 1e9;
	
	for(int i=1;i<=n;i++)
	{
		int k; cin >> k;
		while(k--)
		{
			int x, y, z; cin >> x >> y >> z;
			f[i][x][y] = min(f[i][x][y], z);
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=400;j++){
			for(int k=1;k<=400;k++)
			{
				f[i][j][k] = min(f[i][j][k], f[i][j-1][k]);
				f[i][j][k] = min(f[i][j][k], f[i][j][k-1]);
			}
		}
	}
	
	int seed;
	cin >> seed;

	std::mt19937 rng(seed);
	std::uniform_int_distribution<> u(1,400);
	
	int lastans=0, ans = 0;
	for (int i=1;i<=m;i++)
	{
	    int IQ=(u(rng)^lastans)%400+1;  // The IQ of the i-th friend
	    int EQ=(u(rng)^lastans)%400+1;  // The EQ of the i-th friend
	    int AQ=(u(rng)^lastans)%400+1;  // The AQ of the i-th friend
	    lastans=solve(IQ,EQ,AQ);  // The answer to the i-th friend
	    ans = (ans + lastans * qmi(seed, m-i) % mod) % mod;
	}
	cout << ans;
	
	return 0;
}

还有一种比较巧妙的思路:
类似于单调栈,如果对于当前三元组 {x, y, z},发现集合中存在一个三元组{a, b, c}满足,其三个元素都比当前三元组的三个元素小,那么当每次询问的时候,只需要判断最小值是否满足,那么当前三元组 {x, y, z} 就没有存在的意义,完全可以删掉。

所以可以先按照三元素大小将集合中三元组从小到大排序,维护一个vector表示最后剩下的三元组,遍历所有三元组,如果对于当前三元组发现vector中存在一个三元组比当前优,那么当前的就不要了,否则就加到vector里。

这样删过之后,对于三个位置,要么有比它大的,要么有比它小的,最多只有 8 个三元组了,然后每次询问遍历每个集合的 8 个三元组即可。

这样的话完全不用考虑每个元素的大小。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 998244353;
int T, n, m;
struct node{
	int x, y, z;
};
vector<node> a[12], v[12];

bool cmp(node a, node b){
	if(a.x != b.x) return a.x < b.x;
	else if(a.y != b.y) return a.y < b.y;
	return a.z < b.z;
}

int solve(int x, int y, int z)
{
	int cnt = 0;
	for(int i=1;i<=n;i++)
	{
		for(node t : v[i])
		{
			if(t.x <= x && t.y <= y && t.z <= z){
				cnt ++;
				break;
			}
		}
	}
	return cnt;
}

int qmi(int x, int y)
{
	int ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

signed main(){
	Ios;
	cin >> n >> m;
	
	for(int i=1;i<=n;i++)
	{
		int k; cin >> k;
		while(k--)
		{
			int x, y, z; cin >> x >> y >> z;
			a[i].push_back({x, y, z});
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		sort(a[i].begin(), a[i].end(), cmp);
		for(node t : a[i])
		{
			int flag = 0;
			for(node left : v[i])
			{
				if(left.x <= t.x && left.y <= t.y && left.z <= t.z) flag = 1;
			}
			if(!flag) v[i].push_back(t);
		}
	}
	
	int seed;
	cin >> seed;

	std::mt19937 rng(seed);
	std::uniform_int_distribution<> u(1,400);
	
	int lastans=0, ans = 0;
	for (int i=1;i<=m;i++)
	{
	    int IQ=(u(rng)^lastans)%400+1;  // The IQ of the i-th friend
	    int EQ=(u(rng)^lastans)%400+1;  // The EQ of the i-th friend
	    int AQ=(u(rng)^lastans)%400+1;  // The AQ of the i-th friend
	    lastans=solve(IQ,EQ,AQ);  // The answer to the i-th friend
	    ans = (ans + lastans * qmi(seed, m-i) % mod) % mod;
	}
	cout << ans;
	
	return 0;
}

感谢友队提供思路,很强!


Particle Arts

题意
一共 n 个粒子,每个粒子有权值 a_i。
如果两个值为 a 和 b 的粒子相碰的话,这两个粒子将会消失,同时产生两个新粒子,权值分别为 a&ba|b
这 n 个粒子两两随意碰撞,很长时间之后,所有点权值的方差收敛到一个稳定值。
求最终的方差。

2 ≤ n ≤ 1 0 5 ,   0 ≤ a i < 2 15 2\le n\le 10^5,\ 0≤a_i<2^{15} 2n105, 0ai<215

思路
观察两个粒子碰撞后,两个数的二进制变化:

1 0 1 0 0 1  
0 0 1 0 1 0
———————————— 两粒子相撞后
0 0 1 0 0 0
1 0 1 0 1 1

会发现,如果二进制中有一位两数中只有一个 1,那么肯定会聚集到第二个数中。
那么如果两个聚集过的两数相碰,将不会有任何变化。

重新构造这 n 个数,使得任意两个数碰撞不会发生变化,那么所有权值的方差就是稳定的。也就是,将每一位上,所有的 1 都拿出来,重新分配,尽量分给前面的数。
这样,任意拿出两个数,后面数的某一位有1,前面数也一定有。前面数有后面数不一定有,两数碰撞后不会有任何变化。

求方差,可以直接套方差公式,但是注意范围会爆 longlong,需要用到 __int128,其输出需要自写函数:

void print(__int128 x)
{
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) print(x/10);
	putchar(x%10 + '0');
}

也可以用这个公式:
设一个数组的方差为 D ( x ) , 平均数为 E ( x ) 。 设一个数组的方差为 D(x) , 平均数为 E(x) 。 设一个数组的方差为D(x),平均数为E(x)

 则  D ( x ) = E ( x 2 ) − E 2 ( x ) \text { 则 } D(x)=E\left(x^{2}\right)-E^{2}(x)   D(x)=E(x2)E2(x)

long long 范围内运算即可。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], cnt[30];

void print(__int128 x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) print(x/10);
	putchar(x%10 + '0');
}

__int128 gcd(__int128 a, __int128 b)
{
	if(!b) return a;
	return gcd(b, a%b);
}

signed main(){
	Ios;
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x;
		for(int j=0;j<16;j++)
		{
			if(x & 1) cnt[j]++;
			x >>= 1;
		}
	}
	
	int sum = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<16;j++)
		{
			if(cnt[j]) a[i] |= 1<<j, cnt[j]--;
		}
		sum += a[i];
	}
	
	int x = sum, y = n, g = __gcd(sum, n);
	x /= g, y /= g;
	
	__int128 up = 0, down;
	for(int i=1;i<=n;i++)
	{
		up += (__int128)(a[i]*y - x) * (a[i]*y - x);
	}
	
	if(up == 0){
		cout << "0/1";
		return 0;
	}
	
	down = n*y*y;
	
	__int128 gg = gcd(up, down);
	print(up/gg);
	printf("/");
	print(down/gg);
	
	return 0;
}
#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], cnt[30];

signed main(){
	Ios;
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x;
		for(int j=0;j<16;j++)
		{
			if(x & 1) cnt[j]++;
			x >>= 1;
		}
	}
	
	int sum1 = 0, sum2 = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<16;j++)
		{
			if(cnt[j]) a[i] |= 1<<j, cnt[j]--;
		}
		sum1 += a[i]*a[i];
		sum2 += a[i];
	}
	
	int up = n*sum1 - sum2*sum2, down = n*n;
	if(up == 0){
		cout << "0/1";
		return 0;
	}
	int g = __gcd(up, down);
	cout << up/g << "/" << down/g;
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值