2021“MINIEYE杯”中国大学生算法设计超级联赛(2)
1011
(1)没什么用的小知识
x & -x
在&操作中
x
x
x和
−
x
-x
−x是通过补码来表示的,故x & -x = x & (~x + 1)
当 x x x为偶数的时候,偶数取反后最后一位是1,在+1后就会进位,直到有一位由0变1,相与后只有那一位为1,所以结果是2的幂。
当 x x x为奇数的时候,最后一位是0,在+1后就不会会进位,相与后为1。
(2)思路
因为
i
&
j
≥
k
i\&j≥k
i&j≥k 所以
m
i
n
(
i
,
j
)
≥
k
min(i,j)≥k
min(i,j)≥k ,故
i
≥
k
i≥k
i≥k并且
j
≥
k
j≥k
j≥k。
这里可以用动态规划的思想,
A
[
i
]
=
A[i]=
A[i]= 二进制i为1的位一定为1 其他位可能唯1也可能为0的 A数组中数中的最大值。
例如
i
=
100
i=100
i=100则它可以由101、110、111通过&操作得到。
A
[
101
]
=
m
a
x
(
A
[
101
]
,
A
[
111
]
)
,
A
[
100
]
=
m
a
x
(
A
[
100
]
,
A
[
101
]
,
A
[
110
]
)
A[101]=max(A[101],A[111]),A[100]=max(A[100],A[101],A[110])
A[101]=max(A[101],A[111]),A[100]=max(A[100],A[101],A[110])
举个栗子:
i
=
101001
i=101001
i=101001则它为
m
a
x
(
A
[
101001
]
,
A
[
111001
]
,
A
[
101101
]
,
A
[
101011
]
)
max(A[101001],A[111001],A[101101],A[101011])
max(A[101001],A[111001],A[101101],A[101011])
依次类推
B
[
i
]
=
B[i]=
B[i]= 二进制i为1的位为1其他位可能唯一的B数组中数中的最大值,
a
[
i
]
=
a[i]=
a[i]= 二进制i为1的位为1其他位可能唯一的A数组中数中的最小值
最后维护ans[i]输出答案
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6+10;
const ll mod = 998244353;
const int inf = 1e9+7;
int A[N],B[N],a[N],b[N];
ll ans[N];
int main(){
ios::sync_with_stdio(false); cin.tie(0);
int T,n;
cin >> T;
while(T--){
cin >> n;
for(int i = 0;i<n;i++){
cin >> A[i]; a[i] = A[i];
}
for(int i = 0;i<n;i++){
cin >> B[i]; b[i] = B[i];
}
int m = 1;
while(m<n) m <<= 1;
for(int i = n;i<=m;i++){
A[i] = -inf;
B[i] = -inf;
a[i] = inf;
b[i] = inf;
}
for(int i = n-1;i>=0;i--){
for(int j = 1;j<m;j <<= 1){
if(!(i&j)){
A[i] = max(A[i],A[i^j]);
B[i] = max(B[i],B[i^j]);
a[i] = min(a[i],a[i^j]);
b[i] = min(b[i],b[i^j]);
}
}
}
ans[n] = -(ll)inf*inf;//可能超过1e18
for(int i = n-1;i>=0;i--){
ans[i] = -(ll)inf*inf;
ans[i] = max(ans[i],(ll)A[i]*B[i]);
ans[i] = max(ans[i],(ll)A[i]*b[i]);
ans[i] = max(ans[i],(ll)a[i]*B[i]);
ans[i] = max(ans[i],(ll)a[i]*b[i]);
ans[i] = max(ans[i],ans[i+1]);
}
ll res = 0;
for(int i = 0;i<n;i++){
res = (res+ans[i])%mod;
}
res = (res+mod)%mod; /*!!!!ans[i]可能是负的*/
cout << res << endl;
}
return 0;
}
1010
有用的小知识
引理1:对于任意整数
a
,
b
,
c
a,b,c
a,b,c,若
m
m
m与
c
c
c互质,则若
a
.
c
%
m
=
b
.
c
a.c\%m=b.c
a.c%m=b.c,则有
a
%
m
=
b
a\%m=b
a%m=b
证明:
a
.
c
%
m
=
b
.
c
a.c\%m=b.c
a.c%m=b.c
=
>
(
a
.
c
−
b
.
c
)
%
m
=
0
=>(a.c-b.c)\%m=0
=>(a.c−b.c)%m=0
=
>
(
a
−
b
)
.
c
%
m
=
0
=>(a-b).c\%m=0
=>(a−b).c%m=0
以
为
m
和
c
互
质
以为m和c互质
以为m和c互质
=
>
(
a
−
b
)
%
m
=
0
=>(a-b)\%m=0
=>(a−b)%m=0
=
>
a
%
m
=
b
=>a\%m=b
=>a%m=b
引理2: m m m是一个整数, b b b是一个整数且与 m m m互质,则a[1],a[2],a[3],a[4],…a[m]是模m的一个{1,2,…m-1}的序列,{b.a[i]%m}也是{1,2,…m-1}的序列
费马小定理:
a
p
−
1
%
p
=
1
a^{p-1}\%p=1
ap−1%p=1(
p
p
p是质数,整数
a
a
a不是
p
p
p的倍数)
(证明可百度)
由引理2可知
b
x
{b_x}
bx也是一个
{
1
,
2...
P
−
1
}
\{1,2...P-1\}
{1,2...P−1}的序列。
要想知道
b
x
{b_x}
bx的逆序对数,就是要知道
∏
1
<
j
<
i
<
P
(
b
i
−
b
j
)
\prod_{1<j<i<P}(b_i-b_j)
∏1<j<i<P(bi−bj)的符号,就是要知道
∏
1
<
j
<
i
<
P
(
a
i
%
p
−
a
j
%
p
)
\prod_{1<j<i<P}(ai\%p-aj\%p)
∏1<j<i<P(ai%p−aj%p)的符号,若对每个数除以
i
−
j
i-j
i−j就会得到纯符号位为
∏
1
<
j
<
i
<
P
(
a
%
p
)
\prod_{1<j<i<P}(a\%p)
∏1<j<i<P(a%p)即为
a
P
(
P
−
1
)
2
%
P
a^{\frac{P(P-1)}{2}}\%P
a2P(P−1)%P,该数为1就是偶数个逆序对,-1则为奇数个逆序对
a
P
(
P
−
1
)
2
%
P
a^{\frac{P(P-1)}{2}}\%P
a2P(P−1)%P
<
=
>
(
a
P
−
1
)
P
−
1
2
%
P
.
(
a
P
−
1
2
)
%
P
<=>(a^{P-1})^\frac{P-1}{2}\%P.(a^\frac{P-1}{2})\%P
<=>(aP−1)2P−1%P.(a2P−1)%P
由费马定理得
<
=
>
(
a
P
−
1
2
)
%
P
<=>(a^\frac{P-1}{2})\%P
<=>(a2P−1)%P
所以只要
<
=
>
(
a
P
−
1
2
)
%
P
<=>(a^\frac{P-1}{2})\%P
<=>(a2P−1)%P就能得出结论
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
int T;
ll a,P;
__int128 qp(ll a, ll b) {
__int128 ret = 1;
while (b) {
if (b & 1)
ret = ret * (__int128)a % (__int128)P;
a = (ll)((__int128)a * (__int128)a% (__int128)P);
b >>= 1;
}
return ret;
}
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--){
cin >> a >> P;
ll ans = qp(a,(P-1)/2)%P;
if(ans == 1) cout << 0 << endl;
else cout << 1 << endl;
}
return 0;
}
1008
01背包+分组背包
背包初始化为负无穷表示要恰好装满,初始化为0就只要小于j就行了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
map<string,int> mp;
struct node{
int v,w;
};
vector<node> subject[55];
int score[55][600],dp[600][4];
int main(){
int T;
cin >> T;
while(T--){
int n,m;string s;
cin >> n;
for(int i = 1;i<=n;i++){
cin >> s;
mp[s] = i;
}
memset(score,0,sizeof(score));
memset(dp,-0x3f3f3f3f,sizeof(dp));
cin >> m;
for(int i = 1;i<=m;i++){
int w,v;
cin >> s >> v >> w;
subject[mp[s]].push_back({v,w});
}
int t,p;
cin >> t >> p;
for(int i = 1;i<=n;i++){
for(auto item : subject[i]){
for(int j = t;j>=item.w;j--){
score[i][j] = min(100,max(score[i][j],score[i][j-item.w]+item.v));
}
}
}
dp[0][0] = 0;
for(int i = 1;i<=n;i++){
for(int j = t;j>=0;j--){
for(int pp = p;pp>=0;pp--){
dp[j][pp] = -0x3f3f3f3f;
//每一次都初始化为负无穷,因为如果不能在上一层的前继状态找到可行解那这种状态就无法继续下去
for(int k = 0;k<=j;k++){
if(score[i][k] >= 60) dp[j][pp] = max(dp[j][pp],dp[j-k][pp]+score[i][k]);
else if(pp) {
dp[j][pp] = max(dp[j][pp],dp[j-k][pp-1]+score[i][k]);
}
if(score[i][k] == 100) break;
}
}
}
}
int ans = -1;
for(int i = 0;i<=t;i++){
for(int j = 0;j<=p;j++){
ans = max(ans,dp[i][j]);
}
}
cout << ans << endl;
mp.clear();
for(int i = 1;i<=n;i++) subject[i].clear();
}
return 0;
}