Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)【ABCDE】(题解)
[toc]
涵盖知识点:思维、树状数组。
比赛链接:传送门
A - Even Subset Sum Problem
题意: 找一个子序列使得和为偶数
题解: 选一个偶数或者两个奇数。
Accept Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
int a[maxn];
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
int flag=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(!(a[i]&1))flag=i;
}
if(flag){
puts("1");
cout<<flag<<"\n";
}
else{
if(n==1){
puts("-1");
}
else{
puts("2\n1 2");
}
}
}
return 0;
}
B - Count Subrectangles
题意: 给两个数组$a,b$。矩阵$c$满足$c_{i,j}=a_ib_j$。问$c$中有多少个子区域全为1且1的个数恰好为$k$。
题解: 子区域的长宽一定为$k$的因子,枚举一遍分别在$a,b$数组里面扫描一下可能性。
Accept Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn=4e4+10;
typedef long long ll;
int a[maxn],b[maxn],n,m,k;
int solve(int x){
int cnt1=0,cnt2=0,cnt=0,y=k/x,res=0;
for(int i=1;i<=n;i++){
if(a[i])cnt1++;
else cnt1=0;
if(cnt1>=x)cnt++;
}
for(int i=1;i<=m;i++){
if(b[i])cnt2++;
else cnt2=0;
if(cnt2>=y)res+=cnt;
}
return res;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
ll ans=0;
for(int i=1;i<=sqrt(k);i++){
if(k%i==0){
ans+=solve(i);
if(k/i!=i)
ans+=solve(k/i);
}
}
cout<<ans<<"\n";
return 0;
}
C - Unusual Competitions
题意: 给定一个左右圆括号序列。每次可以花费$l$以选择长度为$l$的字串进行排序。问最少花费多少使得括号匹配。
题解: 从左向右贪心,若右括号之前没有左括号匹配就像后扫描到第一个可以完全匹配所有右括号的点,记录区间长度。
Accept Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
typedef long long ll;
int a[maxn];
int main(){
int n;
cin>>n;
string s;
cin>>s;
int cnt1=0,cnt2=0;
for(char i:s){
if(i=='(')cnt1++;
else cnt2++;
}
if(cnt1!=cnt2){
puts("-1");
return 0;
}
int ans=0,cnt=0,l=0;
for(int i=1;i<=s.length();i++){
if(s[i-1]=='(')a[i]=a[i-1]-1;
else a[i]=a[i-1]+1;
if(a[i]==0){
if(a[i-1]==1){
ans+=i-l;
}
l=i;
}
}
cout<<ans<<"\n";
return 0;
}
D - Present
题意: 给定数组$a$,求下列表达式:
$$(a_1 + a_2) \oplus (a_1 + a_3) \oplus \ldots \oplus (a_1 + a_n) \ \oplus (a_2 + a_3) \oplus \ldots \oplus (a_2 + a_n) \ \vdots \ \oplus (a_ + a_n) \$$
题解: 考虑最后答案的每一位。排除低位对高位的影响后,对于所有数字$a$,对第$k$位的贡献等同于$a% 2^$对第$k$位的贡献。对于$pos=a% 2^\(而言,只有位于区间\)[2^-pos,2^-1]$内的数才能改变第$k$位的状态。所以,我们可以对于每一位开一个树状数组维护区间状态。枚举最多25位即可。
Accept Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+10,maxc=(1<<25)+10;
int a[maxn],n,v;
bool c[maxc];
inline int lowbit(int x){return x&(-x);}
void add(int x){
while(x<v){
c[x]^=1;
x+=lowbit(x);
}
}
bool query(int x){
bool res=0;
while(x){
res^=c[x];
x-=lowbit(x);
}
return res;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int ans=0;
for(int p=0;p<25;p++){
memset(c,0, sizeof c);
v=1<<p;
int res=0;
for(int i=1;i<=n;i++){
if(a[i]>>p&1)res++;
}
res=(res&1)&((n-res)&1);
for(int i=1;i<=n;i++){
int pos=a[i]%v;
if(pos){
res^=query(v-1)^query(v-pos-1);
add(pos);
}
}
if(res)ans=ans|(1<<p);
}
cout<<ans<<"\n";
return 0;
}
E - Instant Noodles
题意: 给定共$2n$个点,$m$条边的二分图。右部的每个点都有一个权值,现规定$S$为左部点的任意子集,$N(S)$为与$S$中的点直接相连的右部点集。$f(S)$为$S$中所有点权值之和。要求所有$f(N(S))$的$gcd$。
题解: 观察到数据范围,显然不能暴力枚举所有左节点的子集映射到右节点进行处理。但是我们可以逆向思考,从右节点映射到左节点只需要扫描一遍即可。对于相同的映射结果我们可以将这些点合并为一个权值等于这些点权值之和的新节点,并将其映射到左节点的子集。实际操作就是对右部点按照所映射的左部点集,利用map对右部点进行缩点处理后,扫描一遍map取$gcd$即可。
对于缩点求和取$gcd$的正确性证明:
设$a=np,b=mp$,\(gcd(n,m)=1\),所以:
\(gcd(a+b,a)=gcd((n+m)p,np)=p=gcd(a,b)\)
\(gcd(a+b,a,b)=gcd(gcd(a+b,a),b)=gcd(gcd(a,b),b)=gcd(a,b)\)
可以向无穷项求和拓展。
Accept Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
typedef long long ll;
ll c[maxn];
vector<int> edg[maxn];
int main(){
int t;
cin>>t;
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&c[i]);
edg[i].clear();
}
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
edg[v].push_back(u);
}
map<vector<int> ,ll> mp;
for(int i=1;i<=n;i++){
if(edg[i].size()){
sort(edg[i].begin(),edg[i].end());
mp[edg[i]]+=c[i];
}
}
ll ans=0;
for(auto i:mp){
ans=__gcd(ans,i.second);
}
cout<<ans<<"\n";
}
return 0;
}