2022牛客寒假算法基础集训营2——F、H、I


F、小沙的算数(二进制,快速幂)

题意:
给出 n 和 m。
一共有多少种长度为 n, 元素之和为 m 的数组,其 异或和最大?

思路:
为了使得异或和最大,异或和二进制中的1都要尽量靠左。
而元素之和为m,那么数 m 的二进制中的 1 的位置肯定是这n个数异或之后的最佳状态。
如何能够异或成 m,并且元素之和为 m 呢?
对于 m 的二进制中的若干个1,可以分给这 n 个元素中的任一个。
设 m 中1的个数为cnt,所以方案数就为 n c n t n^{cnt} ncnt
由于 n 很大,可以用快速幂。

Code:

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

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

signed main(){
	Ios;
	
	cin>>n>>m;
	
	int ans=1, cnt=0;
	while(m)
	{
		if(m&1) cnt++;
		m >>= 1;
	}
	
	cout<<qmi(n, cnt, mod);
	
	return 0;
}

H、小沙的算数(并查集,逆元)

题意
对于一个表达式,操作符n-1个种类只有 + 和 *,操作数有n个。
每次有两种询问,给出x和y,表示将第x个操作数换作y之后,表达式的值为多少?答案对1e9+7取模。
( 2 ≤ n ≤ 1 0 6 , 1 ≤ q ≤ 1 0 5 , 所 有 操 作 数 不 超 过 1 e 9 + 7 ) (2\leq n \leq 10^6 ,1\leq q \leq 10^5 ,所有操作数不超过1e9+7 ) (2n106,1q1051e9+7)

思路
当时做这道题的时候思路很明确,因为询问操作很多,所以肯定将答案处理出来之后,根据变化做加减。
将乘法的操作数都合并到一起,可以用并查集。这些数的和对mod取模就是每加一个值就取一次模。先求出答案。
然后询问的时候,如果改变的是加法操作数,直接对答案作加减;如果是乘法操作数,就将其属于的那一块乘法表达式的值从答案中删掉,然后将那块表达式的值除去修改的数乘上新的数,更新之后再加到答案中。
思路很明确,但是被取模困住了。。
因为数字很大,所有每做一次操作都要取一次模。然后一块乘法表达式的值就变得很小,这时候如果想要除去之前其因数的话,除去的因数就可能比这个整个取模之后的表达式的值大。
这个时候就需要一个技巧,逆元(就相当于是倒数)。
通过求这个因数的逆元,将除法转化为乘法。

对于一个数 x,其关于 m 的逆元: 1 / x 1/x 1/x% m = x ( m − 2 ) m = x^{(m-2)} m=x(m2)% m m m

Code:

const int N = 2000010, mod = 1e9+7;
int T, n, m, k; 
int a[N], val[N], pre[N];
char c[N];
bool f[N];

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++) cin>>c[i];
	
	for(int i=1;i<=n;i++) cin>>a[i], pre[i]=i, val[i]=a[i];
	
	for(int i=1;i<=n;i++)
	{
		if(c[i]=='*')
		{
			f[i+1] = f[i] = 1;
			pre[i+1] = pre[i];
			val[pre[i]] = val[pre[i]] * a[i+1] % mod;
		}
	}
	
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(pre[i] == i) ans = (ans+val[i])%mod;
	}
	
	while(m--)
	{
		int x,y;cin>>x>>y;
		if(!f[x]){
			ans = (ans + y - a[x] + mod)%mod;
			a[x] = y;
		}
		else
		{
			int fx = pre[x];
			ans = (ans - val[fx] + mod)%mod;
			val[fx] = val[fx] * qmi(a[x], mod-2) % mod; //这里用逆元之后别忘了取模。
			a[x] = y;
			val[fx] = val[fx] * y % mod;
			ans = (ans + val[fx]) % mod;
		}
		cout << ans << endl;
	}
	
	return 0;
}

I、小沙的构造(贪心,模拟)

题意
构造一个长度为 n 的不同字符数量为 m 的一个对称字符串,其中字符为除小写字母以外的可见字符(不包括空格)。这里的对称是绝对对称,比如 b 和 d 对称。
具有对称性质的可见字符: "!’*±.08:=^_WTYUIOAHXVM|<>/[]{}()

思路
如果只用单个字符拼对称串的话,要满足:字符个数 ≥ 2*种类数-1.
为了尽量使得这个条件满足,就要多使用成对的字符。这样字符的个数每次-2,种类数每次-2,更容易满足条件。
然后判断剩下的个数能否满足,如果不满足输出-1。满足就构造直接。

Code

const int N = 200010, mod = 1e9+7;
int T, n, m, k; 
int a[N];
string doub="<\\[{(", alon = "!'*+-.08:=^_WTYUIOAHXVM|";
string ans;

signed main(){
	Ios;
	
	alon += char(34);
	
	cin>>n>>m;
	
	int cnt=n;
	for(int i=1;i<=5;i++)
	{
		if(m>2) m-=2, cnt-=2, ans += doub[i-1];
		else break;
	}
	
	if(cnt<2*m-1 || m>25){
		cout<<-1;return 0;
	}
	
	int len = cnt;
	
	for(int i=1;i<=len/2;i++)
	{
		if(i<=m) ans+=alon[i-1];
		else ans+=alon[m-1];
	}
	
	cout << ans;
	
	if(len%2){
		if(len/2+1 <= m) cout<<alon[len/2];
		else cout<<alon[m-1];
	}
	
	for(int i=ans.size()-1;i>=0;i--)
	{
		if(ans[i] == '(') cout<<')';
		else if(ans[i] == '{') cout<<'}';
		else if(ans[i] == '[') cout<<']';
		else if(ans[i] == '\\') cout<<'/';
		else if(ans[i] == '<') cout<<'>';
		else cout<<ans[i];
	}
	
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值