思路来源
https://codeforces.com/blog/entry/67891(官方题解)
其实有时候死磕官方题解没什么不好的
D.Tolik and His Uncle
题目
有一个n*m(1<=n*m<=1e6)的网格图,初始小人位于(1,1),
下一跳可以跳到任意还没跳到的位置,例如跳到(x,y),则差向量为(x-1,y-1),
需在每一跳的差向量(dx,dy)不同下,访问所有位置,输出下一跳的序列
题解
最后向量不同的要求,就是自己在画图的时候,没有两个箭头是平行且长度相同的
考虑一维1*m的情形,应该是(1,1)跳到(1,m)跳回(1,2)跳到(1,m-1)诸如此类,由于每次长度减一,所以最终不同
那两行的也是类似的,从(1,1)跳到(2,m)跳回(1,2)跳到(2,m-1),再重复这个过程
n行的情形,从(1,1)跳到(n,m)跳回(1,2)跳到(n,m-1),由于对称性,最终落在(n,1),
再向(2,1)跳,就变成子问题n-2行了;
特别地,n是奇数的话,中间一行采用一维跳法即可
心得
构造题总有一种智商惨遭碾压的感觉……
代码
#include <bits/stdc++.h>
using namespace std;
int n,m,mid;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n/2;++i)
{
for(int j=1;j<=m;++j)
{
printf("%d %d\n",i,j);
printf("%d %d\n",n+1-i,m+1-j);
}
}
if(n&1)
{
mid=n-n/2;//中间那一行
for(int j=1;j<=m/2;++j)
{
printf("%d %d\n",mid,j);
printf("%d %d\n",mid,m+1-j);
}
if(m&1)printf("%d %d\n",mid,m-m/2);
}
return 0;
}
E.Serge and Dining Room
题目
n(n<=3e5)个菜价,第i个菜价值ai(1<=ai<=1e6)
m(m<=3e5)个小学生,第j个人有bj块钱(1<=bj<=1e6)
每次小学生会挑自己能选的最贵的菜,挑完之后让Serge挑,
但是有q(q<=3e5)个修改,第k次修改包括op[k],pos[k],x[k],op[k]={1,2}
①1 p x 代表把a[p]的值改为x
②2 p x 代表把b[p]的值改为x
修改完之后,Serge有无限钱,会挑没被小学生选过的最贵的,
问他挑到的菜的价钱,如果没菜可挑,输出-1
题解
递减的考虑这个答案的价钱x,
一定会在某一时刻,出现大于等于价钱x的菜数比大于等于x的小学生钱数的个数多的情况,满足这个条件的最大的x就是答案
而且由于前面的x都不满足这一条件,所以小学生人数所拥有的钱始终可以买的了菜,
只需贪心地用递减序中第一个出现的小学生钱数买第一个出现的菜,第二个买第二个,以此类推,即可证明
离散化所有读入的菜价、小学生钱、修改的值,维护3*3e5的权值线段树
每个点维护大于等于这个点所代表的值的个数,即后缀和,
pushup上去左右子的最大值,最大值>0说明必有至少一子满足条件
修改时,把单点修改,变成改前面的数的后缀和的区间修改
查询时,先判整棵树有没有答案,有的话优先右子,然后左子
心得
做过cf393的E的线段树,也是维护单点后缀和,把单点修改变成改前缀和,最后询问最右的大于0的位置
可以说是代码一模一样,然而把思路转到和那个题一样就感觉很费劲啊……
不然,如果赛中能做出一道2400的E,可以说直接上紫了……
代码
#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
using namespace std;
const int N=3e5+10;
const int V=3*N;//有多少不同的值
int n,m,q;
int a[N],b[N],op[N],pos[N],x[N];
int c[V],cnt;//用于离线读入修改之后 将a[]b[]x[]离散化
int suf[4*V],cov[4*V];//线段树上每个点维护后缀和 单点pos修改会对[1,pos]的后缀和都有影响
//pushup维护后缀和的最大值 便于二分查找最后位置时先右后左 只要max>0说明左右子必有>0的
void pushdown(int p,int l,int r)
{
if(!cov[p])return;
suf[p<<1]+=cov[p];
cov[p<<1]+=cov[p];
suf[p<<1|1]+=cov[p];
cov[p<<1|1]+=cov[p];
cov[p]=0;
}
void update(int p,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr)
{
suf[p]+=v;
cov[p]+=v;
return;
}
int mid=l+r>>1;
pushdown(p,l,r);
if(ql<=mid)update(lson,ql,qr,v);
if(qr>mid)update(rson,ql,qr,v);
suf[p]=max(suf[p<<1],suf[p<<1|1]);
}
int ask(int p,int l,int r)
{
if(l==r)return l;//按c数组建线段树 返回位置pos后c[pos]即对应位置的值
int mid=l+r>>1;
pushdown(p,l,r);
if(suf[p<<1|1]>0)return ask(rson);
else return ask(lson);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
c[++cnt]=a[i];
}
for(int i=1;i<=m;++i)
{
scanf("%d",&b[i]);
c[++cnt]=b[i];
}
scanf("%d",&q);
for(int i=1;i<=q;++i)
{
scanf("%d%d%d",&op[i],&pos[i],&x[i]);
c[++cnt]=x[i];
}
sort(c+1,c+cnt+1);
cnt=unique(c+1,c+cnt+1)-(c+1);
for(int i=1;i<=n;++i)
{
a[i]=lower_bound(c+1,c+cnt+1,a[i])-c;
update(1,1,cnt,1,a[i],1);
}
for(int i=1;i<=m;++i)
{
b[i]=lower_bound(c+1,c+cnt+1,b[i])-c;
update(1,1,cnt,1,b[i],-1);
}
for(int i=1;i<=q;++i)
x[i]=lower_bound(c+1,c+cnt+1,x[i])-c;
//a[]菜+1 b[]人-1 找到最后位置的后缀和>0的位置 即菜多人少的位置
for(int i=1;i<=q;++i)
{
if(op[i]==1)
{
update(1,1,cnt,1,a[pos[i]],-1);//删原值
a[pos[i]]=x[i];
update(1,1,cnt,1,a[pos[i]],1);//加新值
}
else if(op[i]==2)
{
update(1,1,cnt,1,b[pos[i]],1);//删原值
b[pos[i]]=x[i];
update(1,1,cnt,1,b[pos[i]],-1);//加新值
}
if(suf[1]<=0)puts("-1");
else printf("%d\n",c[ask(1,1,cnt)]);
}
return 0;
}