Codeforces Round 892 (Div. 2)
比较简单的一场,可惜在家里打的不是很适应,前四道题都写史了然后重构的,不然还能更快。
A. United We Stand
题意
给你 \(n\) 个数,让你把这 \(n\) 个数放到 \(b,c\) 两个集合里,保证 \(b_i\) 不能被 \(c_j\) 整除 \((1\leq i\leq |b|,1\leq j\leq |c|)\) 。且 \(b,c\) 不能为空。
思路
一个比他大的数一定不能被他整除,所以只要把最小的数放到 \(b\) 里面,把剩余的数放到 \(c\) 里面就可以了。
代码
void solve()
{
int n;
cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
sort(a.begin()+1,a.end());
vector<int> b,c;
b.push_back(a[1]);
int flag=0;
for(int i=2;i<=n;i++)
{
if(a[i]!=a[i-1]) flag=1;
if(flag) c.push_back(a[i]);
else b.push_back(a[i]);
}
if(!flag){cout<<-1<<endl;return;}
cout<<b.size()<<' '<<c.size()<<endl;
for(auto x:b) cout<<x<<' ';
cout<<endl;
for(auto x:c) cout<<x<<' ';
cout<<endl;
}
B. Olya and Game with Arrays
题意
给你 \(n\) 个数组。每个数组至多操作 \(1\) 次:你可以把这个数组中的一个数放到另一个数里面。让你求出每个数组中最小值之和的最大值。即 \(\sum_{i=1}^{n} min_{j=1}^{m_i} a_{i,j}\)。
思路
首先,所有数组中的最小值一定会被选的,那我们就让所有数组中的最小值都放到同一个数组里。这样每个数组的贡献就是他的次小值和所有数组中的最小值再减去次小值中的最小值。
代码
void solve()
{
int n,ans=0,num1=1e18,num2=1e18;
cin>>n;
for(int i=1;i<=n;i++)
{
int m;
cin>>m;
vector<int> a(m+1);
for(int j=1;j<=m;j++)
cin>>a[j];
sort(a.begin()+1,a.end());
num1=min(num1,a[1]);
num2=min(num2,a[2]);
ans+=a[2];
}
ans-=num2;
ans+=num1;
cout<<ans<<endl;
}
C. Another Permutation Problem
题意
让你构造一个排列,使 \((\sum_{i=1}^{n}p_i\cdot i)-(max_{j_i}^{n}p_j\cdot j)\) 这个柿子的值最大。
思路
一开始直接暴力找的规律。
注意到如果想使这个式子最大,前面 \(x\) 个数就是正常的 \(1...x\) 的顺序排列,对于最后 \(x+1...n\) 则是反过来的。这个结论看起来非常的正确,但是我不会证明,稍微的感性思考一下。
但是这个具体的 \(x\) 的位置我并不知道是多少,但是发现 \(n\) 的数据范围才 \(500\) 那么我们直接暴力枚举 \(x\) 的位置。 \(O(n^2)\) 。
代码
void solve()
{
int n,ans=0;
cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++) a[i]=i;
for(int pos=1;pos<=n;pos++)
{
vector<int> b=a;
reverse(b.begin()+pos,b.end());
int sum=0,maxx=0;
for(int j=1;j<=n;j++)
{
sum+=a[j]*b[j];
maxx=max(maxx,a[j]*b[j]);
}
ans=max(ans,sum-maxx);
}
cout<<ans<<endl;
}
D. Andrey and Escape from Capygrad
题意
给你 \(n\) 个由 \(l_i,r_i,a_i,b_i\) 描述的区间。你可以从 \([l_i,r_i]\) 中的任意位置传送到 \([a_i,b_i]\) 中任意的位置,传送次数使无限的。然后给你 \(q\) 次询问,每次询问给你一个初始点 \(x_i\) ,问你从 \(x_i\) 为起点最远能传送到哪个位置。
思路
观察这个区间我们发现,你一定不可能从 \([b_i,r_i]\) 这里面传送到前面的位置,这样一定是亏的。
所以每段区间就转化成了你可以在 \([l_i,b_i]\) 中的任意位置传送,然后考虑怎么传送才能更远。如果两个区间相交了那么这两个区间就可以合并成一个区间,那么这个区间上的每一个点都能传送到右端点。
我一开始想的是并查集维护,但是区间范围很大,不可能对于 \([1,10^9]\) 每一个点都去维护,然后想到了离散化。离散化完点的个数不会超过 \(2n+m\) ,把每个区间的点都遍历一次让他们能传送到的最远位置都是当前区间的右端点,合并的时候先按照右端点排序,再用个栈模拟一下区间合并就行了。如果某个点没有被覆盖说明他不能传送,直接输出当前的位置就行了。
可惜一开始写史了,没想到用栈。不然还能更快的QAQ。
代码
void solve()
{
int n;
cin>>n;
vector<int> X;
vector<pair<int,int>> a(n+1);
for(int i=1;i<=n;i++)
{
int l,r,x,y;
cin>>l>>r>>x>>y;
a[i]={l,y};
X.push_back(l);
X.push_back(y);
}
int m; cin>>m;
vector<int> q(m+1);
for(int i=1;i<=m;i++)
{
cin>>q[i];
X.push_back(q[i]);
}
sort(X.begin(),X.end());
X.erase(unique(X.begin(),X.end()),X.end());
sort(a.begin()+1,a.end(),cmp);
for(int i=1;i<=n;i++)
{
a[i].first=lower_bound(X.begin(),X.end(),a[i].first)-X.begin()+1;
a[i].second=lower_bound(X.begin(),X.end(),a[i].second)-X.begin()+1;
}
vector<pair<int,int>> st;
for(int i=1;i<=n;i++)
{
if(!st.size()) st.push_back(a[i]);
else
{
auto [l,r]=st.back();
if(a[i].second>=l)
{
l=min(l,a[i].first),r=max(r,a[i].second);
st.pop_back();
st.push_back({l,r});
}
else st.push_back(a[i]);
}
}
vector<int> pos(2*n+m+1);
// for(int i=1;i<=n;i++) cout<<a[i].first<<' '<<a[i].second<<endl;
for(auto [l,r]:st)
for(int i=l;i<=r;i++) pos[i]=r;
// for(int i=1;i<=X.size();i++) cout<<pos[i]<<' ';
// cout<<endl;
for(int i=1;i<=m;i++)
{
int temp=lower_bound(X.begin(),X.end(),q[i])-X.begin()+1;
if(pos[temp]) cout<<X[pos[temp]-1]<<' ';
else cout<<q[i]<<' ';
}
cout<<endl;
}