A. Min Or Sum
样例输入:
4
3
1 3 2
5
1 2 4 8 16
2
6 6
3
3 5 6
样例输出:
3
31
6
7
题意:给你一个长度为n的数组,我们可以对这个数组进行操作,每次操作可以选择两个不同的下标i和j,可以用一个x和y替代a[i]和a[j],前提是x|y等于a[i]|a[j]。问操作后数组的和的最小值是多少。
分析:对于a[i]和a[j],如果对于两者的二进制表示中的第k位都为1,那么我们可以令x的二进制表示中的第k位为1,y的二进制表示中的第k位为0,那么这样就能使得总和减少2^(k-1),按照这个思路我们可以整体讨论,对于第k位而言,如果数组中所有的数都为0,那么操作后的数组中该位也都为0,但是如果存在一些数在该位为1,那么我们可以设法将这些数中的1消掉,但是至少会保留一个,那么也就是说对于存在的1,我们可以只保留一个,那么答案就是所有的数直接或起来的结果。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
int ans=0,t;
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
ans|=t;
}
printf("%d\n",ans);
}
return 0;
}
B. Avoid Local Maximums
样例输入:
5
3
2 1 2
4
1 2 3 1
5
1 2 1 2 1
9
1 2 1 3 2 3 1 2 1
9
2 1 3 1 3 1 3 1 3
样例输出:
0
2 1 2
1
1 3 3 1
1
1 2 2 2 1
2
1 2 3 3 2 3 3 2 1
2
2 1 3 3 3 1 1 1 3
题意:给你一个长度为n的数组,你可以替换其中任意一个数,问最少的操作次数使得数组满足对于任意一个下标1<i<n均没有a[i]>a[i-1]和a[i]>a[i+1]同时满足。
分析:我们直接遍历数组,遇到一个i同时满足a[i]>a[i-1]和a[i]>a[i+1]的就把a[i+1]变为max(a[i],a[i+2])即可。统计一下答案直接输出即可。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int ans=0;
for(int i=2;i<n;i++)
{
if(a[i]>a[i-1]&&a[i]>a[i+1])
{
a[i+1]=max(a[i],a[i+2]);
ans++;
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
puts("");
}
return 0;
}
C. Differential Sorting
样例输入:
3
5
5 -4 2 -1 2
3
4 3 2
3
-3 -2 -1
样例输出:
2
1 2 3
3 4 5
-1
0
题意:给你一个长度为n的数组,你的目的是要把这个数组变为一个非递降的数组,每次操作可以选择三个下标i,j,k满足1<=i<j<k<=n,然后将a[i]用a[j]-a[k]替换,如果能实现就输出操作次数及操作方法,否则输出-1。
分析:假如a[n-1]<=a[n]并且a[n-1]-a[n]<=a[n-1],那么我们就可以把前n-2个数直接变成a[n-1]-a[n-2],这样的话就直接满足题意了。
如果a[n-1]>a[n],那显然是不满足题意的,因为我们没法修改第n-1个数,所以无论怎样操作数组都不可能变为一个非递降数组。
a[n-1]-a[n]<=a[n-1]等价于a[n]>=0,假如a[n]<0,那么我们肯定不会用第n个数来改变前面的数,那样只会使得前面的数越来越大。而且前面的数必须要是负数,因为只要有正数,那么这个正数要想变为负数必须利用其后面的正数,但是后面的正数也需要变为负数,当然这是不可能永远成立的,所以通过操作使得数组非递降也是不行的,只有不操作,也就是原来就是非递降的那样才能满足题意。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
if(n==1) puts("0");
else if(a[n]<0)
{
bool flag=true;
for(int i=1;i<n;i++)
if(a[i]>a[i+1]) flag=false;
if(!flag)
puts("-1");
else
puts("0");
}
else
{
if(a[n-1]<=a[n])
{
printf("%d\n",n-2);
for(int i=1;i<=n-2;i++)
printf("%d %d %d\n",i,n-1,n);
}
else
puts("-1");
}
}
return 0;
}
D. Infinite Set
样例输入:
2 200000
48763 1000000000
样例输出:
448201910
题意:初始时给定n个不同的数构成一个集合,现在对这个集合进行扩展,假如x属于这个集合,那么2*x+1和4*x也是属于这个集合的,现在给定一个p,问小于2^p的元素有多少个是在这个集合中的。
分析:我们先来看一下2*x+1和4*x有什么规律,往二进制上考虑的话我们可以发现前者是往x的二进制数位后添一个1,而后者是添2个0.再加上问的是小于2^p的元素的个数,所以我们就可以往二进制方面上考虑这个问题。设想我们有一个区间[l,r],其中值位于该区间内的数任意两个之间都不会相互生成,那么我们可以用这个区间内的数去生成别的区间内的数,比如区间[2*l+1,2*r+1]以及区间[4*l,4*r]。所以这就给了我们一个启发,那就是按照二进制进行动态规划。
设f[i]表示二进制表示有i位的数中在集合中的数的个数
那么就有关系式f[i]=f[i]+f[i-1]+f[i-2]
包含f[i-2]很好理解,就是通过乘以4得到,包含f[i-1]也很好理解,就是通过乘以2然后加1得到。为什么还会包含f[i]自己呢?因为一开始给定的n个数中可能存在某些数是由i位二进制表示的且不能由比他小的数扩展得到,所以一开始f[i]的值不一定为空。
最后就剩下一个问题了,就是f的初始化问题,我们就是利用给定的n个数进行初始化,把可以相互产生的数去掉较大的那个,这样就可以进行转移了。怎样判断两个数是否存在生成关系也很简单,首先可以确定的是一定是小数生成大数。那么我们从低位往高位分析大数的二进制位,如果是1,那么就把这个1去掉,然后判断这个数是否在n个数中出现过,如果出现过,那么这个大数就可以被生成,那么就不需要计数,如果是0,那么我们需要再往前看一位,如果还是0,说明是两个00相连,那么就一块去掉,再判断去掉后的数是否出现过。如果是1,那么直接返回就行了,因为末尾是10的是不可能被其他数生成的,所以直接将原数记录即可。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10,mod=1e9+7;
int f[N],a[N];//f[i]表示二进制表示有i位的数中在集合中的数的个数
int cal(int x)
{
int cnt=0;
while(x)
{
cnt++;
x/=2;
}
return cnt;
}
int main()
{
int n,p;
cin>>n>>p;
map<int,int>mp;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),mp[a[i]]=1;
for(int i=n;i>=1;i--)
{
int cnt=cal(a[i]);
int t=a[i];
for(int j=0;j<cnt;j++)
{
if(a[i]>>j&1) t/=2;
else if(a[i]>>(j+1)&1) break;//10结尾
else t/=4,j++;
if(mp[t]) break;
}
if(!mp[t]||t==a[i]||t==0) f[cal(a[i])]++;
}
int ans=f[1];
for(int i=2;i<=p;i++)
{
f[i]=(f[i]+f[i-1])%mod;
if(i>2) f[i]=(f[i]+f[i-2])%mod;
ans=(ans+f[i])%mod;
}
printf("%d",ans);
return 0;
}
E. Cars
样例1输入:
4 4
1 1 2
1 2 3
2 3 4
2 4 1
样例1输出:
YES
R 0
L -3
R 5
L 6
样例2输入:
3 3
1 1 2
1 2 3
1 1 3
样例2输出:
NO
题意:现在有n辆汽车在一个坐标轴上,每辆汽车有一个行驶方向和一个初始坐标,现在给出m个限制,一个是相关,另一个是不相关,汽车u和v相关指的是无论汽车u和v的速度如何,他们都一定会相遇的,不相关指的就是无论汽车u和v的速度如何,他们都一定不会相遇。现在给出m对关系,每对关系给出两个汽车并指出是否相关,问是否可以同时满足m个关系,如果可以输出n辆汽车的初始方向和初始坐标。
分析:首先我们来看一下汽车u和v相关满足的条件,既然说了无所谓速度,那么他俩要想相遇必然要满足方向相反而且要使得向右行驶的汽车的坐标偏小,因为同向可能不会相遇。同理汽车u和v不相关需要满足的条件就是方向相反而且要使得向右行驶的汽车的坐标偏大。
可以发现,无论两辆汽车是否相关,他们的行驶方向都一定是相反的,只是坐标的大小关系不同。那么我们可以根据m个汽车的关系建立一个图,来表明是否存在一个图可以同时满足m个限制条件,这个图的含义很简单,两点之间存在边说明这两个点代表的车的行驶方向是相反的,那么我们直接01染色判断是否存在合理染色方案即可,如果不存在那么说明一定不存在答案,如果存在一种合理染色方案那么我们还需要后续判断坐标是否合理,因为我前面说过,对于不同的关系,两辆车的坐标大小关系不同,我们可以再根据m个限制条件新建立一张图,这个图的含义如下:如果u到v有一条有向边,那么说明u的坐标比v的坐标小,根据刚才的关系分析我们也能很容易建起这么一张图,然后我们对这个图进行拓扑排序,然后直接按照拓扑序列直接从小到大分配坐标即可,如果存在环就输出无解,否则输出答案。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=5e5+10;
//c[i]=1/2代表第i个车的方向是向左/右
int h[N],e[N],ne[N],c[N],idx;
int op[N],u[N],v[N];
int p[N],du[N];
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
bool dfs(int x,int color)
{
c[x]=color;
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(c[j]&&c[j]==color) return false;
else if(c[j]) continue;
dfs(j,3-color);
}
return true;
}
bool sort_top(int n)
{
int tt=0;
queue<int> q;
for(int i=1;i<=n;i++)
if(!du[i]) q.push(i);
while(!q.empty())
{
int x=q.front();
q.pop();
p[x]=++tt;
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
du[j]--;
if(!du[j]) q.push(j);
}
}
return tt==n;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
h[i]=-1;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&op[i],&u[i],&v[i]);
add(u[i],v[i]);
add(v[i],u[i]);
}
for(int i=1;i<=n;i++)
if(!c[i])
{
if(!dfs(i,1))
{
puts("NO");
return 0;
}
}
idx=0;
for(int i=1;i<=n;i++) h[i]=-1;
for(int i=1;i<=m;i++)
{
int l,r;//分别记录向左向右走的车辆的编号
if(c[u[i]]==1) l=u[i],r=v[i];
else l=v[i],r=u[i];
if(op[i]==1) add(l,r),du[r]++;
else add(r,l),du[l]++;
}
if(sort_top(n))
{
puts("YES");
for(int i=1;i<=n;i++)
printf("%c %d\n",c[i]==1?'L':'R',p[i]);
}
else puts("NO");
return 0;
}