20161026的考试】KMP,二维LIS,DAG删一个点求最长路径最小值(BZOJ 3832)

80 篇文章 0 订阅
20 篇文章 0 订阅

总结:…………我忘了,反正当时T2是写了两个版本对拍,一个是线段树一个是STL乱搞,然后……交了STL版…………WA,线段树……A………………总之还是觉得有时间的话自己写写比较靠谱【23333


T1:

题意:给一个字符串,求有多少个子串可以和这个字符串的前缀匹配(样例:input【aaba】,output【6】,解释:六个子串的下标分别是[1,1],[1,2],[1,3],[1,4],[2,2],[4,4]

思路:

        反正一看就可以用KMP搞,想想似乎不太会写KMP,不管了现场推吧哼=、=。

        然后……思考思考,似乎ans就是fail树的所有节点深度之和啊

代码:

#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
char s[MAXN];
int fail[MAXN];

long long f[MAXN];
long long ans=0;

void KMP (){
	int lth=strlen(s+1);
	f[1]=1;
	for(int i=1,j;i<=lth;++i){
		j=fail[i];
		while((s[i+1]^s[j+1])&&j) j=fail[j];
		if(s[i+1]==s[j+1])	fail[i+1]=j+1,f[i+1]=f[j+1]+1;
		else	fail[i+1]=0,f[i+1]=1;
		ans+=f[i];
	}
//	for(int i=1;i<=lth;++i)	printf("  %c",s[i]);
//	puts("");
//	for(int i=1;i<=lth;++i)	printf("%3d",fail[i]);
//	puts("");
}

int main(){
	freopen("gene.in","r",stdin);
	freopen("gene.out","w",stdout);
	
	scanf("%s",s+1);
	KMP();
	
	printf("%lld",ans);
	return 0;
}

T2:

题意:给定两个向量(夹角始终小于180°)和若干点的坐标,每个点可以转移到这两个向量夹角之间(包含边界)的地方,求从一个点(任选)出发,最多可以经过多少个点。

思路:

        想一想……好像直接搞的话会爆精度还需要特判…………根据高一数学知识,可以用两个线性无关的向量表示二维平面内的任一点(点坐标显然可以当成向量x),于是直接改坐标,推一推公式用这两个向量当x轴和y轴,于是转化成了每个点只能走向它的右上(包含边界)方的点。。。

        naive的flaze并没有一眼看出可以直接LIS搞……于是扫了一遍并用线段树更新答案…………写完发现这不是lis吗……裸的那种2333,于是手贱又用lower_bound随便水水地敲了一遍lis…………然后……嗯对拍发现没有问题诶w,交交交……嗯交短的好了,然而……嗯……WA了【并不想debug2333】

代码:

#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;	int n;
struct vec{
	long long x,y;
	vec():x(0),y(0){}	
	vec(long long x,long long y):x(x),y(y){}
	bool operator < (const vec ano) const{
		return x==ano.x? y<ano.y:x<ano.x;
	}
}V1,V2,rec[MAXN];

inline void get_xy(long long &x,long long &y){
	long long a = x*V2.y - V2.x*y;
	long long b = V1.x*y - x*V1.y;
	x=a,y=b;
}

long long base_y[MAXN],cnt_y;
int dt[MAXN<<2],tag[MAXN<<2];

inline void pushdown(int now,int l,int r){
	if(l==r){
		dt[now]=max(dt[now],tag[now]);
		tag[now]=0;
		return ;	
	}
	tag[now<<1]=max(tag[now<<1],tag[now]);
	tag[now<<1|1]=max(tag[now<<1|1],tag[now]);
	int  mid=(l+r)>>1;
	if(l==mid)	dt[now<<1]=max(dt[now<<1],tag[now]);
	if(mid+1==r)	dt[now<<1|1]=max(dt[now<<1|1],tag[now]);
	tag[now]=0;
}

void modify(int now,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		tag[now]=max(tag[now],v);
		pushdown(now,l,r);
		return ;
	}
	int mid=(l+r)>>1;
	if(tag[now])	pushdown(now,l,r);
	if(L<=mid)	modify(now<<1,l,mid,L,R,v);
	if(mid<R)	modify(now<<1|1,mid+1,r,L,R,v);
}

int inqry(int now,int l,int r,int pos){
	if(l==r)	return dt[now];
	int mid=(l+r)>>1;
	if(tag[now])	pushdown(now,l,r);
	if(pos<=mid)	return inqry(now<<1,l,mid,pos);
	else	return inqry(now<<1|1,mid+1,r,pos);
}

int ans=0;

long long read_x,read_y;
int main(){
	freopen("sheild.in","r",stdin);
//	freopen("sheild.out","w",stdout);
	
	scanf("%d",&n);
	scanf("%lld%lld",&read_x,&read_y);	V1=vec(read_x,read_y);
	scanf("%lld%lld",&read_x,&read_y);	V2=vec(read_x,read_y);
	
	for(int i=1;i<=n;++i){
		scanf("%lld%lld",&read_x,&read_y);
		get_xy(read_x,read_y);
		base_y[i]=read_y;
		rec[i]=vec(read_x,read_y);
	}
	
	sort(base_y+1,base_y+n+1);
	cnt_y=unique(base_y+1,base_y+n+1)-base_y-1;

//	for(int i=1;i<=cnt_y;++i)	printf("y = %d\n",base_y[i]);
	
	V1=vec(1,0),V2=vec(0,1);
	sort(rec+1,rec+n+1);
	
//	for(int i=1;i<=n;++i)	printf("( %lld , %lld )\n",rec[i].x,rec[i].y);
	
	for(int i=n;i;--i){
		int pos_y=lower_bound(base_y+1,base_y+cnt_y+1,rec[i].y)-base_y;
		int dd=inqry(1,1,n,pos_y);
		ans=max(ans,dd+1);
		modify(1,1,n,1,pos_y,dd+1);
	}
	
	printf("%d",ans);
	return 0;
}

T3

题意:(BZOJ 3832)给一个DAG(不一定联通),求删去一个节点后的最长路最短是多少

思路:

        GG

        安利popoqqq和heheda的题解……beiyu和claris的也都很稳……

        思路超神啊%%%%%%%

        好吧不扯了,考虑显然先把DAG转化成拓扑序,然后……然后?没了啊

        显然最naive的思路就是分别删去每个点,然后算最长路,如果每次算最长路都是暴力的话显然会T,于是考虑优化一下

        删去每个点的时候,只会影响会经过它的最长路,也就是只会影响经过【以它为端点的边】的最长路,而且是使它从路径长度集合中删去。

        因为会出现删点后图不连通【或者它自己本来就不连通】的情况,所以要加一个源点,向所有节点连边,再加一个汇点,从所有节点向它连边【

        维护每条边所在的最长路的长度,显然可以用【它的起点到源点】的最长路长度加上【它的终点到汇点】的最长路长度来表示。

        然后在枚举删去另外的点时,把之前删去的长度加进集合里就好

        再想想,就相当于是用一个数据结构,支持 加入,删除,查询最大值=、=

        multiset,priority_queue,堆,平衡树,值域线段树……都很靠谱

        再稍微搞一下细节……

        嗯学会了用priority_queue写支持删除的堆的姿势【对不起我就是菜】


        什么……你问我为什么要写读入优化?【艹榜啊x

代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define MAXN 500005
#define MAXM 1000005
#define INF 0x3f3f3f3f
using namespace std;	int n,m;
int S=0,T=MAXN-1;
//==================================================
struct io_t{
	char op[1<<25];
	char* s;
	int a[24];
	char ed[1];
	char* t;
	io_t():s(op),t(ed){
		fread(s,1,
		sizeof op,stdin);
	}
	~io_t(){
		fwrite(ed,1,
		t-ed,stdout);
	}
	void scan(char* q){
		while(*s<48)
			++s;
		while(*s>32)
			*q++=*s++;
		*q=0;
	}
	void print(const
	char* q){
		while(*q)
			*t++=*q++;
		*t++=10;
	}
	int scan(){
		static int v,j;
		v=0,j=1;
		while(*s<48)
			j=*s++^45;
		do
			v=v*10+*s++-48;
		while(*s>32);
		return j?v:-v;
	}
	template< class T >
	void print( register T v){
		static int* q=a;
		if(!v)*t++=48;
		else{
			if(v<0)
				*t++=45,
				v*=-1;
			while(v)
				*q++=v%10+48,
				v/=10;
			while(q!=a)
				*t++=*--q;
		}
		*t++=10;
	}
}io;
#define read io.scan
#define print io.print
//==========================================================

vector <int> link1[MAXN],link2[MAXN];
int f[MAXN],g[MAXN];

int ind[MAXN],oud[MAXN];

int que[MAXN],head,tail;
inline void topu_1(int now){
	head=tail=0;
	f[now]=0;
	que[tail++]=now;
	while(head^tail){
		now=que[head++];
		for(register int i=0;i<(int)link1[now].size();++i){
			int aim=link1[now][i];
			--ind[aim];
			if(!ind[aim]){
				que[tail++]=aim;
			}
		}
	}
	for(register int i=0;i<tail;++i){
		int x=que[i];
		for(register int j=0;j<(int)link1[x].size();++j)
			f[link1[x][j]] = max(f[link1[x][j]],f[x]+1);
	}
	for(register int i=tail-1;~i;--i){
		int x=que[i];
		for(register int j=0;j<(int)link2[x].size();++j)
			g[link2[x][j]] = max(g[link2[x][j]],g[x]+1);
	}
}

struct heap{
	priority_queue<int> p1,p2;
	void add(int x){p1.push(x);}
	void del(int x){
		p2.push(x);
		while((!p1.empty())&&(!p2.empty())&&(p1.top()==p2.top()))
			p1.pop(),p2.pop();
	}
	int max(){return p1.top();}
}pq;

int ans=INF-1,id=INF;

inline void work(){
	--tail;
	for(register int i=0;i<tail;++i){
		int x=que[i];
		
		int kk;
		
		
		for(register int j=0;j<(int)link2[x].size();++j)
			pq.del(kk=f[link2[x][j]]+g[x]);
		
		int tmp=pq.max();
		if(ans>tmp)	ans=tmp,id=x;
		else	
			if(ans==tmp&&id>x)	id=x;
		
		for(register int j=0;j<(int)link1[x].size();++j)
			pq.add(kk=f[x]+g[link1[x][j]]);
	}
	printf("%d %d",id,ans-1);
}

int read_x,read_y;
int main(){
	n=read(),m=read();
	for(register int i=1;i<=m;++i){
		read_x=read(),read_y=read();
		link1[read_x].push_back(read_y);
		link2[read_y].push_back(read_x);
		++ind[read_y],++oud[read_x];
	}
	
	pq.p1.push(INF),pq.p2.push(INF);
	
	for(int i=1;i<=n;++i){
		link1[S].push_back(i),link2[i].push_back(S),++ind[i],++oud[S];
		link1[i].push_back(T),link2[T].push_back(i),++ind[T],++oud[i];
	}
	
	topu_1(S);
	
	work();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值