T1
给定 a 1 − n a_{1-n} a1−n 和 m m m , 要求 i ∗ p ∗ a i ≥ m i*p *a_i\ge m i∗p∗ai≥m 且 i ∗ p ∗ ( a i − 1 ) < m i*p*(a_i-1)<m i∗p∗(ai−1)<m 求整数 p p p 的可能个数
#include <bits/stdc++.h>
using namespace std;
const double inf = 1e18;
int n, m, cnt, a;
double l, r=inf;
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
{
cin >> a;
l = max( l, 1.0*m/(a*i) );
if(a[i] != 1)
r = min( r, 1.0*m/((a-1)*i) );
}
if(r==inf) {puts("xiaogougege"); return 0;}
int cnt = 0;
for(int i=ceil(l);i<r;i++) cnt++;
cout << cnt;
}
T2
给定序列 a n a_n an , 现有两个操作 :
- 删除 x x x , x x x 没有则输出 − 1 -1 −1 跳过
- 加入 x x x
对于每个操作 , 输出 ∣ a 1 − a 2 ∣ + ∣ a 2 − a 3 ∣ + . . . ∣ a n − a 1 ∣ |a_1-a_2|+|a_2-a_3|+...|a_n-a_1| ∣a1−a2∣+∣a2−a3∣+...∣an−a1∣ 的最小值
考虑 ∣ a 1 − a 2 ∣ + ∣ a 2 − a 3 ∣ + . . . ∣ a n − a 1 ∣ |a_1-a_2|+|a_2-a_3|+...|a_n-a_1| ∣a1−a2∣+∣a2−a3∣+...∣an−a1∣ 的几何意义 , 那么答案为 2 ∗ ( m a x ( a n ) − m i n ( a n ) ) 2*(max(a_n)-min(a_n)) 2∗(max(an)−min(an))
那么只有影响到最大最小值的操作才会影响答案 , 由于删除操作我们还需要维护次大次小值 , 考虑使用堆
数据较水 , 接近暴力的算法也能过
赛时代码 :
#include <iostream>
using namespace std;
const int MAXN = 1e6+5;
int n,q,mn=1e9,mx=-1,vis[MAXN];
int main()
{
std::ios::sync_with_stdio(false);std::cin.tie(0);
cin >> n >> q;
for(int i=1;i<=n;i++)
{
int x; cin >> x; vis[x]++;
mx = max(mx, x); mn = min(mn, x);
}
while(q--)
{
int opt,x;
cin >> opt >> x;
if(opt == 1)
{
if(!vis[x]) cout<<-1<<endl;
else
{
vis[x]--;
if(x == mx)
for(int i=mx;i>=mn;i--)
if(vis[i]) {mx = i; break;}
if(x == mn)
for(int i=mn;i<=mx;i++)
if(vis[i]) {mn = i; break;}
cout << 2*(mx-mn) << endl;
}
}
if(opt==2)
{
vis[x]++;
mn = min(mn, x); mx = max(mx, x);
cout << 2*(mx-mn) << endl;
}
}
}
正解 (堆) :
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+5;
priority_queue <int> q1,q2;
int n, q, vis[MAXN];
int main()
{
cin >> n >> q;
for(int i=1;i<=n;i++)
{
int x; cin >> x; vis[x] ++ ;
q1.push(x); q2.push(-x);
}
while(q--)
{
int opt, x;
cin >> opt >> x;
if(opt == 1)
{
if(!vis[x]) cout<<-1<<endl;
else
{
vis[x]--;
while(!vis[q1.top()]) q1.pop();
while(!vis[-q2.top()]) q2.pop();
cout << 2*(q1.top()+q2.top()) << endl;
}
}
if(opt==2)
{
vis[x]++;
q1.push(x); q2.push(-x);
cout << 2*(q1.top()+q2.top()) << endl;
}
}
return 0;
}
T3
给定 a n a_n an 你可以花费 r − l + 1 + k r-l+1+k r−l+1+k 将区间 [ l , r ] [l,r] [l,r] 变为 g c d ( a l , a l + 1 . . . . , a r ) gcd(a_l,a_{l+1}....,a_r) gcd(al,al+1....,ar) , 求将序列变相同的最小花费
容易想出最终的答案数列一定都为 G = g c d ( a 1 , a 2 . . . . a n ) G=gcd(a_1,a_2....a_n) G=gcd(a1,a2....an)
我们令 f i f_i fi 表示将 a 1 − a i a_1-a_i a1−ai 变成 G G G 的最小代价 , 那么有 f i = m i n { f j + i − j + k } ( 0 ≤ j < i ) f_i=min\lbrace f_j+i-j+k \rbrace(0\le j< i) fi=min{fj+i−j+k}(0≤j<i) ( 将 [ j + 1 , i ] [j+1,i] [j+1,i] 操作变为 G G G )
可以写出一个 O ( n 3 ) O(n^3) O(n3) 的朴素dp , 可以拿到 40 p t s 40pts 40pts
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e6+5;
const int inf = 1e9;
int n, k, a[MAXN], f[MAXN];
int calc(int l,int r)
{
int res = a[l];
for(int i=l+1;i<=r;i++) res = __gcd(res, a[i]);
return res;
}
int main()
{
cin >> n >> k;
for(int i=1;i<=n;i++) cin >> a[i];
int G = calc(1,n);
for(int i=1;i<=n;i++) f[i] = inf;
for(int i=1;i<=n;i++)
{
if(a[i] == G) f[i] = f[i-1];
for(int j=0;j<i;j++)
{
if(calc(j+1,i)==G)
f[i] = min(f[i], f[j]+i-j+k);
}
}
cout << f[n] <<endl;
return 0;
}
在 n ≤ 1000 n\le 1000 n≤1000 时 , 我们可以 O ( n 2 ) O(n^2) O(n2) 预处理出 g c d ( a i . . . . a j ) gcd(a_i....a_j) gcd(ai....aj) , 可以拿到 60 p t s 60pts 60pts
for(int i=1;i<=n;i++)
{
g[i][i] = a[i];
for(int j=i+1;j<=n;j++)
{
g[i][j] = __gcd(g[i][j-1], a[j]);
}
}
正解 :
每个数至多变一次 . 我们将数列划分成若干个连续为 G G G 的区间以及若干个其他区间 .
.
.
.
.
.
.
G
G
.
.
.
.
.
.
.
.
G
G
G
G
.
.
.
.
.
.
.
.
.
.
G
G
G
.
.
.
.
.
......GG. .......GGGG..........GGG.....
......GG........GGGG..........GGG.....
[
1
]
[
2
]
[
3
]
[
4
]
[\enspace1\enspace]\quad[\enspace\space2\enspace\space]\qquad\quad[\quad3\quad]\quad\quad[\space4\space]
[1][ 2 ][3][ 4 ]
记 f i f_i fi 为前 i i i 个其他区间变为 G G G 的最小花费
对于第 i i i 个区间 , 可选择将其变为连续 G G G 区间 , 代价为 r i − l i + 1 + k + ( g c d ( a l i . . . a r i ) ! = G ) r_i-l_i+1+k+(gcd(a_{l_i}...a_{r_i})\space!=G) ri−li+1+k+(gcd(ali...ari) !=G) ( 是否需要借助前面的一个 G G G )
也可选择将第 j + 1 j+1 j+1 到 i i i 个区间合并 ( 0 ≤ j < i − 1 ) (0\le j<i-1) (0≤j<i−1) 合并 , j + 1 , i j+1,i j+1,i 间必定跨过一段连续 G G G 的区间 , 因此代价即为 r i − l j + 1 + 1 + k r_i-l_{j+1}+1+k ri−lj+1+1+k
f i = m i n { f i − 1 + r i − l i + 1 + k + ( g c d ( a l i . . . a r i ) ! = G ) f j + r i − l j + 1 + 1 + k ( 0 ≤ j < i − 1 ) f_i=min\begin{cases} \small f_{i-1}+r_i-l_i+1+k+(gcd(a_{l_i}...a_{r_i})\space!=G)\\ f_j+r_i-l_{j+1}+1+k(0\le j<i-1) \end{cases} fi=min{fi−1+ri−li+1+k+(gcd(ali...ari) !=G)fj+ri−lj+1+1+k(0≤j<i−1)
这样做仍然是 O ( n 2 ) O(n^2) O(n2) 的 , m i n ( f j + r i − l j + 1 + 1 + k ) = m i n ( f j − l j + 1 ) + r i + k + 1 min(f_j+r_i-l_{j+1}+1+k)=min(f_j-l_{j+1})+r_i+k+1 min(fj+ri−lj+1+1+k)=min(fj−lj+1)+ri+k+1 动态维护 m i n ( f j − l j + 1 ) min(f_j-l_{j+1}) min(fj−lj+1) 即可
AC 代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e6+5;
const int inf = 1e9;
int n, k, a[MAXN], f[MAXN], l[MAXN], r[MAXN], tot=1;
int calc(int l,int r)
{
int res = a[l];
for(int i=l+1;i<=r;i++) res = __gcd(res, a[i]);
return res;
}
int main()
{
std::ios::sync_with_stdio(false);std::cin.tie(0);
cin >> n >> k;
for(int i=1;i<=n;i++) cin >> a[i];
int G = calc(1,n);
for(int i=1;i<=n;i++)
{
if(a[i]!=G && l[tot]==0) l[tot] = i;
if(a[i]==G && l[tot]) r[tot++] = i-1;
}
if(l[tot]) r[tot]=n; else tot--;
int mn = inf;
for(int i=1;i<=tot;i++)
{
f[i] = f[i-1]+r[i]-l[i]+1+k+(calc(l[i],r[i])!=G);
f[i] = min(f[i], mn+r[i]+k+1);
mn = min(mn, f[i-1]-l[i]);
}
cout << f[tot] <<endl;
return 0;
}
T4
给定 n , k , p n,k,p n,k,p 要求序列 a 1 − p a_{1-p} a1−p 满足 :
- 1 ≤ a i ≤ n 1\le a_i \le n 1≤ai≤n
- a i ∧ a i + 1 a_i \land a_{i+1} ai∧ai+1 二进制 1 1 1 的个数为 k k k
- a p a_p ap 中两两互不相同
考虑到限制较多 , 经过一些预处理 , 暴力的复杂度并不会特别高 , 可以拿到 72 72 72 分
赛时代码:
#include <iostream>
#include <vector>
using namespace std;
const int mod = 998244353;
int n, k, p, vis[1005];
vector <int> G[1005];
int dfs(int i, int lst)
{
if(i==p+1) return 1;
int res = 0 ;
for(auto d : G[lst])
{
if(vis[d]) continue;
vis[d] = 1;
res += dfs(i+1,d), res%=mod;
vis[d] = 0;
}
return res;
}
int main()
{
cin >> n >> k >> p;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(__builtin_popcount(i^j)==k)
G[i].push_back(j);
if(p==1) {cout<<n<<endl;return 0;}
int res = 0;
for(int i=1;i<=n;i++)
{
vis[i] = 1;
res += dfs(2,i), res%=mod;
vis[i] = 0;
}
cout << res;
}
正解 :
令 c n t [ i ] cnt[i] cnt[i] 表示满足 p o p c o u n t ( i ∧ x ) = k popcount(i\land x)=k popcount(i∧x)=k 的 x x x 个数
p = 1 p=1 p=1
if(p==1) {cout<<n<<endl;return 0;}
p = 2 p=2 p=2
枚举第一位为 i i i , 第二位有 c n t [ i ] cnt[i] cnt[i] 种填法
if(p==2)
{
int res= 0;
for(int i=1;i<=n;i++)
res+= G[i].size();
cout << res<< endl;
}
p = 3 p=3 p=3
枚举第二位为 i i i , 第一位可以填 c n t [ i ] cnt[i] cnt[i] 种 , 由于不重复 , 最后一位可以填 c n t [ i ] − 1 cnt[i]-1 cnt[i]−1 种
if(p==3)
{
int ans = 0;
for(int i=1;i<=n;i++)
ans += G[i].size() * (G[i].size()-1);
cout << ans << endl;
}
p = 4 p=4 p=4
枚举中间两位 : 则其余两位应该有 ( c n t [ i ] − 1 ) ∗ ( c n t [ j ] − 1 ) (cnt[i]-1)*(cnt[j]-1) (cnt[i]−1)∗(cnt[j]−1) (不与 i , j i,j i,j 重复) , 但需要减去左右两位重复的情况.
可以使用 bitset
统计重复个数 :
(B[i]&B[j]).count()
表示满足
p
o
p
c
o
u
n
t
(
i
∧
x
)
=
k
popcount(i\land x)=k
popcount(i∧x)=k 且
p
o
p
c
o
u
n
t
(
j
∧
x
)
=
k
popcount(j\land x)=k
popcount(j∧x)=k 的
x
x
x 的个数
if(p==4)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(__builtin_popcount(i^j)==k)
B[i].set(j), B[j].set(i);
int ans = 0;
for(int i=1;i<=n;i++)
{
for(auto j : G[i])
{
ans += (G[i].size()-1) * (G[j].size()-1) - (B[i]&B[j]).count(), ans%=mod
}
}
cout << ans << endl;
}
p = 5 p=5 p=5
枚举
2
,
4
2,4
2,4 位为
i
,
j
i,j
i,j , 则第
3
3
3 位有(B[i]&B[j]).count()
种填法 , 则若满足
p
o
p
c
o
u
n
t
(
i
∧
x
)
=
k
popcount(i\land x)=k
popcount(i∧x)=k 的
x
x
x 中包含
j
j
j , 那么第
1
1
1 位应该为
c
n
t
[
i
]
−
2
cnt[i]-2
cnt[i]−2 种 , 否则有
c
n
t
[
i
]
−
1
cnt[i]-1
cnt[i]−1 种 , 用 bitset
表示即为
c
n
t
[
i
]
−
1
−
B
[
i
]
[
j
]
cnt[i]-1-B[i][j]
cnt[i]−1−B[i][j] , 同理第
5
5
5 位有
c
n
t
[
j
]
−
1
−
B
[
j
]
[
i
]
cnt[j]-1-B[j][i]
cnt[j]−1−B[j][i] 种 . 最终减去
1
,
5
1,5
1,5 重复的情况 , 因为 已经排除
1
,
5
1,5
1,5 与
3
3
3 重复的情况 , 因此
−
1
-1
−1 即为: (B[i]&B[j]).count()-1
if(p==5)
{
int ans = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j) continue;
int x = (B[i]&B[j]).count();
ans += x*((G[i].size()-1-B[i][j])*(G[j].size()-1-B[j][i])-(x-1)), ans%=mod;
}
}
cout << ans << endl;
}
AC 代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n, k, p, ans;
vector <int> G[1005];
bitset <1005> B[1005];
int main()
{
cin >> n >> k >> p;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(__builtin_popcount(i^j)==k)
G[i].push_back(j),B[i].set(j), B[j].set(i);
if(p==1) {cout<<n<<endl;}
if(p==2)
{
for(int i=1;i<=n;i++) ans += G[i].size();
cout << ans << endl;
}
if(p==3)
{
for(int i=1;i<=n;i++)
ans += G[i].size() * (G[i].size()-1);
cout << ans << endl;
}
if(p==4)
{
for(int i=1;i<=n;i++)
for(auto j : G[i])
ans += (G[i].size()-1) * (G[j].size()-1) - (B[i]&B[j]).count(), ans%=mod;
cout << ans << endl;
}
if(p==5)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j) continue;
int x = (B[i]&B[j]).count();
ans += x*((G[i].size()-1-B[i][j])*(G[j].size()-1-B[j][i])-(x-1)), ans%=mod;
}
}
cout << ans << endl;
}
}
总结 : 这场比赛拿到 100 + 100 + 60 + 72 100+100+60+72 100+100+60+72 还是不难的 , 但本人考场经验太少 , 经常犯蠢 , 所以多打月赛 / 模拟赛