div3 A-F
A. Two Vessels
签到
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
void solve()
{
int a,b,c;
cin>>a>>b>>c;
int x=abs(a-b);
int ans=(x+c*2-1)/(c*2);
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
while(t--){
solve();
}
system("pause");
return 0;
}
B. The Corridor or There and Back Again
思路:考虑对于每个炸弹,到达该位置后,在给定时间内能走过的最远距离。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
void solve()
{
int n;
cin>>n;
vector<pll> a(n);
for(int i=0;i<n;i++){
int d,s;
cin>>d>>s;
a[i]={d,s};
}
sort(a.begin(),a.end());
ll sum=2e9;
for(int i=0;i<n;i++){
auto [x,y]=a[i];
sum=min(sum,x+(y-1)/2);
}
cout<<sum<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
while(t--){
solve();
}
system("pause");
return 0;
}
C. Non-coprime Split
思路:对于一个大于2的偶数 x x x,我们一定能将其分解为 x + ( x − 2 ) x+(x-2) x+(x−2)的形式,所以大于二的偶数一定合法,那么考虑奇数的情况,对于奇数 y y y,如果其有一个不为本身且不为1的因子,那么我们一定能将其分解为 y + ( y − 2 ) y+(y-2) y+(y−2)的形式。
综上所述:
对于一个区间 [ l , r ] [l,r] [l,r],如果 l − r > = 1 l-r>=1 l−r>=1,并且合法的偶数不为2的情况下,那么我们一定可以选择这个区间中的某个偶数。
如果 l = = r l==r l==r,那么我们只需要对其进行质因数分解判断是否存在合法的质因子。
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
void solve()
{
int l,r;
cin>>l>>r;
if(l!=r){
if(r%2!=0){
r--;
}
if(r-2!=0) {
cout<<2<<" "<<r-2<<endl;
return ;
}
else {
cout<<-1<<endl;
return ;
}
}
else{
for(int i=2;i*i<=l;i++){
if(l%i==0&&l/i>1){
cout<<i<<" "<<l-i<<endl;
return ;
}
}
}
cout<<-1<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
//ol();
while(t--){
solve();
}
system("pause");
return 0;
}
D. Plus Minus Permutation
思路:因为n的数据范围很大,所以考虑这题是否跟结论挂钩。考虑所有 x x x位置和 y y y位置对答案的贡献,因为总贡献为 x x x的贡献减去 y y y的贡献,我们贪心的想,尽可能把大的数放在的位置,把小的数放在的位置,但对于 x x x和 y y y可能存在位置重合的情况,那么对于这种情况,该位置对答案的贡献为0。现在考虑如何求解 x x x和 y y y的贡献。
我们其实可以O(1)的算出 x x x位置的个数和 y y y位置的个数,同时我们也可以算出来重合位置的个数,因为所有重合位置的坐标即为x和y的最小公倍数,最后进行贡献计算即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
void solve()
{
ll n,x,y;
cin>>n>>x>>y;
ll l=lcm(x,y);
int cnt=n/l;
ll c1=n/x;
ll c2=n/y;
c1-=cnt;
c2-=cnt;
ll sum=0;
sum+=(n-c1+1+n)*c1/2;
sum-=(1+c2)*c2/2;
cout<<sum<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
ol();
while(t--){
solve();
}
system("pause");
return 0;
}
E. Data Structures Fan
题意:
给你一个整数数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an,以及一个由 n n n个字符组成的二进制字符串由 n n n个字符组成。
给定 q q q个询问。询问有两种类型:
-
“1 l l l r r r”( 1 ≤ l ≤ r ≤ n 1\le l \le r \le n 1≤l≤r≤n)–用相反的字符替换 l ≤ i ≤ r l \le i \le r l≤i≤r的每个字符 s i s_i si。即用 1 \texttt{1} 1替换所有 0 \texttt{0} 0,用 0 \texttt{0} 0替换所有 1 \texttt{1} 1。
-
“2 g g g” ( g ∈ { 0 , 1 } g \in \{0, 1\} g∈{0,1}) - 计算所有索引 i i i 的数字 a i a_i ai 的 bitwise XOR 的值,使得 s i = g s_i = g si=g。注意,空数集的 XOR \operatorname{XOR} XOR被认为等于 0 0 0
给你一个整数数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an,以及一个由 n n n个字符组成的二进制字符串 3 3 3由 n n n个字符组成。
例如,如果 n = 4 n = 4 n=4, a = [ 1 , 2 , 3 , 6 ] a = [1, 2, 3, 6] a=[1,2,3,6], s = 1001 s = \texttt{1001} s=1001,请考虑下面一系列的疑问:
- “2 0 0 0”–我们感兴趣的是 s i = 0 s_i = \tt{0} si=0的索引 i i i,因为 s = 1001 s = \tt{1001} s=1001,这些索引是索引 2 2 2和 3 3 3,所以查询的答案将是 a 2 ⊕ a 3 = 2 ⊕ 3 = 1 a_2 \oplus a_3 = 2 \oplus 3 = 1 a2⊕a3=2⊕3=1。
- “1 1 1 1 3 3 3”–我们需要将字符 s 1 , s 2 , s 3 s_1, s_2, s_3 s1,s2,s3替换为它们的对立面,因此查询前为 s = 1001 s = \tt{1001} s=1001,查询后为 s = 0111 s = \tt{0111} s=0111: s = 0111 s = \tt{0111} s=0111.
- “2 1 1 1”–我们感兴趣的是 s i = 1 s_i = \tt{1} si=1的索引 i i i,因为 s = 0111 s = \tt{0111} s=0111,这些索引就是 2 2 2、 3 3 3和 4 4 4,所以查询的答案将是 a 2 ⊕ a 3 ⊕ a 4 = 2 ⊕ 3 ⊕ 6 = 7 a_2 \oplus a_3 \oplus a_4 = 2 \oplus 3 \oplus 6 = 7 a2⊕a3⊕a4=2⊕3⊕6=7。
- “1 2 2 2 4 4 4” - s = 0111 s = \tt{0111} s=0111 → \to → s = 0000 s = \tt{0000} s=0000.
- “2 1 1 1” - s = 0000 s = \tt{0000} s=0000,没有带 s i = 1 s_i = \tt{1} si=1的索引,所以因为空数集的 XOR \operatorname{XOR} XOR被认为等于 0 0 0,所以这个查询的答案是 0 0 0。
† ^{\dagger} †二进制字符串是指只包含字符 0 \texttt{0} 0或 1 \texttt{1} 1的字符串。
思路:
法一:运用异或相关的性质。
比如给定数组
a
1
,
a
2
,
a
3
,
a
4
,
a
5
a_1,a_2,a_3,a_4,a_5
a1,a2,a3,a4,a5,给定字符串为
01110
01110
01110,我们可以先计算出
s
i
s_i
si等于1的值,为
a
2
⊕
a
3
⊕
a
4
a_2 \oplus a_3 \oplus a_4
a2⊕a3⊕a4,同样,我们可以计算出
s
i
s_i
si等于0的值,为
a
1
⊕
a
5
a_1 \oplus a_5
a1⊕a5。
考虑修改操作,我们可以先预处理出来数组的异或前缀和,还是以上文为例,为:
a
1
⊕
a
2
⊕
a
3
⊕
a
4
⊕
a
5
a_1\oplus a_2 \oplus a_3 \oplus a_4 \oplus a_5
a1⊕a2⊕a3⊕a4⊕a5,我们如果对区间
[
4
,
5
]
[4,5]
[4,5]进行修改,修改后字符串变为
01101
01101
01101,值为1的结果为
a
2
⊕
a
3
⊕
a
5
a_2 \oplus a_3 \oplus a_5
a2⊕a3⊕a5,我们会发现,我们原来1的结果为
a
2
⊕
a
3
⊕
a
4
a_2 \oplus a_3 \oplus a_4
a2⊕a3⊕a4,我们和区间
[
4
,
5
]
[4,5]
[4,5]的异或值进行异或,为
a
2
⊕
a
3
⊕
a
4
⊕
a
4
⊕
a
5
a_2 \oplus a_3 \oplus a_4\oplus a_4\oplus a_5
a2⊕a3⊕a4⊕a4⊕a5,即为
a
2
⊕
a
3
⊕
a
5
a_2 \oplus a_3 \oplus a_5
a2⊕a3⊕a5;对于0的情况也同理。
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
void solve()
{
int n;
cin>>n;
vector<ll> a(n+5);
vector<ll> s(n+5);
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]^a[i];
}
string st;
cin>>st;
st=" "+st;
ll tot=s[n];
ll tot1=0;
ll tot2=0;
for(int i=1;i<=n;i++){
if(st[i]=='1') tot1^=a[i];
else tot2^=a[i];
}
int q;
cin>>q;
while(q--){
int op;
cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
ll val=s[l-1]^s[r];
tot1^=val;
tot2^=val;
}
else{
int g;
cin>>g;
if(g==1){
cout<<tot1<<" ";
}
else{
cout<<tot2<<" ";
}
}
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
//ol();
while(t--){
solve();
}
system("pause");
return 0;
}
法二:线段树板子
思路:我们使用线段树,对于线段树的每个节点,我们储存两个值,分别为节点所在区间的在二进制字符串中为0的异或值的和与1的异或值的和。然后进行正常的区间修改和查询即可。具体思路看代码注释
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> p3;
int mod=998244353;
const int maxv=4e6+5;
struct node
{
int l,r,b[2],tag;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define tag(x) tr[x].tag
#define b0(x) tr[x].b[0]
#define b1(x) tr[x].b[1]
} tr[N*4];
string s;
int a[N];
void update(int p)
{
b0(p)=b0(p*2)^b0(p*2+1);
b1(p)=b1(p*2)^b1(p*2+1);
}
void bulid(int p,int l,int r)
{
if(l==r){
l(p)=l,r(p)=r;
if(s[l]=='1'){
tr[p].b[1]=a[l];
tr[p].b[0]=0;
}
else {
tr[p].b[0]=a[l];
tr[p].b[1]=0;
}
tag(p)=0;
return ;
}
l(p)=l,r(p)=r,b0(p)=b1(p)=tag(p)=0;
int mid=(l+r)/2;
bulid(p*2,l,mid);
bulid(p*2+1,mid+1,r);
update(p);
}
void pushdown(int p)//pushdown和modify中同理,把子节点进行交换
{
if(tag(p)){
swap(b0(p*2),b1(p*2));
swap(b0(p*2+1),b1(p*2+1));
tag(p*2)^=1;
tag(p*2+1)^=1;
}
tag(p)=0;
}
void modify(int p,int l,int r)
{
if(l<=l(p)&&r(p)<=r){
swap(b0(p),b1(p));//修改把区间中储存的0和1进行交换即可
tag(p)^=1;
return ;
}
pushdown(p);
int mid=(l(p)+r(p))/2;
if(l<=mid) modify(p*2,l,r);
if(r>mid) modify(p*2+1,l,r);
update(p);
}
int query(int p,int l,int r,int f)//query函数其实不用写,因为这题的查询相当于查询区间[1,n]
{
if(l<=l(p)&&r(p)<=r){
if(f==1) return b1(p);
else return b0(p);
}
pushdown(p);
int mid=(l(p)+r(p))/2;
int res=0;
if(l<=mid) res^=query(p*2,l,r,f);
if(r>mid) res^=query(p*2+1,l,r,f);
return res;
}
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>s;
s=" "+s;
bulid(1,1,n);
int q;
cin>>q;
while(q--){
int op;
cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
modify(1,l,r);
}
else{
int g;
cin>>g;
cout<<query(1,1,n,g)<<" ";
}
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
//ol();
while(t--){
solve();
}
system("pause");
return 0;
}
F. Selling a Menagerie
题意:您是一个动物园的主人,动物园里有 n n n只动物,编号从 1 1 1到 n n n。然而,维持动物园的费用相当昂贵,因此您决定出售动物园!
众所周知,每只动物都害怕另一只动物。更确切地说,动物 i i i害怕动物 a i a_i ai( a i ≠ i a_i \neq i ai=i)。另外,每只动物的成本是已知的,对于动物 i i i来说,成本等于 c i c_i ci。
您将按固定顺序出售所有动物。从形式上看,您需要选择某种排列【1011】,先卖出动物 p 1 p_1 p1,然后卖出动物 p 2 p_2 p2,以此类推,最后卖出动物 p n p_n pn。
当你卖出动物 i i i时,有两种可能的结果:
- 如果动物 a i a_i ai是在动物 i i i之前***卖出的,那么你卖出动物 i i i会得到 c i c_i ci元钱。
- 如果动物 a i a_i ai在动物 i i i之前***没有卖出,那么卖出动物 i i i可以得到 2 ⋅ c i 2 \cdot c_i 2⋅ci金钱。 令人惊讶的是,目前害怕的动物更值钱)。
你的任务是选择出售动物的顺序,使总利润最大化。
例如,如果 a = [ 3 , 4 , 4 , 1 , 3 ] a = [3, 4, 4, 1, 3] a=[3,4,4,1,3], c = [ 3 , 4 , 5 , 6 , 7 ] c = [3, 4, 5, 6, 7] c=[3,4,5,6,7],而你选择的排列顺序是 [ 4 , 2 , 5 , 1 , 3 ] [4, 2, 5, 1, 3] [4,2,5,1,3],那么:
- 第一只出售的动物是动物 4 4 4。动物 a 4 = 1 a_4 = 1 a4=1以前没有卖过,所以卖掉它你可以得到 2 ⋅ c 4 = 12 2 \cdot c_4 = 12 2⋅c4=12元钱。
- 第二只要出售的动物是动物 2 2 2。动物 a 2 = 4 a_2 = 4 a2=4之前已经卖出,所以卖出它你会得到 c 2 = 4 c_2 = 4 c2=4金钱。
- 第三只要出售的动物是动物 5 5 5。动物 a 5 = 3 a_5 = 3 a5=3之前没有卖出过,所以卖出它你会得到 2 ⋅ c 5 = 14 2 \cdot c_5 = 14 2⋅c5=14金钱。
- 第四只要出售的动物是动物 1 1 1。动物 a 1 = 3 a_1 = 3 a1=3之前没有卖出过,所以卖出它你会得到 2 ⋅ c 1 = 6 2 \cdot c_1 = 6 2⋅c1=6金钱。
- 第五只要出售的动物是动物 3 3 3。动物 a 3 = 4 a_3 = 4 a3=4之前被卖掉了,所以卖掉它你可以得到 c 3 = 5 c_3 = 5 c3=5金钱。
在这种排列方式下,你的总利润为 12 + 4 + 14 + 6 + 5 = 41 12 + 4 + 14 + 6 + 5 = 41 12+4+14+6+5=41。请注意,在这个例子中, 41 41 41并不是***可能的最大利润。
† ^\dagger †长度为 n n n的排列是一个数组,由 n n n个不同的整数组成,这些整数从 1 1 1到 n n n依次排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是排列( 2 2 2在数组中出现两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4]也不是排列( n = 3 n=3 n=3,但 4 4 4在数组中出现)。
思路:拓扑排序。
动物
i
i
i害怕动物
a
i
a_i
ai,并且对于第
i
i
i个动物,动物
a
i
a_i
ai没有在它前面卖出,那么我们的收获会越大,所以我们贪心的想,让对于每一个
i
i
i,我们贪心的认为,让所有令
i
i
i害怕的动物全部在
i
i
i后进行售卖。对
i
i
i和
a
i
a_i
ai进行连边,那么就转为了求在该图下的拓扑序列,但又因为存在环,所以我们需要断环为链,我们同样贪心的想,我们想让断环为链的损失最小,所以我们找到价值最小的那个动物x,然后让令x害怕的那个动物排在第一位即可,因为这样整个环只有价值最小的那个动物以价格
c
i
c_i
ci卖出。具体注释看代码。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<int,4> p4;
int mod=998244353;
const int maxv=4e6+5;
vector<int> e[N];
int d[N],st[N];//st数组用于标记已经在拓扑序中的点
int n;
vector<int> a(N),c(N);
vector<int> ans;
int val=2e9,idx=0;
void add(int u,int v)
{
e[u].push_back(v);
}
void dfs(int x)
{
st[x]=1;
for(auto u: e[x]){
if(c[u]<val){
val=c[u],idx=u;
}
if(!st[u]){
dfs(u);
}
else return;
}
}
void topsort()//拓扑排序板子
{
queue<int >q;
for(int i=1;i<=n;i++) {
if(d[i]==0) q.push(i);
}
while(!q.empty()){
auto t=q.front();
q.pop();
st[t]=1;
ans.push_back(t);
for(auto x: e[t]){
d[x]--;
if(d[x]==0) q.push(x);
}
}
}
void solve()
{
cin>>n;
memset(st,0,(n+5)*sizeof (int));
memset(d,0,(n+5)*sizeof (int));
for(int i=1;i<=n;i++) {
e[i].clear();
}
ans.clear();
for(int i=1;i<=n;i++ ){
cin>>a[i];
add(i,a[i]);
d[a[i]]++;
}
for(int i=1;i<=n;i++) cin>>c[i];
topsort();//拓扑序进行完毕,再考虑断环为
for(int i=1;i<=n;i++){//dfs找环上价值最小的点
if(!st[i]){
val=2e9,idx=0;
dfs(i);
int x=a[idx];//最先放入x的后继
do{
ans.push_back(x);
x=a[x];
}while(x!=a[idx]);
}
}
for(auto x: ans) cout<<x<<" ";
cout<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
cin>>t;
while(t--){
solve();
}
system("pause");
return 0;
}