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