杂题
\(AtCoder~~Regular~~Contest~~067~~Yakiniku~~Restaurants\)
考虑最优移动方式,一定可以是从某个点\(a\)向右移动到\(b\)。
在这个区间\([a,b]\)内,要做到吃的最多。
考虑一餐券\(j\)在\(i\)餐馆使用的情况下,移动范围\([l,r]\)最大时不亏(即如果区间为\([l-1,r]\),在\(l-1\)位置使用餐券j更优,如果区间为\([l,r+1]\),在\(r+1\)位置使用餐券\(j\)更优)。
记餐券\(j\)在\(i\)餐馆使用的情况下,最大移动范围\([~~L[i][j],R[i][j]~~]\)不亏。这个东西可以用单调栈求。
所以如果确定了移动范围\([a,b]\),如果\(a\leq j \And\And b\geq j\And\And L[i][j]\leq a \And\And R[i][j]\geq b\),你就会在\(i\)餐馆使用\(j\)餐券,因为这样一定最优。
\(O(n^2)\)枚举移动范围 \([a,b]\) ,如何统计这个区间内使用餐券的贡献呢?
将移动范围\([a,b]\)看成一个二维坐标。使用j餐券的范围\([~~L[i][j],R[i][j]~~]\)会在一个矩形内产生贡献。这个矩形的左上角右下角分别为\((L[i][j],j),(j,R[i][j])\)。枚举\([a,b]\)的时候用二维前缀和统计就好了。
\(AtCoder~~Regular~~Contest~~080~~Young~~Maids\)
要求字典序最小,就要使得最后留下的一对最小,在这个前提下,倒数第二对最小......
总之就是,只要能够构造成功,越靠前的位置越小越好。
考虑最后一对有什么约束构造才能成功。
显然,最后一对\((a,b)\)中,\(a\)一定为奇,\(b\)一定大于\(a\)且为偶。
由于\((a,b)\)字典序最小,我们一定要让它留在最后了,考虑要付出什么代价,即倒数第二对\((c,d)\)有什么约束。
从序列中删掉\(a,b\)后,\(c,d\)的奇偶性约束和\(a,b\)一样,而且\(c,d\)必须同时属于三个区间\([1,a-1],[a+1,b-1],[b+1,n]\)中同一个。
到了这里,正解很显然了。
初始区间为\([1,n]\),从中选出最小的合法的\((a,b)\),然后区间分为三个\([1,a-1],[a+1,b-1],[b+1,n]\),再从这三个区间内找出最小的合法的\((c,d)......\)一直做下去。
用单调队列维护区间,从区间中找最小的数对可以用线段树或者倍增\(rmq\),需要分奇偶的查询。
\(codeforces~~gym~~101955~~E~~The~~Kouga~~Ninja~~Scrolls\)
先假设没有修改家族的操作且所有忍者属于不同家族。
问题变成纯粹的维护区间点对最大曼哈顿距离。
这个直接做很难。
科普一个技巧:
定义两点\((x1,y1),(x2,y2)\)的曼哈顿距离\(=|x1-x2|+|y1-y2|\)
定义两点\((x1,y1),(x2,y2)\)的切比雪夫距离\(=\max(|x1-x2|,|y1-y2|)\)
将一个点\((x,y)\)的坐标变为\((x+y,x−y)\)后,原坐标系中的曼哈顿距离\(=\) 新坐标系中的切比雪夫距离
将一个点\((x,y)\)的坐标变为\(((x+y)/2,(x−y)/2)\) 后,原坐标系中的切比雪夫距离\(=\) 新坐标系中的曼哈顿距离。
证明的话,画图或者用上面的式子变换一下均可。
上回说到曼哈顿距离转切比雪夫距离。
这回聊聊转化之后怎么做。
切比雪夫距离
其中
那个绝对值不好处理,我们只能枚举拆开的两种方法,因此这个式子的值有四种。在这四种里面的最大值一定是正确答案。
我们维护区间连续点的
(1).\(x+y\)最大值及其所属集合
(2).与\(x+y\)最大值所属集合不同的最大值及其所属集合
(3).\(x+y\)最小值及其所属集合
(4).与\(x+y\)最小值所属集合不同的最小值及其所属集合
(5).\(x-y\)最大值及其所属集合
(6).与\(x-y\)最大值所属集合不同的最大值及其所属集合
(7).\(x-y\)最小值及其所属集合
(8).与\(x-y\)最小值所属集合不同的最小值机器所属集合
(9).\(-x+y\)最大值及其所属集合
(10).与\(-x+y\)最大值所属集合不同的最大值及其所属集合
(11).\(-x+y\)最小值及其所属集合
(12).与\(-x+y\)最小值所属集合不同的最小值机器所属集合
(13).\(-x-y\)最大值及其所属集合
(14).与\(-x-y\)最大值所属集合不同的最大值及其所属集合
(15).\(-x-y\)最小值及其所属集合
(16).与\(-x-y\)最小值所属集合不同的最小值机器所属集合
\(~~~~\)
修改操作不用多说,修改点坐标和所属集合都是单点修改,叶节点改一下重新向上合并就好了。
查询答案时,要把整个区间的\(16\)个数据项都处理出来,答案就是:
用面向对象的思想写逻辑会顺畅一些,逃。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include<set>
#include<utility>
#include<iomanip>
using namespace std;
int read(){
int xx=0,ff=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){xx=xx*10+ch-'0';ch=getchar();}
return xx*ff;
}
long long READ(){
long long xx=0,ff=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){xx=xx*10+ch-'0';ch=getchar();}
return xx*ff;
}
char one(){
char ch=getchar();
while(ch==' '||ch=='\n')
ch=getchar();
return ch;
}
const int maxn=100010;
const long long INF=1LL<<60;
int N,M,C[maxn];
long long X[maxn],Y[maxn];
struct obj{
long long v[2];
int id[2];
bool mx;
void init(long long x,int y,bool m){
v[0]=x,id[0]=y,mx=m;
id[1]=0;
if(mx)v[1]=-INF;
else v[1]=INF;
}
obj friend operator+(obj A,const obj&B){
if(A.mx){
if(A.v[0]<B.v[0]){
if(A.id[0]!=B.id[0])
A.v[1]=A.v[0],A.id[1]=A.id[0];
A.v[0]=B.v[0],A.id[0]=B.id[0];
}
else if(A.v[1]<B.v[0]&&A.id[0]!=B.id[0])
A.v[1]=B.v[0],A.id[1]=B.id[0];
if(A.v[1]<B.v[1]&&A.id[0]!=B.id[1])
A.v[1]=B.v[1],A.id[1]=B.id[1];
}
else{
if(A.v[0]>B.v[0]){
if(A.id[0]!=B.id[0])
A.v[1]=A.v[0],A.id[1]=A.id[0];
A.v[0]=B.v[0],A.id[0]=B.id[0];
}
else if(A.v[1]>B.v[0]&&A.id[0]!=B.id[0])
A.v[1]=B.v[0],A.id[1]=B.id[0];
if(A.v[1]>B.v[1]&&A.id[0]!=B.id[1])
A.v[1]=B.v[1],A.id[1]=B.id[1];
}
return A;
}
long long friend operator-(const obj&A,const obj&B){
long long ret=0;
if(A.id[0]!=B.id[0])
return A.v[0]-B.v[0];
if(A.id[0]!=B.id[1])
ret=max(ret,A.v[0]-B.v[1]);
if(A.id[1]!=B.id[0])
ret=max(ret,A.v[1]-B.v[0]);
if(A.id[1]!=B.id[1])
ret=max(ret,A.v[1]-B.v[1]);
return ret;
}
void print(){
printf("%d %I64d %d %I64d %d\n",mx,v[0],id[0],v[1],id[1]);
}
};
struct Seg{
obj o[8];
Seg friend operator+(Seg A,const Seg&B){
for(int i=0;i<8;i++)
A.o[i]=A.o[i]+B.o[i];
return A;
}
long long calc(){
long long ret=0;
for(int i=0;i<8;i+=2)
ret=max(ret,o[i]-o[i+1]);
return ret;
}
void print(){
for(int i=0;i<8;i++)
o[i].print();
puts("");
}
}T[maxn*4];
void build(int L,int R,int root){
if(L==R){
T[root].o[0].init(X[L]+Y[L],C[L],1);
T[root].o[1].init(X[L]+Y[L],C[L],0);
T[root].o[2].init(X[L]-Y[L],C[L],1);
T[root].o[3].init(X[L]-Y[L],C[L],0);
T[root].o[4].init(-X[L]+Y[L],C[L],1);
T[root].o[5].init(-X[L]+Y[L],C[L],0);
T[root].o[6].init(-X[L]-Y[L],C[L],1);
T[root].o[7].init(-X[L]-Y[L],C[L],0);
return;
}
int mid=(L+R)/2;
build(L,mid,root*2);
build(mid+1,R,root*2+1);
T[root]=T[root*2]+T[root*2+1];
}
void upd(int L,int R,int root,int k,int x){
if(L==R){
C[L]=x;
T[root].o[0].init(X[L]+Y[L],C[L],1);
T[root].o[1].init(X[L]+Y[L],C[L],0);
T[root].o[2].init(X[L]-Y[L],C[L],1);
T[root].o[3].init(X[L]-Y[L],C[L],0);
T[root].o[4].init(-X[L]+Y[L],C[L],1);
T[root].o[5].init(-X[L]+Y[L],C[L],0);
T[root].o[6].init(-X[L]-Y[L],C[L],1);
T[root].o[7].init(-X[L]-Y[L],C[L],0);
return;
}
int mid=(L+R)/2;
if(k<=mid)
upd(L,mid,root*2,k,x);
else
upd(mid+1,R,root*2+1,k,x);
T[root]=T[root*2]+T[root*2+1];
}
void upd(int L,int R,int root,int k,int x,int y){
if(L==R){
X[L]+=x,Y[L]+=y;
T[root].o[0].init(X[L]+Y[L],C[L],1);
T[root].o[1].init(X[L]+Y[L],C[L],0);
T[root].o[2].init(X[L]-Y[L],C[L],1);
T[root].o[3].init(X[L]-Y[L],C[L],0);
T[root].o[4].init(-X[L]+Y[L],C[L],1);
T[root].o[5].init(-X[L]+Y[L],C[L],0);
T[root].o[6].init(-X[L]-Y[L],C[L],1);
T[root].o[7].init(-X[L]-Y[L],C[L],0);
return;
}
int mid=(L+R)/2;
if(k<=mid)
upd(L,mid,root*2,k,x,y);
else
upd(mid+1,R,root*2+1,k,x,y);
T[root]=T[root*2]+T[root*2+1];
}
Seg query(int L,int R,int root,int x,int y){
if(x<=L&&y>=R)
return T[root];
int mid=(L+R)/2;
if(x<=mid&&y>mid)
return query(L,mid,root*2,x,y)+query(mid+1,R,root*2+1,x,y);
else if(x<=mid)
return query(L,mid,root*2,x,y);
return query(mid+1,R,root*2+1,x,y);
}
void print(int L,int R,int root){
printf("%d %d %d:\n",L,R,root);
T[root].print();
if(L==R)
return;
int mid=(L+R)/2;
print(L,mid,root*2);
print(mid+1,R,root*2+1);
}
int main(){
//freopen("in","r",stdin);
for(int _=read(),cas=1;_;_--,cas++){
printf("Case #%d:\n",cas);
N=read(),M=read();
for(int i=1;i<=N;i++)
X[i]=read(),Y[i]=read(),C[i]=read();
build(1,N,1);
while(M--){
int opt=read(),x,y,k;
if(opt==1){
k=read(),x=read(),y=read();
upd(1,N,1,k,x,y);
}
else if(opt==2){
k=read(),x=read();
upd(1,N,1,k,x);
}
else{
x=read(),y=read();
Seg ans=query(1,N,1,x,y);
printf("%I64d\n",ans.calc());
//ans.print();
}
}
//print(1,N,1);
}
return 0;
}
\(AtCoder~~Grand~~Contest~~012~~Splatter~~Painting\)
发现\(d\leq 10\),可以做突破口。
铺地毯应该还记得吧,倒序处理修改。
一个操作记为\(v,d,c\)。
将\(u\)周围距离\(d\)以内的点染色可以转化为:将\(u\)染色,并把与\(u\)相邻的点\(v\)距离\(d-1\)以内的点染色。
显然这个操作可以做如下递归(边界自己处理):
def opt(v,d,c):
if color[v]==0:
color[v]=c
for u in (u,v):
opt(u,d-1,c)
然后我们给这个递归加上记忆化,做过的操作不需要再做。
def opt(v,d,c):
if vis[v][d]==true:
return
for i in [0,d]:
vis[v][d]=true
if color[v]==0:
color[v]=c
for u in (u,v):
opt(u,d-1,c)
每层递归至少标记\(vis\)的一个元素,复杂度正确。
\(codeforces~~567~~E~~President~~and~~Roads\)
我还没写,不过看起来大家都过了啊。
\(codeforces~~1205~~B~~ Shortest~~Cycle\)
首先统计每一位分别出现了多少次,如果存在某一位出现了\(3\)次或以上,那最小环的大小就为\(3\)。
如果没有大小为\(3\)的环,你会发现这个图一定非常小,因为每一位最多出现两次,也就是说这个图最多只有约\(100\)个点。然后就可以用各种姿势求最小环了。
\(codeforces~~gym~~101257~~F~~ISlands~~II\)
方便起见,最外层套一圈\(0\)。然后遍历这个矩阵,如果相邻两个元素不同,将这两个元素值连边。
结论:对于一个点\(u\),如果他的子节点\(v\)是割点(更自然的:\((u,v)\)是割边),那么\(v\)的子树中的节点一定是\(u\)的\(sub-iland\)。
\(Tarjan\)一遍求割点/边顺便统计答案即可。