Codeforces Round #737 (Div. 2)
Codeforces Round #737 (Div. 2)
A - Ezzat and Two Subsequences
给出\(n\)个数分成非空的两组,输出两组分别的平均数之和的最大值。
对于\(100\%\)的数据\(1 \leq n\leq 3\times 10^5\)
将最大数单独作为一组,其他数作为另外一组的情况最优。
考虑将另外一组的任一一个数拿到最大数存在的组,那么最大数的组的平均数变小,另外一组平均数变小。
此时,两组平均数之和的值变小,因此将最大数单独作为一组,其他数作为另外一组的情况最优。
时间复杂度\(O(n)\)
# include <bits/stdc++.h>
# define inf 1e9
# define int long long
using namespace std;
const int N=3e5+10;
int a[N],n;
signed main()
{
int t; scanf("%lld",&t);
while (t--) {
scanf("%lld",&n);
int mx=-inf,sum=0;
for (int i=1;i<=n;i++) scanf("%lld",&a[i]),mx=max(mx,a[i]),sum+=a[i];
double ans=mx+(double)(sum-mx)/(double)(n-1);
printf("%.7lf\n",ans);
}
return 0;
}
B - Moamen and k-subarrays
给出\(n,k\),和\(n\)个数\(a_i\)。
询问是否能通过一次依次操作可以使得数组递增有序。
- 将数组分成恰好\(k\)个连续的块。
- 任意排列\(k\)个块
- 将\(k\)个块依次排列构成新数组。
对于\(100\%\)的数据\(1 \leq k\leq n \leq 10^5, |a_i|\leq 10^9\),\(a_i\)的值互不相同。
将\(n\)个数离散化,由于\(a_i\)的互异性,最终有序的数组为\(1-n\)依次排列。
考虑最长的连续的数字作为一个块,最优方案是这些块交换位置。
最优方案时,所需要的块数最少。
当然,使用更多块甚至分成\(n\)块也能完成任务,这是由于一个连续最长块,可以任意分成独立的若干块。
当然,使用更少的块不能完成排序,因为这样做势必造成至少一次在同一块中相邻的两个数不是连续的。显然通过交换操作,不能让这两个数造成分离,不能完成排序。
时间复杂度\(O(n log_2 n)\)
# include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
map<int,int>mp;
int main()
{
int t; scanf("%d",&t);
while (t--) {
int n,k; scanf("%d%d",&n,&k);
mp.clear();
for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+1+n);
for (int i=1;i<=n;i++) mp[b[i]]=i;
for (int i=1;i<=n;i++) a[i]=mp[a[i]];
int ans=n;
for (int i=1;i<n;i++) if (a[i+1]==a[i]+1) ans--;
puts(k>=ans?"Yes":"No");
}
return 0;
}
C - Moamen and XOR
\(n\)个数字构成的数组\(a_i\),给出\(k\),满足\(0 \leq a_i < 2^k\).
满足\(a_1 \& ... \& a_n \geq a_1 \oplus ... \oplus a_n\)。
求出\(a_i\)数组的个数,对\(10^9+7\)的取模。
对于\(100\%\)的数据\(1 \leq n\leq 2\times 10^5, 0 \leq k\leq 2\times 10^5\)
从高二进制位到低二进制位考虑这\(n\)个数的问题,
满足\(a_1 \& ... \& a_n \geq a_1 \oplus ... \oplus a_n\),就是有这样两种情况:
- \(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n\)
- \(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\)
对于所有\(n\)个数的第\(i\)个二进制位,若可以确定此时\(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\)就无须进行后面的比较。
这个时候只有这种可能,第\(i\)个二进制位上\(a_1 \& ... \& a_n = 1\)并且$ a_1 \oplus ... \oplus a_n = 0$.
只有\(n\)为偶数时,才可能在某个二进制位上存在\(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\).
因此,只有\(n\)位偶数时,才有可能\(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\).
对于所有\(n\)个数的第\(i\)个二进制位,若可以确定此时\(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n\)就进行后面的比较。
这里有两种可能:
- \(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n = 1\)
- \(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n = 0\)
分别对应:
- \(n\)为奇数,\(a_1 = a_2 = ... = a_n = 1\). (情况数为\(0\)或者\(1\),取决于\(n\)的奇偶性)
- \(a_1 ... a_n\)至少存在偶数个$a_j = 1 $并且至少存在一个\(0\).
- 当\(n\)为奇数时情况数为$C_{n}^{0} + C_{n}^{2} + ... C_{n}^{n-1}= 2^{n-1} $
- 当\(n\)为偶数时情况数为$C_{n}^{0} + C_{n}^{2} + ... C_{n}^{n} - C_{n}^{n}= 2^{n-1}-1 $
最后的答案有两种构成方案:
- \(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n\)
对于一个二进制位\(i\),当\(n\)为奇数时,答案为\(2^{n-1} + 1\),当\(n\)为偶数时,答案为\(2^{n-1}-1\)
而一共有\(k\)个二进制位需要安排,则任意安排一种的情况总数为:
\(n\)为奇数时:\((2^{n-1}+1)^k\) ,\(n\)为偶数时:\((2^{n-1}-1)^k\)
- \(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\),\(n\)为偶数。
从高位到低位枚举决断出\(a_1 \& ... \& a_n > a_1 \oplus ... \oplus a_n\)的位置\(i\)。
考虑到\(i\)之前二进制位\(j\)权值高,需要所有第\(j\)个二进制数\(a_1 \& ... \& a_n = a_1 \oplus ... \oplus a_n\),可以参考上面的解法。
考虑到\(i\)二进制位只有\(1\)种可能,即\(a_1 \& ... \& a_n = 1\)。
考虑到\(i\)之后二进制位可以任意选取,因此这些位置可以\(0/1\)乱填。
将上述两种答案构成方案相加,即为全部答案。
用快速幂计算\(2^n\),程序时间复杂度\(O(k log_2 n)\)
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int mo=1e9+7;
int pow(int x,int n){
int ans=1;
while (n) {
if (n&1) ans=ans*x%mo;
x=x*x%mo;
n>>=1;
}
return ans;
}
signed main() {
int t; scanf("%lld",&t);
while (t--) {
int n,k; scanf("%lld%lld",&n,&k);
int res=pow(2ll,n-1);
if (n&1) res=res+1; else res=res-1;
int ans=pow(res,k);
if (!(n&1)) {
for (int i=1;i<=k;i++) {
ans=(ans+pow(res,i-1)*pow(pow(2ll,n),k-i)%mo)%mo;
}
}
printf("%lld\n",ans);
}
return 0;
}
D - Ezzat and Grid
\(n\)行\(10^9\)列的\(01\)矩阵,初始全为\(0\),一些行上添加连续的\(1\),要求删除最小的行数,满足:
任意相邻的两行至少存在一列上同时为\(1\)。
要求删除最小行数的同时输出方案。
对于\(100\%\)的数据\(1 \leq n \leq 3\times 10^5\)。
按照操作的行号排序,对于第\(i\)行,设\(f_i\)第\(i\)行选取最多保留的行数。
转移方程:\(f_i= max(f_i , f_j + 1) , 1\leq j <i\),且第\(i\)行和第\(j\)行至少存在一列上同时为\(1\)。
考虑\(f_i\)更新后面的\(f_j\)的值,将\(i\)行对应的连续区间用\(f_i\)更新,求出所有更新值得最大可以转移。
这样就需要一个支持值域\([1,10^9]\),动态区间更新,支持区间求最大值和方案的数据结构。
动态开点的线段树可以做到\(O(n log_2 10^9)\)时间空降常数较大,笔者尝试写了一下\(MLE\)。
# include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int Lim = 1e9;
struct node{
int l,r;
};
struct tmp {
int val,id;
};
vector<node>a[N];
struct rec{
int l,r,ls,rs,val,id;
tmp tag;
}tr[N<<2];
int tot,n,m,f[N],g[N];
bool vis[N];
int New(int l,int r) {
++tot;
tr[tot].l=l; tr[tot].r=r;
tr[tot].id=-1;
tr[tot].tag.id=-1;
return tot;
}
void down(int x) {
if (tr[x].tag.val > tr[tr[x].ls].val) tr[tr[x].ls].val = tr[x].tag.val,tr[tr[x].ls].id = tr[x].tag.id;
if (tr[x].tag.val > tr[tr[x].ls].tag.val) tr[tr[x].ls].tag = tr[x].tag;
if (tr[x].tag.val > tr[tr[x].rs].val) tr[tr[x].rs].val = tr[x].tag.val,tr[tr[x].rs].id = tr[x].tag.id;
if (tr[x].tag.val > tr[tr[x].rs].tag.val) tr[tr[x].rs].tag = tr[x].tag;
tr[x].tag=(tmp){0,0};
}
void update(int x,int opl,int opr,int val,int id) {
if (opl<=tr[x].l&&tr[x].r<=opr) {
if (val > tr[x].val) tr[x].val=val,tr[x].id=id;
if (val > tr[x].tag.val) tr[x].tag=(tmp){val,id};
return ;
}
int mid=(tr[x].l+tr[x].r)/2;
if (!tr[x].ls) tr[x].ls = New(tr[x].l,mid);
if (!tr[x].rs) tr[x].rs = New(mid+1,tr[x].r);
down(x);
if (opl<=mid) update(tr[x].ls,opl,opr,val,id);
if (opr>mid) update(tr[x].rs,opl,opr,val,id);
if (tr[x].val < tr[tr[x].ls].val) tr[x].val = tr[tr[x].ls].val,tr[x].id=tr[tr[x].ls].id;
if (tr[x].val < tr[tr[x].rs].val) tr[x].val = tr[tr[x].rs].val,tr[x].id=tr[tr[x].rs].id;
}
tmp query(int x,int opl,int opr) {
if (!x) return (tmp) {0,0};
if (opl<=tr[x].l&&tr[x].r<=opr) {
return (tmp) {tr[x].val,tr[x].id};
}
int mid=(tr[x].l+tr[x].r)/2;
if (!tr[x].ls) tr[x].ls = New(tr[x].l,mid);
if (!tr[x].rs) tr[x].rs = New(mid+1,tr[x].r);
down(x);
tmp res = (tmp){0,0};
if (opl<=mid) {
tmp t=query(tr[x].ls,opl,opr);
if (t.val > res.val) res = t;
}
if (opr>mid) {
tmp t=query(tr[x].rs,opl,opr);
if (t.val > res.val) res=t;
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int id,l,r; scanf("%d%d%d",&id,&l,&r);
a[id].push_back((node){l,r});
}
tot=1;
tr[1].id=-1; tr[1].val=0;
tr[1].l=1; tr[1].r=Lim;
for (int i=1;i<=n;i++) {
for (int j=0;j<a[i].size();j++) {
int l=a[i][j].l,r=a[i][j].r;
tmp w = query(1,l,r);
if (w.val+1>f[i]) {
f[i]=w.val+1; g[i]=w.id;
}
}
for (int j=0;j<a[i].size();j++) {
int l=a[i][j].l,r=a[i][j].r;
update(1,l,r,f[i],i);
}
}
int ans = 0 , ii = -1;
for (int i=1;i<=n;i++) if (f[i]>ans) {
ans=f[i] , ii = i;
}
printf("%d\n",n-ans);
vis[ii]=1;
while (true) {
ii = g[ii];
if (ii <= 0) break;
vis[ii]=1;
}
for (int i=1;i<=n;i++) if (!vis[i]) printf("%d ",i);
puts("");
return 0;
}
// Runtime Error on Test 15
一个更聪明的办法是,离散化后直接普通线段树维护,时间空间可以做到\(O(n log_2 n)\)。
# include <bits/stdc++.h>
using namespace std;
const int N=3e5+10;
const int Lim = 1e9;
struct node{
int l,r;
};
struct tmp {
int val,id;
};
vector<node>a[N];
struct rec{
int val,id;
tmp tag;
}tr[N<<3];
int tot,n,m,f[N],g[N];
bool vis[N];
void down(int x) {
if (tr[x].tag.val > tr[2*x].val) tr[2*x].val = tr[x].tag.val,tr[2*x].id = tr[x].tag.id;
if (tr[x].tag.val > tr[2*x].tag.val) tr[2*x].tag = tr[x].tag;
if (tr[x].tag.val > tr[2*x+1].val) tr[2*x+1].val = tr[x].tag.val,tr[2*x+1].id = tr[x].tag.id;
if (tr[x].tag.val > tr[2*x+1].tag.val) tr[2*x+1].tag = tr[x].tag;
tr[x].tag=(tmp){0,0};
}
void update(int x,int l,int r,int opl,int opr,int val,int id) {
if (opl<=l&&r<=opr) {
if (val > tr[x].val) tr[x].val=val,tr[x].id=id;
if (val > tr[x].tag.val) tr[x].tag=(tmp){val,id};
return ;
}
int mid=(l+r)/2;
down(x);
if (opl<=mid) update(2*x,l,mid,opl,opr,val,id);
if (opr>mid) update(2*x+1,mid+1,r,opl,opr,val,id);
if (tr[x].val < tr[2*x].val) tr[x].val = tr[2*x].val,tr[x].id=tr[2*x].id;
if (tr[x].val < tr[2*x+1].val) tr[x].val = tr[2*x+1].val,tr[x].id=tr[2*x+1].id;
}
tmp query(int x,int l,int r,int opl,int opr) {
if (!x) return (tmp) {0,0};
if (opl<=l&&r<=opr) {
return (tmp) {tr[x].val,tr[x].id};
}
int mid=(l+r)/2;
down(x);
tmp res = (tmp){0,0};
if (opl<=mid) {
tmp t=query(2*x,l,mid,opl,opr);
if (t.val > res.val) res = t;
}
if (opr>mid) {
tmp t=query(2*x+1,mid+1,r,opl,opr);
if (t.val > res.val) res=t;
}
return res;
}
int tt[2*N],T;
int getli(int x) {
return lower_bound(tt+1,tt+1+T,x)-tt;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int id,l,r; scanf("%d%d%d",&id,&l,&r);
tt[++tt[0]]=l; tt[++tt[0]]=r;
a[id].push_back((node){l,r});
}
sort(tt+1,tt+1+tt[0]);
T=unique(tt+1,tt+1+tt[0])-tt-1;
int mx=0;
for (int i=1;i<=n;i++)
for (int j=0;j<a[i].size();j++) {
a[i][j].l=getli(a[i][j].l);
a[i][j].r=getli(a[i][j].r);
mx = max(mx,a[i][j].l);
mx = max(mx,a[i][j].r);
}
tot=1;
for (int i=1;i<=n;i++) {
for (int j=0;j<a[i].size();j++) {
int l=a[i][j].l,r=a[i][j].r;
tmp w = query(1,1,mx,l,r);
if (w.val+1>f[i]) {
f[i]=w.val+1; g[i]=w.id;
}
}
for (int j=0;j<a[i].size();j++) {
int l=a[i][j].l,r=a[i][j].r;
update(1,1,mx,l,r,f[i],i);
}
}
int ans = 0 , ii = -1;
for (int i=1;i<=n;i++) if (f[i]>ans) {
ans=f[i] , ii = i;
}
printf("%d\n",n-ans);
vis[ii]=1;
while (true) {
ii = g[ii];
if (ii <= 0) break;
vis[ii]=1;
}
for (int i=1;i<=n;i++) if (!vis[i]) printf("%d ",i);
puts("");
return 0;
}