杂题

\(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)\) 后,原坐标系中的切比雪夫距离\(=\) 新坐标系中的曼哈顿距离。

证明的话,画图或者用上面的式子变换一下均可。

上回说到曼哈顿距离转切比雪夫距离。

这回聊聊转化之后怎么做。

切比雪夫距离

\[\max{(|x_1'-x_2'|,|y_1'-y_2'|)} \]

其中

\[(x_1',y_1')=(x_1+y_1,x_1-y_1) \]

\[(x_2',y_2')=(x_2+y_2,x_2-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\)个数据项都处理出来,答案就是:

\[\max\{(1)-(2),(3)-(4),(5)-(6),(7)-(8),(9)-(10),(11)-(12),(13)-(14),(15)-(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\)一遍求割点/边顺便统计答案即可。

posted @ 2019-11-02 13:13  咸鱼lzh  阅读(209)  评论(0编辑  收藏  举报