Post

CF1872(div3)题解

CF1872

D. Plus Minus Permutation(思维)

题意:
给定三个整数n,x,y,定义一个permutation的价值是:
$(p_{1 \cdot x}+p_{2 \cdot x}+…+p_{i \cdot x})-(p_{1 \cdot y}+…+p_{j \cdot y}) $ ,其中 $i \cdot x<=n$, $j \cdot y<=n$
求permutation最大价值
思路:
分别求i,j的值,再求出lcm(x,y)对应的k值,表示二者共同走过的格子,这不会产生价值,所以i-=k,j-=k,后i个数的和减去前j个数的和即为答案
注意:
用等差数列求和公式,否则会爆TLE
复杂度:O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void solve(){
	ll n,x,y;
	cin>>n>>x>>y;
	ll cnt1=n/x,cnt2=n/y;
	ll c=x*y/__gcd(x,y);
	ll cnt3=n/c;
	cnt1-=cnt3,cnt2-=cnt3;
	ll sum=0;
	ll bg=n,eg=n-cnt1+1;
	sum+=cnt1*(eg+bg)/2;
	bg=1,eg=cnt2;
	sum-=cnt2*(bg+eg)/2;
	cout<<sum<<"\n";
}

E. Data Structures Fan(前缀异或和)

题意:
给定一个数组a和一个string01串,有q次访问,每次访问:
“1 l r”,将[l,r]的0反转,0变1,1变0 “2 g”,g=0:求所有s[i]=’0’的对应的a[i]的异或和;反之亦然
思路:
容易联想到前缀异或和,先将0和1分别的异或和求出来,每次询问l,r时,两个答案异或上区间异或和,原来为0的数会在ans0中被抵消掉,为1的加进来,从而可以维护当前值,每次操作O(1)
复杂度:O(n+q)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void solve(){
	int n;
	cin>>n;
	vector<ll> a(n+1),sum(n+1);
	ll ans0=0,ans1=0;
	for(int i=1;i<=n;i++)cin>>a[i];
	string s;
	cin>>s;
	s=" "+s;
	for(int i=1;i<=n;i++){
		if(s[i]=='0'){
			ans0^=a[i];
		}
		else ans1^=a[i];
		sum[i]=sum[i-1]^a[i];
	}
	int q;
	cin>>q;
	while(q--){
		int op;
		cin>>op;
		if(op==1){
			int l,r;
			cin>>l>>r;
			ans0^=(sum[r]^sum[l-1]);
			ans1^=(sum[r]^sum[l-1]);
		}
		else{
			int x;
			cin>>x;
			if(x==0)cout<<ans0<<" ";
			else cout<<ans1<<" ";
		}
	}
	cout<<"\n";
}

F. Selling a Menagerie(dfs+集合+dfs内置写法)

题意:
c[i]表示i的价值,当i在a[i]前面出现时,i的贡献价值为2*c[i],否则为c[i],求一个顺序使总价值最大,所有a[i],i<=n
思路:
i向a[i]连边,先做个拓扑序,剩下还有数说明有环,在环内找出最小的c[i],当前i在该环排最后即可(题目中集合可分为三种:1.单个环,2.单条边,3.一个环和若干条指向它的边(环指向边不可能存在))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void solve(){
	int n;
	cin>>n;
	vector<int> a(n+1),c(n+1),deg(n+1);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		deg[a[i]]++;
	}
	for(int i=1;i<=n;i++)cin>>c[i];
	vector<int> ans;
	vector<bool> vis(n+1);
	auto dfs=[&](auto self,int u)->void{
		if(--deg[a[u]]==0&&!vis[a[u]]){
			vis[a[u]]=1;
			ans.pb(a[u]);
			self(self,a[u]);
		}
	};
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		if(deg[i]==0){
			vis[i]=1;
			ans.pb(i);
			dfs(dfs,i);
		}
	}
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		vector<int> b,C;
		for(int j=i;!vis[j];j=a[j]){
			b.pb(j);
			C.pb(c[j]);
			vis[j]=1;
		}
		int p=min_element(all(C))-C.begin();
		for(int j=0;j<b.size();j++){
			ans.pb(b[(j+1+p)%b.size()]);
		}
	}
	for(auto it:ans)cout<<it<<" ";
	cout<<"\n";
}

G. Replace With Product(剪枝+前缀和)

题意:
对数组a做一次操作:
选择l,r,[l,r]的数相乘变成一个数,然后求和使和最大,求此时l,r
思路:
1相乘任何数不变,只有相加才能增加贡献,所以前缀1和后缀1可以先排除掉,此时范围从[1,n]缩小到[l,r]
如果直接对范围内的数用前缀求解,相乘时会爆long long,枚举l,r复杂度为 $N^2$ 承受不住
容易得知,当相乘大于某个值,即使是最小的数2,相乘的贡献都要大于相加,可以把这个值设为1e9(大约INT_MAX),当大于1e9时直接输出l,r
小于1e9时,直接算复杂度也不一定过得去,因此先把[l,r]中大于1的值对应的坐标取出来,再用类似前缀和的知识计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void solve(){
	int n;
	cin>>n;
	vector<int> a(n+1);
	for(int i=1;i<=n;i++)cin>>a[i];
	int l=1,r=n;
	while(l<r&&a[l]==1)l++;
	while(l<r&&a[r]==1)r--;
	vector<ll> sum(n+1),mul(n+1,1);
	for(int i=l;i<=r;i++){
		mul[i]=mul[i-1]*a[i];
		sum[i]=sum[i-1]+a[i];
		if(mul[i]>1e9){
			cout<<l<<" "<<r<<'\n';
			return;
		}
	}
	ll ans=-INF,lpos=l,rpos=r;
	vector<int> b;
	for(int i=1;i<=n;i++){
		if(a[i]>1)b.pb(i);
	}
	for(int i=0;i<b.size();i++){
		for(int j=i;j<b.size();j++){
			ll tmp=sum[b[i]-1]+sum[r]-sum[b[j]]+mul[b[j]]/mul[b[i]-1];
			if(tmp>ans){
				ans=tmp;
				lpos=b[i];
				rpos=b[j];
			}
		}
	}
	cout<<lpos<<" "<<rpos<<"\n";
 
}
This post is licensed under CC BY 4.0 by the author.