usaco 2017 2月月赛

usaco月赛题很健脑啊。。一直坚持没看题解自己做,感觉收获很大,希望自己能坚持下去。。

gold 1

给出一个n*n(输入)表格图,每个格子上都有一个权值(输入),现在从左上角走到右下角,每走一步需要花费t(输入)的代价,同时,每走三步需要花费当前所在格子权值的代价,求最小代价。

用spfa跑,状态用四位数压缩,表示为dis[now][step],其中step表示当前走的步数%3的值,now表示当前所在格子,数组表示当前所用最小花费。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
#include<queue>
using namespace std;
int get(int x,int y)
{
	return x*100+y;
}
bool vis[10050][3];
int dis[10050][3];
int score[10050];
struct state
{
	int w,s;
};
int n,t;
queue<state> q;
void spfa()
{
	for(int i=0;i<10000;i++)
		dis[i][0]=dis[i][1]=dis[i][2]=0x3f3f3f3f;
	dis[0][0]=0;
	q.push((state){0,0});
	vis[0][0]=1;
	while(!q.empty())
	{
		state now=q.front();
		vis[now.w][now.s]=0;
		q.pop();
		if(now.w/100!=0)
		{
			if(dis[now.w-100][1]>dis[now.w][0]+t)
			{
				dis[now.w-100][1]=dis[now.w][0]+t;
				if(!vis[now.w-100][1])
					vis[now.w-100][1]=1,q.push((state){now.w-100,1});
			}
			if(dis[now.w-100][2]>dis[now.w][1]+t)
			{
				dis[now.w-100][2]=dis[now.w][1]+t;
				if(!vis[now.w-100][2])
					vis[now.w-100][2]=1,q.push((state){now.w-100,2});
			}
			if(dis[now.w-100][0]>dis[now.w][2]+t+score[now.w-100])
			{
				dis[now.w-100][0]=dis[now.w][2]+t+score[now.w-100];
				if(!vis[now.w-100][0])
					vis[now.w-100][0]=1,q.push((state){now.w-100,0});
			}
		}
		if(now.w/100!=n-1)
		{
			if(dis[now.w+100][1]>dis[now.w][0]+t)
			{
				dis[now.w+100][1]=dis[now.w][0]+t;
				if(!vis[now.w+100][1])
					vis[now.w+100][1]=1,q.push((state){now.w+100,1});
			}
			if(dis[now.w+100][2]>dis[now.w][1]+t)
			{
				dis[now.w+100][2]=dis[now.w][1]+t;
				if(!vis[now.w+100][2])
					vis[now.w+100][2]=1,q.push((state){now.w+100,2});
			}
			if(dis[now.w+100][0]>dis[now.w][2]+t+score[now.w+100])
			{
				dis[now.w+100][0]=dis[now.w][2]+t+score[now.w+100];
				if(!vis[now.w+100][0])
					vis[now.w+100][0]=1,q.push((state){now.w+100,0});
			}
		}
		if(now.w%100!=0)
		{
			if(dis[now.w-1][1]>dis[now.w][0]+t)
			{
				dis[now.w-1][1]=dis[now.w][0]+t;
				if(!vis[now.w-1][1])
					vis[now.w-1][1]=1,q.push((state){now.w-1,1});
			}
			if(dis[now.w-1][2]>dis[now.w][1]+t)
			{
				dis[now.w-1][2]=dis[now.w][1]+t;
				if(!vis[now.w-1][2])
					vis[now.w-1][2]=1,q.push((state){now.w-1,2});
			}
			if(dis[now.w-1][0]>dis[now.w][2]+t+score[now.w-1])
			{
				dis[now.w-1][0]=dis[now.w][2]+t+score[now.w-1];
				if(!vis[now.w-1][0])
					vis[now.w-1][0]=1,q.push((state){now.w-1,0});
			}
		}
		if(now.w%100!=n-1)
		{
			if(dis[now.w+1][1]>dis[now.w][0]+t)
			{
				dis[now.w+1][1]=dis[now.w][0]+t;
				if(!vis[now.w+1][1])
					vis[now.w+1][1]=1,q.push((state){now.w+1,1});
			}
			if(dis[now.w+1][2]>dis[now.w][1]+t)
			{
				dis[now.w+1][2]=dis[now.w][1]+t;
				if(!vis[now.w+1][2])
					vis[now.w+1][2]=1,q.push((state){now.w+1,2});
			}
			if(dis[now.w+1][0]>dis[now.w][2]+t+score[now.w+1])
			{
				dis[now.w+1][0]=dis[now.w][2]+t+score[now.w+1];
				if(!vis[now.w+1][0])
					vis[now.w+1][0]=1,q.push((state){now.w+1,0});
			}
		}
	}
	cout<<min(min(dis[get(n-1,n-1)][0],dis[get(n-1,n-1)][1]),dis[get(n-1,n-1)][2]);
}
int x;
int main()
{
	freopen("visitfj.in","r",stdin);
	freopen("visitfj.out","w",stdout);
	cin>>n>>t;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>x,score[get(i,j)]=x;
	spfa();
}

gold 2

路两旁各有N只牛,N个牛棚,一只牛可以走到所有与自己编号值相差<=4的牛棚,问使牛棚间不相交的最大匹配数是多少

dp[i][j]表示第i只牛走到第j个牛棚且1~i的匹配都合法时最大匹配数,n^2dp即可

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
using namespace std;
int a[1005],b[1005];
vector<int> tu[1005];
int n,dp[1005][1005];
int main()
{
	freopen("nocross.in","r",stdin);
	freopen("nocross.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		cin>>b[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(abs(a[i]-b[j])<=4)
				tu[i].push_back(j);
	for(int i=1;i<=n;i++)
		dp[0][i]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			dp[i][j]=max(dp[i-1][j],dp[i][j]);
			for(int k=0;k<tu[i].size();k++)
			{
				int f=tu[i][k];
				if(f>j)
					dp[i][f]=max(dp[i][f],dp[i-1][j]+1);
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,dp[n][i]);
	cout<<ans;
}

gold 3

(相当于)给出n条端点不重合的线段,问有多少条线段两两相交(一个包含另一个不算),注意这里n<=100000!

这个题我还是想了好一会的(毕竟人太瓷了),先将所有起点全部插入一个树状数组,再把所有终点全部插入另一个树状数组,然后按照后端点排序,对于每个线段,ans+=起点树状数组中值小于该线段起点的个数-终点树状数组中值小于该线段起点的个数,然后把这个线段的起点,终点在各自的树状数组中删去,再处理下一条线段。

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
int a[100050],b[100050],n;
int ans=0,q[100050],z[100050];
struct line
{
	int q,z;
}per[100050];
int in[200050],in2[200050]; 
int cmp(line a,line b)
{
	return a.z==b.z? (a.q>b.q):(a.z>b.z);
}
int lowbit(int x)
{
	return (x&(-x));
} 
void plu(int pos,int num)
{
	while(pos<=2*n)
	{
		in[pos]+=num;
		pos+=lowbit(pos);
	}
} 
int sum(int end)
{
	int sum=0;
	while(end>0)
	{
		sum+=in[end];
		end-=lowbit(end);
	}
	return sum;
}
void plus2(int pos,int num)
{
	while(pos<=2*n)
	{
		in2[pos]+=num;
		pos+=lowbit(pos);
	}
} 
int sum2(int end)
{
	int sum=0;
	while(end>0)
	{
		sum+=in2[end];
		end-=lowbit(end);
	}
	return sum;
}
int main()
{
	freopen("circlecross.in","r",stdin);
	freopen("circlecross.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=2*n;i++)
	{
		scanf("%d",&a[i]);
		if(!per[a[i]].q)
			per[a[i]].q=i,plu(i,1);
		else
			per[a[i]].z=i,plus2(i,1);
	}
	sort(per+1,per+n+1,cmp);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		ans+=sum(per[i].q-1)-sum2(per[i].q-1);
	//	cout<<sum(per[i].q-1)<<" "<<sum2(per[i].q-1)<<" "<<per[i].q<<" "<<per[i].z<<endl;
		plu(per[i].q,-1);
		plus2(per[i].z,-1);
	}
	cout<<ans<<endl;
}

platinum 1

还是路两边各有N只牛,N个牛棚,这次牛只回属于自己的牛棚,但约翰很厉害,他可以将牛棚中最后任意k间牛棚不变顺序地提到最前面,如45123可以变成12345,当然也可以是34512,同时,他也可以选择用同样的办法对待这群奶牛,现在请求出修改后最小路径交叉对 数。

如果枚举k,那么肯定会爆,我们可以先处理出第i个奶牛所在牛棚位置,那么这个序列的最小逆序对数一定就是答案之一,注意当a[n]移到a[1]时,逆序对数会增加(a[n]-1)个,也会减少(n-a[n])个,所以可以通过dp来获得修改后最大减少值。如果答案不是这个,那就是第一步处理改为第i间牛棚所在奶牛位置,再做上述操作。

为什么会分两种情况呢?是因为约翰即可选择改左边牛,也可选择改右边牛棚,所以都得顾及。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
using namespace std;
long long a1[500050],b1[500050],a[500050],b[500050];
long long minn=0,zhuan,g[500050],minans=0x7f7f7f7f7f;
long long tmp[500050],ans=0;
int n;
void merge(int l,int m,int r)
{
	int i=l,j=m+1,k=l;
	while(i<=m&&j<=r)
	{
		if(a[i]>a[j])
		{
			tmp[k++]=a[j++];
			ans+=m-i+1;
		}
		else
			tmp[k++]=a[i++];
	}
	while(i<=m)
		tmp[k++]=a[i++];
	while(j<=r)
		tmp[k++]=a[j++];
	for(int i=l;i<=r;i++)
		a[i]=tmp[i];
}
void mergesort(int l,int r)
{
	if(l<r)
	{
		int m=(l+r)/2;
		mergesort(l,m);
		mergesort(m+1,r);
		merge(l,m,r);
	}
}
int main()
{
	freopen("mincross.in","r",stdin);
	freopen("mincross.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a1[i]),a[a1[i]]=i;
	for(int i=1;i<=n;i++)
		scanf("%lld",&b1[i]),b[i]=a[b1[i]];
	 for(int i=1;i<=n;i++)
	{
	 	g[i]=g[i-1]+n-2*b[i]+1;
		minn=min(minn,g[i]);
	}
	for(int i=1;i<=n;i++)
		a[i]=b[i];
//	for(int i=1;i<=n;i++)
//		if(g[i]<minn)
//			zhuan=i,minn=g[i];
//	for(int i=1;i<=n-zhuan;i++)
//		a[i]=b[i+zhuan];
//	for(int i=n-zhuan+1;i<=n;i++)
//		a[i]=b[i+zhuan-n];
	mergesort(1,n);
	minans=min(ans+minn,minans);
	for(int i=1;i<=n;i++)
		a[b1[i]]=i;
	for(int i=1;i<=n;i++)
		b[i]=a[a1[i]];
	minn=0;
	for(int i=1;i<=n;i++)
	{
	 	g[i]=g[i-1]+n-2*b[i]+1;
		minn=min(minn,g[i]);
	}
	for(int i=1;i<=n;i++)
		a[i]=b[i];
	ans=0;
	mergesort(1,n);
	minans=min(ans+minn,minans);
	cout<<minans<<endl;
}

platinum 2

问题同gold2,但n的范围已经变成了100000,这就需要我们去优化。

开始想了好一会都没想到怎么优化,不知道为什么在看机房别人打游戏时突然想到了(手动滑稽):因为在枚举i以前的状态时,我们只关心这个状态的最后一个位置在哪,以及这个的匹配数,而且这个“”最后一个位置“只要比当前小就好了,我们并不关心他们的,那为什么不用一棵线段树,维护区间最大值来优化转移呢?

#include<iostream>
#include<string.h>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#define mid (l+r)/2
#define lson rt*2
#define rson rt*2+1
using namespace std;
int a[100050],b[100050],loc[100050],n,all[100050][10],f[100050][10];
int dp[100050][15];
struct node
{
	int maxx,flag;
}tree[400050];
void pushup(int rt)
{
	tree[rt].maxx=max(tree[rson].maxx,tree[lson].maxx);
}
void pushdown(int rt)
{
	if(tree[rt].flag)
	{
		tree[rson].flag=tree[lson].flag=tree[rt].flag;
		tree[rt].flag=0;
		tree[lson].maxx=tree[lson].flag;
		tree[rson].maxx=tree[rson].flag;
	}
}
void ins(int rt,int l,int r,int L,int R,int v)
{
	if(L>r||l>R)
		return ;
	if(L<=l&&r<=R)
	{
		tree[rt].maxx=v,tree[rt].flag=v;
		return ;
	}
	pushdown(rt);
	ins(lson,l,mid,L,R,v);
	ins(rson,mid+1,r,L,R,v);
	pushup(rt);
}
int query(int rt,int l,int r,int L,int R)
{
	if(L>r||l>R)
		return 0;
	if(L<=l&&r<=R)
		return tree[rt].maxx;
	pushdown(rt);
	return max(query(lson,l,mid,L,R),query(rson,mid+1,r,L,R));
}
int main()
{
	freopen("nocross.in","r",stdin);
	freopen("nocross.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&b[i]),loc[b[i]]=i;
	for(int i=1;i<=n;i++)
	{
		if(a[i]>=5)
		all[i][1]=loc[a[i]-4];
		if(a[i]>=4)
		all[i][2]=loc[a[i]-3];
		if(a[i]>=3)
		all[i][3]=loc[a[i]-2];
		if(a[i]>=2)
		all[i][4]=loc[a[i]-1];
		all[i][5]=loc[a[i]];
		if(a[i]<=n-4)
		all[i][6]=loc[a[i]+4];
		if(a[i]<=n-3)
		all[i][7]=loc[a[i]+3];
		if(a[i]<=n-2)
		all[i][8]=loc[a[i]+2];
		if(a[i]<=n-1)
		all[i][9]=loc[a[i]+1];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(all[i][j])
				f[i][j]=query(1,0,n,0,all[i][j]-1);
		}
		for(int j=1;j<=9;j++)
		{
			if(all[i][j])
			{
				int ms=query(1,0,n,all[i][j],all[i][j]);
				if(ms<f[i][j]+1)
					ins(1,0,n,all[i][j],all[i][j],f[i][j]+1);
			}
		}
	}
	cout<<query(1,0,n,0,n);
}


platinum 3

这是这套题中对我而言最有纪念意义的题了。

还是两边N只牛N个牛棚,这次牛们只能一一对应回自己的棚,问有多少对线路相交而且牛的编号差距小于k(输入)。

这是一道三位偏序问题,要用CDQ分治,我先是想了半个多小时才发现这是三维偏序问题。。之前一直没写出来过cdq分治,只是知道其思想,而这道题则是我第一次实践,能够在没看题解的情况下自己YY出做法和cdq分治并AC,对我这个蒟蒻而言已经是很有纪念意义了。。。。

这次用结构体存两个量,第i个表示第i个牛棚,第一个量所对应的牛棚牛棚编号,第二个量表示所对应牛的位置。

solve(l,r)时,先将(l,mid)(mid+1,r)按照对应牛位置排序,因为要求的是逆序对,所以这样排序的话对于左区间的点直接查看已经插入了那些点(这些点一定是编号在它前面的),然后像归并排序那样依次看左右两个区间谁小,如果右边小,那么把1插入到它的对应编号即可,如果左边小,(设此时编号为x)ans便+=sum(x-k-1)+(sum(n)-sum(x+k)),顺便注意判一下边界,同时每次solve完树状数组要清零即可。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#define mid (l+r)/2
using namespace std;
const int N=100050;
long long a1,b1[N],a[N],b[N];
int n,k;
long long in[N];
long long ans=0;
struct node
{
	long long w,h;
}line[N];
int cmp(node a,node b)
{
	return a.w<b.w;
}
long long lowbit(int x)
{
	return x&(-x);
}
void plu(int pos,long long num)
{
	while(pos<=2*n)
	{
		in[pos]+=num;
		pos+=lowbit(pos);
	}
} 
long long sum(int end)
{
	long long sum=0;
	while(end>0)
	{
		sum+=in[end];
		end-=lowbit(end);
	}
	return sum;
}
void solve(int l,int r)
{
	if(l==r)
		return ;
	solve(l,mid);
	solve(mid+1,r);
	sort(line+l,line+mid+1,cmp);
	sort(line+mid+1,line+r+1,cmp);
	int i=l,j=mid+1;
	while(i<=mid&&j<=r)
	{
		if(line[i].w< line[j].w)
		{
			if(line[i].h>k)
			ans+=sum(line[i].h-k-1);
				if(line[i].h<=n-k)
			ans+=sum(n)-sum(line[i].h+k);
			i++;
		}
		else
		{
			plu(line[j].h,1);
			j++;
		}
	}
	while(i<=mid)
	{
		if(line[i].h>k)
			ans+=sum(line[i].h-k-1);
		if(line[i].h<=n-k)
			ans+=sum(n)-sum(line[i].h+k);
		i++;
	}
	while(j<=r)
		plu(line[j].h,1),j++;
	for(int i=mid+1;i<=r;i++)
		plu(line[i].h,-1);
}
int main()
{
	freopen("friendcross.in","r",stdin);
	freopen("friendcross.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a1),a[a1]=i;
	for(int i=1;i<=n;i++)
		scanf("%lld",&b1[i]),b[i]=a[b1[i]];
	for(int i=1;i<=n;i++)
		line[i].w=b[i],line[i].h=b1[i];	
	solve(1,n);			
	cout<<ans<<endl;	
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值