ceoi2011 切题记

18th Central European Olympiad in Informatics

Tasks

Similarity(Day 0)(100/100)
Balloons(Day 1)(100/100)
Matching(Day 1)(100/100)
Treasure Hunt(Day 1)(100/100)
Hotel(Day 2)(100/100)
Teams(Day 2)(100/100)
Traffic(Day 2)(100/100)
        不得不说ceoi的题目还是很不错的。。。然而水平太差了QAQ根本切不动

       但是切完辣~~~~

Similarity

       搞笑来的题。随便怎么搞都行

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int n,m,num[26]; char a[2000005],b[2000005];
int main(){
	scanf("%s%s",a+1,b+1);
	int i; n=strlen(a+1); m=strlen(b+1);
	for (i=1; i<=m-n+1; i++) num[b[i]-'a']++;
	long long ans=num[a[1]-'a'];
	for (i=m-n+2; i<=m; i++){
		num[b[i-m+n-1]-'a']--; num[b[i]-'a']++;
		ans+=num[a[i-m+n]-'a'];
	}
	printf("%lld\n",ans);
	return 0;
}

Balloons

        随便列一个方程就能知道r[i]=min{(a[i]-a[j])^2/4/r[j]},然后感性理解吹气球的过程,不妨从当前有用的气球一个个尝试,如果比当前有用的气球最右边一个半径大那么那个气球就没用了。这样一直做下去直到比最右边那个要小,这个时候就是膨胀的极限了。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;

int n,q[N]; double a[N],r[N];
int main(){
	scanf("%d",&n);
	int i,j,tail=0;
	for (i=1; i<=n; i++){
		scanf("%lf%lf",&a[i],&r[i]);
		while (tail){
			j=q[tail]; r[i]=min(r[i],(a[i]-a[j])*(a[i]-a[j])/4/r[j]);
			if (r[i]>=r[j]) tail--; else break;
		}
		printf("%.3f\n",r[i]); q[++tail]=i;
	}
	return 0;
}

Matching

       拓展kmp,,根本想不到啊。

       首先预处理出模板中每一位,求出它之前比它小的最大的那个,以及比它大的最小的那个作为前后驱。然后用kmp一样的方法处理即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000005
using namespace std;

int n,m,cnt,pre[N],nxt[N],ans[N],lf[N],rg[N],a[N],b[N],c[N],q[N],f[N];
int read(){
	int x=0; char cr=getchar();
	while (cr<'0' || cr>'9') cr=getchar();
	while (cr>='0' && cr<='9'){ x=x*10+cr-'0'; cr=getchar(); }
	return x;
}
bool ok(int *a,int x,int y){
	return (!lf[x] || a[y+lf[x]]<a[y]) && (!rg[x] || a[y+rg[x]]>a[y]);
}
int main(){
	m=read(); n=read();
	int i,j;
	for (i=1; i<=m; i++) b[c[i]=read()]=i;
	for (i=1; i<=n; i++) a[i]=read();
	for (i=1; i<=m; i++){ pre[i]=i-1; nxt[i]=i+1; } 
	for (i=m; i; i--){
		j=b[i];
		if (pre[j]) lf[i]=c[pre[j]]-i; if (nxt[j]<=m) rg[i]=c[nxt[j]]-i;
		pre[nxt[j]]=pre[j]; nxt[pre[j]]=nxt[j];
	}
	for (i=2; i<=m; i++){
		f[i]=f[i-1]; while (f[i] && !ok(b,f[i]+1,i)) f[i]=f[f[i]];
		if (ok(b,f[i]+1,i)) f[i]++;
	}
	for (i=1,j=0; i<=n; i++){
		while (j && !ok(a,j+1,i)) j=f[j];
		if (ok(a,j+1,i)) j++;
		if (j==m){ ans[++cnt]=i-m+1; j=f[j]; }
	}
	printf("%d\n",cnt);
	for (i=1; i<=cnt; i++) printf("%d%c",ans[i],(i<cnt)?' ':'\n');
	return 0;
}

Treasure Hunt

         由于要兹瓷动态加块,而且显然要求lca,那就不能用树剖或者rmq了。。考虑倍增,令fa[x][i]表示x这个块向上走2^i步到达的块,dad[x][i]表示x这个块向上走2^i步走到的点。这样就可以很方便地处理了。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 400005
using namespace std;

int cnt,bin[36],fa[N][19],dad[N][19],d[N],dep[N];
struct node{ int l,r; }a[N];
void init(){
	a[cnt=1].l=a[1].r=1; d[cnt]=dep[cnt]=1;
	int i;
	bin[0]=1; for (i=1; i<=19; i++) bin[i]=bin[i-1]<<1;
}
int find(int x){
	int l=1,r=cnt,mid;
	while (l<r){
		mid=(l+r)>>1;
		if (x<=a[mid].r) r=mid; else l=mid+1;
	}
	return l;
}
void path(int x,int len){
	cnt++;
	a[cnt].l=a[cnt-1].r+1; a[cnt].r=a[cnt-1].r+len;
	fa[cnt][0]=find(x); dad[cnt][0]=x;
	d[cnt]=d[fa[cnt][0]]+x-a[fa[cnt][0]].l+1;
	dep[cnt]=dep[fa[cnt][0]]+1;
	int i;
	for (i=1; i<=18; i++){
		fa[cnt][i]=fa[fa[cnt][i-1]][i-1]; dad[cnt][i]=dad[fa[cnt][i-1]][i-1];
	}
}
int dig(int x,int y){
	int u=find(x),v=find(y),dx=d[u]+x-a[u].l,dy=d[v]+y-a[v].l,p=x,q=y;
	if (dep[u]<dep[v]){ swap(u,v); swap(x,y); }
	int tmp=dep[u]-dep[v],i;
	for (i=0; i<=18; i++) if (tmp&bin[i]){
		x=dad[u][i]; u=fa[u][i];
	}
	for (i=18; i>=0; i--) if (fa[u][i]!=fa[v][i]){
		x=dad[u][i]; y=dad[v][i];
		u=fa[u][i]; v=fa[v][i];
	}
	if (u!=v){
		x=dad[u][0]; y=dad[v][0];
		u=fa[u][0]; v=fa[v][0];
	}
	tmp=dx+dy-(d[u]+min(x,y)-a[u].l<<1);	
	x=p; y=q;
	if (dx<dy){ swap(x,y); swap(dx,dy); tmp=tmp+1>>1; }
	else tmp>>=1;
	tmp=dx-tmp;
	u=find(x);
	for (i=18; i>=0; i--)
		if (d[fa[u][i]]+dad[u][i]-a[fa[u][i]].l>=tmp) u=fa[u][i];
	return a[u].l+tmp-d[u];
}

Hotel

       最朴素的贪心。。然而是对的。

       考虑将订单按价格从高到低排序,然后按顺序安排最便宜房间并求出收益。然后把最大的o个收益加起来就好了。这样显然是对的,因为如果房间有冲突,显然会满足价格最高的;另一方面如果2个订单对2个房间可以任意分配,那么将价格高的订单分配便宜的房间或是贵的并没有影响。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
using namespace std;

int n,m,cnt,ans[N],fa[N];
struct node{ int x,y; }a[N],b[N];
int read(){
	int x=0; char cr=getchar();
	while (cr<'0' || cr>'9') cr=getchar();
	while (cr>='0' && cr<='9'){ x=x*10+cr-'0'; cr=getchar(); }
	return x;
}
bool cmpx(const node &u,const node &v){ return u.x>v.x; }
bool cmpy(const node &u,const node &v){ return u.y<v.y || u.y==v.y && u.x<v.x; }
int getfa(int x){ return (x==fa[x])?x:fa[x]=getfa(fa[x]); }
int find(int x){
	int l=1,r=n+1,mid;
	while (l<r){
		mid=(l+r)>>1;
		if (a[getfa(mid)].y<x) l=mid+1; else r=mid;
	}
	return getfa(l);
}
int main(){
	n=read(); m=read(); cnt=read();
	int i,j;
	for (i=1; i<=n; i++){
		a[i].x=read(); a[i].y=read();
	}
	for (i=1; i<=m; i++){
		b[i].x=read(); b[i].y=read();
	}
	sort(a+1,a+n+1,cmpy); sort(b+1,b+m+1,cmpx);
	for (i=1; i<=n+1; i++) fa[i]=i; a[n+1].y=1000000000;
	for (i=1; i<=m; i++){
		j=find(b[i].y);
		if (j<=n && a[j].x<b[i].x){
			ans[i]=b[i].x-a[j].x; fa[j]=j+1;
		}
	}
	sort(ans+1,ans+m+1);
	long long sum=0;
	for (i=m-cnt+1; i<=m; i++) sum+=ans[i];
	printf("%lld\n",sum);
	return 0;
}

Teams

       O(N)的思路好神啊。。波兰人怎么这么熟练啊根本想不到(只会带log的sb做法)

       首先按照a[i]降序排序,然后令f[i]表示前i个最多分成几组。显然f[i-1]<=f[i]<=f[i-1]+1,考虑f[i]=f[i-1]+1的条件,一定是存在f[j]==f[i-1]且j+a[j+1]==i,这样所有i向i+a[i+1]连边,最多N个转移。

        然后考虑最大组人数最小,不妨设为g[i]。如果f[i]=f[i-1],显然可以把i塞到前面随便什么地方,如果已经塞满了那么g[i]=g[i-1]+1;否则g[i]=g[i-1]。如果f[i]=f[i-1]+1,那么显然g[i]=max(g[x],i-x),其中f[x]==f[i-1]且x+a[x+1]==i。

        然后打个表示表示如果f[i]=f[i-1]+1那么i是从那个x转移来的然后把x+1~i的都编成一组。剩下的按照a[i]从大到小塞入从大到小的组中即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000005
using namespace std;

int n,cnt,fst[N],nxt[N],a[N],b[N],f[N],g[N],p[N],sz[N],ans[N]; bool bo[N];
int read(){
	int x=0; char cr=getchar();
	while (cr<'0' || cr>'9') cr=getchar();
	while (cr>='0' && cr<='9'){ x=x*10+cr-'0'; cr=getchar(); }
	return x;
}
void add(int x,int y){ nxt[y]=fst[x]; fst[x]=y; }
int main(){
	n=read();
	int i,x;
	for (i=1; i<=n; i++){
		x=read(); add(x,i);
	}
	for (i=n; i; i--)
		for (x=fst[i]; x; x=nxt[x]){ a[++cnt]=i; b[cnt]=x; }
	memset(fst,0,sizeof(fst)); memset(p,-1,sizeof(p));
	f[a[1]]=1; g[a[1]]=a[1]; p[a[1]]=0;
	add(a[1]+a[a[1]+1],a[1]);
	for (i=a[1]+1; i<=n; i++){
		f[i]=f[i-1]; g[i]=((long long)f[i-1]*g[i-1]==i-1)?g[i-1]+1:g[i-1];
		for (x=fst[i]; x; x=nxt[x])
			if (f[x]+1>f[i]){
				f[i]=f[x]+1; g[i]=max(g[x],i-x); p[i]=x;
			} else if (f[x]+1==f[i] && max(g[x],i-x)<g[i]){
				g[i]=max(g[x],i-x); p[i]=x;
			}
		add(i+a[i+1],i);
	}
	printf("%d\n",f[n]);
	memset(fst,0,sizeof(fst)); memset(bo,1,sizeof(bo));
	for (i=n,cnt=0; i>=a[1]; i=(p[i]>=0)?p[i]:i-1)
		if (p[i]>=0){
			cnt++;
			for (x=i; x>p[i]; x--){ add(cnt,b[x]); sz[cnt]++; bo[x]=0; }
		}
	for (i=1,x=cnt; i<=n; i++) if (bo[i]){
		while (sz[x]==g[n]) x--;
		add(x,b[i]); sz[x]++;
	}
	for (i=1; i<=cnt; i++){
		printf("%d",sz[i]);
		for (x=fst[i]; x; x=nxt[x]) printf(" %d",x); puts("");
	}
	return 0;
}

Traffic

       题目中的关键在于任意两条路没有交叉。

       这说明一个点能到达的右侧的点一定是连续的(永远到不了的除外)。

       那么tarjan缩点以后dp出每个点能到达的最小和最大的编号即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300005
#define M 1800005
using namespace std;

int n,m,ta,tb,cnt,pt,tot,dfsclk,fst[N],pnt[M],nxt[M],tp,stk[N];
int mx[N],mn[N],scc[N],q[N],id[N],pos[N],low[N],a[N],b[N]; bool vis[N];
struct node{ int x,y,z; }e[M>>1];
int read(){
	int x=0; char cr=getchar();
	while (cr<'0' || cr>'9') cr=getchar();
	while (cr>='0' && cr<='9'){ x=x*10+cr-'0'; cr=getchar(); }
	return x;
}
void add(int x,int y){
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x){
	if (vis[x]) return; vis[x]=1;
	int i;
	for (i=fst[x]; i; i=nxt[i]) dfs(pnt[i]);
}
void tarjan(int x){
	int i,y; pos[x]=low[x]=++dfsclk; stk[++tp]=x;
	for (i=fst[x]; i; i=nxt[i]){
		y=pnt[i];
		if (!pos[y]){
			tarjan(y); low[x]=min(low[x],low[y]);
		} else if (!scc[y]) low[x]=min(low[x],pos[y]);
	}
	if (low[x]==pos[x]){
		cnt++; mx[cnt]=-1000000000; mn[cnt]=1000000000;
		while (1){
			scc[stk[tp]]=cnt;
			if (y=id[stk[tp]]){
				mx[cnt]=max(mx[cnt],y); mn[cnt]=min(mn[cnt],y);
			}
			if (stk[tp--]==x) break;
		}
	}
}
void dp(int x){
	if (vis[x]) return; vis[x]=1;
	int i,y;
	for (i=fst[x]; i; i=nxt[i]){
		y=pnt[i];
		dp(y); mx[x]=max(mx[x],mx[y]); mn[x]=min(mn[x],mn[y]);
	}
}
bool cmp(const int &x,const int &y){ return b[x]>b[y]; }
int main(){
	n=read(); m=read(); ta=read(); tb=read();
	int i,x,y;
	for (i=1; i<=n; i++){
		a[i]=read(); b[i]=read();
	}
	for (i=1; i<=m; i++){
		e[i].x=read(); e[i].y=read(); e[i].z=read();
		add(e[i].x,e[i].y);
		if (e[i].z==2) add(e[i].y,e[i].x);
	}
	for (i=1; i<=n; i++) if (!a[i]) dfs(i);
	for (i=1; i<=n; i++) if (a[i]==ta && vis[i]) q[++pt]=i;
	sort(q+1,q+pt+1,cmp);
	for (i=1; i<=pt; i++) id[q[i]]=i;
	for (i=1; i<=n; i++) if (!pos[i]) tarjan(i);
	tot=0; memset(fst,0,sizeof(fst));
	for (i=1; i<=m; i++){
		x=scc[e[i].x]; y=scc[e[i].y];
		if (x!=y) add(x,y);
	}
	memset(vis,0,sizeof(vis));
	for (i=1; i<=cnt; i++) dp(i);
	pt=0;
	for (i=1; i<=n; i++) if (!a[i]) q[++pt]=i; 
	sort(q+1,q+pt+1,cmp);
	for (i=1; i<=pt; i++) printf("%d\n",max(0,mx[scc[q[i]]]-mn[scc[q[i]]]+1));
	return 0;
}

by lych
2016.8.3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值