cf Round #772(Div. 2)
B
Description
给定一个长度为n的序列,可以选择任意位置修改成任意数。求最少需要修改多少次能使序列不存在i满足\(a_i>a_{i-1}\)且\(a_i>a_{i+1}\)。
Solution
对于一个三元对(\(a_{i-1},a_i,a_{i+1}\)),如果\(a_i>a_{i-1}\)且\(a_i>a_{i+1}\),那么势必要消除三元对中的一个才可满足条件。
从左往右依次寻找这样的三元对,此时会对后续产生影响的只有\(a_{i+1}\),破坏该三元对需要使\(a_{i+1}\)变大到\(a_i\),我们希望此次修改能够使\((a_{i+1},a_{i+2},a_{i+3})\)也不会是这样的三元对,那么如果\(a_{i+2}>a_i\),还需将\(a_{i+1}\)提到\(a_{i+2}\)才能同时规避\((a_{i+1},a_{i+2},a_{i+3})\)的风险。即\(a_{i+1}=max\{a_i,a_{i+2}\}\)。
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int a[N],n,ans;
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
ans=0;a[0]=a[1];a[n+1]=a[n];
for(int i=1;i<=n;++i)
if(a[i]>a[i-1]&&a[i]>a[i+1]){
++ans;
a[i+1]=max(a[i],a[i+2]);
}
printf("%d\n",ans);
for(int i=1;i<=n;++i)
printf("%d ",a[i]);
printf("\n");
}
return 0;
}
C
Description
给定一个长度为n的序列,可以进行不多于n次的操作:每次选择\(1\leq x<y<z\leq n\)使\(a_x=a_y-a_z\)。求是否存在方案使得\(a_1\leq a_2\leq...\leq a_n\)。
Solution
就硬构造。
首先,最后两位是无法改变的,所以需要\(a_{n-1}<a_n\)。
对于\(a_n\geq0\)的情况,\(a_{n-1}-a_n\leq a_{n-1}<a_n\),对于前n-2个数均执行这种操作即可。
对于\(a_n<0\)的情况,\(a_{n-1}<a_n<0\),如果\(a_{n-2}>a_{n-1}\),无法通过\(a_{n-2}=a_{n-1}-a_n>a_{n-1}\)的操作使序列合法。即需要\(a_{n-2}\leq a_{n-1}\leq a_n\)。通过数学归纳法可得,需要满足条件\(a_1\leq a_2\leq...\leq a_n\)序列才有可能合法。
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int a[N],n,ans;
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
if(a[n-1]>a[n]){
puts("-1");
continue;
}
if(a[n]<0){
bool flag=true;
for(int i=1;i<n;++i)
if(a[i]>a[i+1]){
flag=false;break;
}
if(flag) puts("0");
else puts("-1");
}
else{
printf("%d\n",n-2);
for(int i=1;i<=n-2;++i)
printf("%d %d %d\n",i,n-1,n);
}
}
return 0;
}
D
Description
给定一个集合的初始状态。如果集合中存在x,那么4x和2x+1也必须在集合中。在集合中所有数<\(2^p\)的前提下,求集合的最终大小。
Solution
可以证明,任意两个不相等的数,经过若干次操作后不会相等。
那么我们可以把问题转化成求每个数\(a_i\)一共可以扩展出多少个数(记为\(t_i\))以及它们能通过若干次操作到达哪些数\(a_j\)。那么最终集合大小为\(\sum_i(t_i-\sum_jt_j)\)。
问题转化为如何求\(t_i\)和满足上述关系的(i,j)。
对于操作4x和2x+1,我们可以用二进制操作来表示它们:
- 4x:x左移2位,即末尾多2个0
- 2x+1:x左移1位,末尾变为1,即末尾多1个1
- <\(2^p\):二进制位数\(\leq\)p
那么对于一个数\(a_j\),我们可以通过判断末尾还原一步步操作,得到可能的\(a_i\)。
对于1个数,实际上两种操作就是分别将二进制位数+1或+2,那么每个数导致的最终大小是和位数相关的。
f[i]表示集合初始值只有一个二进制位数为i时的集合最终大小:f[i]=f[i+1]+f[i+2]。
按上述思路容斥即可。
#include<bits/stdc++.h>
using namespace std;
const int N=200005,M=(1e9+7);
int a[N],f[N],t[N],n,p,ans;
int bits(int x){
int ret=0;
while(x){
++ret;x>>=1;
}
return ret;
}
void RemoveDuplicate(int i){
int x=a[i];
while(x){
if((x&1)&&x>1){
x=x>>1;
int j=lower_bound(a+1,a+i,x)-a;
if(a[j]==x) t[j]-=t[i];
if(t[j]<0) t[j]+=M;
}
else if(!(x&3)&&x>=4){
x=x>>2;
int j=lower_bound(a+1,a+i,x)-a;
if(a[j]==x) t[j]-=t[i];
if(t[j]<0) t[j]+=M;
}
else break;
}
}
int main(){
scanf("%d%d",&n,&p);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+1+n);
f[p]=1;
for(int i=p-1;i;--i){
f[i]=f[i+1]+1;
if(i<p-1) f[i]+=f[i+2];
f[i]%=M;
}
for(int i=n,j,k;i;--i){
k=bits(a[i]);
if(k-1<=p) t[i]+=f[k];
else continue;
t[i]%=M;
ans=(ans+t[i])%M;
RemoveDuplicate(i);
}
printf("%d\n",ans);
return 0;
}
E
Description
数轴上有n个坐落在不同位置的点,每个点有自己的朝向,给出m个约束条件:点x和点y是相向还是背向。求满足条件的n个点的坐标和朝向。
Solution
- 相向的点对:朝右 朝左
- 相反的点对:朝左 朝右
- 按约束条件建无向图,则有解的必要条件是边的两端朝向不同。bfs染色即可判定。
- 然后再按约束条件的左右关系建边,有解的充要条件是该图存在包含所有点的拓扑序。
不用纠结1中染色时哪个颜色代表左边,哪个颜色代表右边,因为将数轴的正负方向翻转之后约束条件仍成立。
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
struct edge{
int l,r,t;
}ed[N];
struct graph{
int nxt,to;
}e[N<<1];
int g[N],col[N],x[N],deg[N],n,m,cnt;
void addedge(int x,int y){
e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y;
}
void adde(int x,int y){
addedge(x,y);addedge(y,x);
}
queue<int> q;
bool bfs(int u){
q.push(u);col[u]=2;//L:2 R:3
while(!q.empty()){
u=q.front();q.pop();
for(int i=g[u];i;i=e[i].nxt)
if(!col[e[i].to]){
col[e[i].to]=col[u]^1;
q.push(e[i].to);
}
else if(!(col[u]^col[e[i].to]))
return false;
}
return true;
}
void topo(int u){
q.push(u);
while(!q.empty()){
u=q.front();q.pop();x[u]=++cnt;
for(int i=g[u];i;i=e[i].nxt)
if(!(--deg[e[i].to])){
q.push(e[i].to);
}
}
}
bool topo(){
cnt=0;
for(int i=1;i<=n;++i)
if(!deg[i]&&!x[i]) topo(i);
for(int i=1;i<=n;++i)
if(!x[i]) return false;
return true;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
scanf("%d%d%d",&ed[i].t,&ed[i].l,&ed[i].r);
adde(ed[i].l,ed[i].r);
}
bool flag=true;
for(int i=1;i<=n;++i)
if(!col[i]){
if(!bfs(i)){
flag=false;break;
}
}
if(!flag){
puts("NO");
return 0;
}
memset(g,0,sizeof(g));cnt=0;
for(int i=1;i<=m;++i)
if(ed[i].t&1){
if(col[ed[i].l]&1) swap(ed[i].l,ed[i].r);
addedge(ed[i].l,ed[i].r);++deg[ed[i].r];//L R
}
else{
if(col[ed[i].l]&1) swap(ed[i].l,ed[i].r);
addedge(ed[i].r,ed[i].l);++deg[ed[i].l];//R L
}
if(topo()){
puts("Yes");
for(int i=1;i<=n;++i)
printf("%c %d\n",(col[i]&1)?'R':'L',x[i]);
}
else puts("NO");
return 0;
}
F
Description
给定x个点的坐标及它们的权值,保证坐标升序。q次询问,每次求区间[l,r]之间的加权点距\(|x_i-x_j|(w_i+w_j)\)的最小值。
Solution
显然对于每个点i,它贡献的最小加权点距为\(min\{|x_i-x_j|(w_i+w_{j_x})\}\),\(j_x\)是i左边/右边第一个\(w_j\)比\(w_i\)大/小的下标。即只有这些点对有意义。
设有意义的点对为(x,y),
问题转化成求完全落在区间[l,r]内的点对(x,y)的加权点距的最小值。
线段树或树状数组实现即可。
#include<bits/stdc++.h>
using namespace std;
const int N=300005;
struct query{
int l,r,i;
friend bool operator < (const query a,const query b){
if(a.l!=b.l) return a.l>b.l;
}
}qry[N];
struct segment{
int l,r;
friend bool operator < (const segment a,const segment b){
if(a.l!=b.l) return a.l>b.l;
}
}a[N<<2];
int x[N],w[N],n,q,cnt;
long long bit[N],ans[N];
int s1[N],s2[N],t1,t2;
int lowbit(int x){
return x&(-x);
}
void change(int x,long long k){
for(int i=x;i<=n;i+=lowbit(i))
if(bit[i]<0||k<bit[i]) bit[i]=k;
}
long long ask(int x){
long long ret=-1;
for(int i=x;i;i-=lowbit(i))
if(ret<0||(bit[i]>=0&&bit[i]<ret)) ret=bit[i];
return ret;
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)
scanf("%d%d",&x[i],&w[i]);
for(int i=1;i<=q;++i){
scanf("%d%d",&qry[i].l,&qry[i].r);
qry[i].i=i;
}
for(int i=1;i<=n;++i){
//左边第一个比w[i]小
while(t1&&w[i]<w[s1[t1]]) --t1;
if(t1) a[++cnt]=(segment){s1[t1],i};
s1[++t1]=i;
//左边第一个比w[i]大
while(t2&&w[i]>w[s2[t2]]) --t2;
if(t2) a[++cnt]=(segment){s2[t2],i};
s2[++t2]=i;
}
t1=t2=0;
for(int i=n;i>=1;--i){
//右边第一个比w[i]小
while(t1&&w[i]<w[s1[t1]]) --t1;
if(t1) a[++cnt]=(segment){i,s1[t1]};
s1[++t1]=i;
//右边第一个比w[i]大
while(t2&&w[i]>w[s2[t2]]) --t2;
if(t2) a[++cnt]=(segment){i,s2[t2]};
s2[++t2]=i;
}
sort(a+1,a+1+cnt);
sort(qry+1,qry+1+q);
fill(bit+1,bit+1+n,-1);
for(int i=1,j=1;i<=q;++i){
while(j<=cnt&&a[j].l>=qry[i].l){
change(a[j].r,1ll*(x[a[j].r]-x[a[j].l])*(w[a[j].r]+w[a[j].l]));
++j;
}
ans[qry[i].i]=ask(qry[i].r);
}
for(int i=1;i<=q;++i)
printf("%lld\n",ans[i]);
return 0;
}