【比赛报告】NOIP2015真题 NOIP练习赛卷三十二

这篇博客回顾了NOIP2015比赛中涉及的算法问题,包括神奇的幻方的模拟实现,信息传递中利用并查集解决最小环问题,斗地主的线性DP和DFS策略,跳石头的二分查找方法,以及子串和运输计划问题的线性DP和树上差分等复杂算法的应用。作者通过学习和总结,展示了不同算法在解决实际问题中的效能。
摘要由CSDN通过智能技术生成

神奇的幻方 模拟

题目链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


#include<cstdio>
const int N=40;
int a[N][N],num=1,n,x,y;
int main()
{
	scanf("%d",&n);
	while(num<=n*n)
	{
		if(num==1)a[x=1][y=(n+1)/2]=num++;
		else if(x==1&&y!=n)a[x=n][y=y+1]=num++;
		else if(x!=1&&y==n)a[x=x-1][y=1]=num++;
		else if(x==1&&y==n)a[x=x+1][y=y]=num++;
		else if(!a[x-1][y+1])a[x=x-1][y=y+1]=num++;
		else a[x=x+1][y=y]=num++;
	}
	for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	        printf("%d%c",a[i][j],j==n?'\n':' ');
	return 0;
}

总结

模拟


信息传递 并查集

题目链接
在这里插入图片描述
在这里插入图片描述


求最小环。可以通过并查集判断是否成环了。统计环长度可以再写一个无路径压缩的并查集,暴力的跳上去找。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2e5+10;
inline int read()
{
	int s=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	if(f)s=-s;return s;
}
int n,t[N],fa[N],nx[N],ans=0x3f3f3f3f;
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main()
{
	//freopen("in.txt","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        nx[i]=fa[i]=i;
    for(int i=1;i<=n;i++)
    {
    	t[i]=read();
    	int fx=find(i),fy=find(t[i]);
    	if(fx==fy)
    	{
    		int cnt=1;
    		for(int j=t[i];j!=i;j=nx[j])cnt++;
		    ans=min(ans,cnt);
		}
		nx[i]=t[i];
		fa[fx]=fy;
	}
	printf("%d\n",ans);
	return 0;
}

总结

并查集的应用。


斗地主(增强版) 线性DP+dfs

题目链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


终于看明白了大佬题解,然而还是各种错误。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
int t,n,ans,dp[25][25][25][25][3],p[25],cnt[6];
//dp[a][b][c][d][e],a个单张,b个两张,c个三张,d个四张,e个王的最小出牌次数 
void Init()//dp预处理出最小操作次数 
{
	memset(dp,0x3f,sizeof(dp));dp[0][0][0][0][0]=0;
	_rep(d,0,n)_rep(c,0,n)_rep(a,0,n)_rep(b,0,n)_rep(e,0,2)//循环顺序很重要 
	{
		int tmp=100;
		if(a>0)tmp=min(tmp,dp[a-1][b][c][d][e]+1);
		if(b>0)tmp=min(tmp,dp[a][b-1][c][d][e]+1);
		if(c>0)tmp=min(tmp,dp[a][b][c-1][d][e]+1);
		if(d>0)tmp=min(tmp,dp[a][b][c][d-1][e]+1);
		if(e>0)tmp=min(tmp,dp[a][b][c][d][e-1]+1);
		//先走单张
		if(e>1)tmp=min(tmp,dp[a][b][c][d][e-2]+1);
		//王炸
		if(a>0&&c>0)tmp=min(tmp,dp[a-1][b][c-1][d][e]+1);
		if(c>0&&e>0)tmp=min(tmp,dp[a][b][c-1][d][e-1]+1);
		//三带一
		if(b>0&&c>0)tmp=min(tmp,dp[a][b-1][c-1][d][e]+1);
		//三带二
		if(a>1&&d>0)tmp=min(tmp,dp[a-2][b][c][d-1][e]+1);
		if(a>0&&d>0&&e>0)tmp=min(tmp,dp[a-1][b][c][d-1][e-1]+1);
		if(d>0&&e>1)tmp=min(tmp,dp[a][b][c][d-1][e-2]+1);
		if(b>0&&d>0)tmp=min(tmp,dp[a][b-1][c][d-1][e]+1);
		if(b>1&&d>0)tmp=min(tmp,dp[a][b-2][c][d-1][e]+1);
		if(d>1)tmp=min(tmp,dp[a][b][c][d-2][e]+1);
		//四带二
		if(c>0)tmp=min(tmp,dp[a+1][b+1][c-1][d][e]);
		if(d>0)tmp=min(tmp,dp[a+1][b][c+1][d-1][e]);
		//拆牌
		dp[a][b][c][d][e]=min(dp[a][b][c][d][e],tmp); 
	}
}
void dfs(int step)
{
	if(step>=ans)return;
	int len;bool flag;
	_rep(k,1,3)_rep(i,1,12)
	{
		flag=true;
		if(k==1)len=5;//顺子 
		if(k==2)len=3;//连对
		if(k==3)len=2;//飞机
		while(flag&&i+len-1<=12)
		{
			_rep(j,1,len)if(p[i+j-1]<k){flag=false;break;}
			if(!flag)continue;
			_rep(j,1,len)p[i+j-1]-=k;
			dfs(step+1);
			_rep(j,1,len)p[i+j-1]+=k;
			len++;//尝试更长的顺子连对飞机 
		} 
	}
	cnt[1]=cnt[2]=cnt[3]=cnt[4]=cnt[5]=0;_rep(i,1,13)cnt[p[i]]++;
	cnt[5]=p[14];
	ans=min(ans,step+dp[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]]);
}
int main()
{
	//freopen("in.txt","r",stdin);
	scanf("%d%d",&t,&n);
    Init();    
	while(t--)
    {
    	memset(p,0,sizeof(p));ans=n;
    	_rep(i,1,n)
    	{
    		int num,col;scanf("%d%d",&num,&col);
    		if(num==0){p[14]++;continue;}
    		if(num>=3)p[num-2]++;
    		if(num==2)p[13]++;
    		if(num==1)p[12]++;
		}
		dfs(0);printf("%d\n",ans);
	}
	return 0;
}

总结

牛批的爆搜


跳石头 二分

题目链接
在这里插入图片描述
在这里插入图片描述


挺水一题,二分跳跃的最小距离

#include<cstdio>
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
const int N=5e4+10;
int L,m,n;
int d[N];
bool judge(int lim)
{
	int cnt=0,pre=0;
	_rep(i,1,n)
	{
		if(d[i]-pre<lim){cnt++;continue;}
		pre=d[i];
	}
	return cnt<=m;
}
int main()
{
	//freopen("in.txt","r",stdin);
    scanf("%d%d%d",&L,&n,&m);
    _rep(i,1,n)scanf("%d",&d[i]);
    int l=1,r=L;
    while(l<r)
    {
    	int mid=(l+r+1)>>1;
    	if(judge(mid))l=mid;
    	else r=mid-1;
	}
	printf("%d\n",l);
	return 0;
}

子串 线性DP+前缀和

题目链接
在这里插入图片描述
在这里插入图片描述


#include<cstdio>
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep_(i,a,b) for(int i=(a);i>=(b);i--)
const int mod=1e9+7;
int dp[201][201],sum[201][201],n,m,K;
char a[1001],b[1001];
int main()
{
	scanf("%d%d%d%s%s",&n,&m,&K,a+1,b+1);dp[0][0]=1;
	_rep(i,1,n)rep_(j,m,1)rep_(k,K,1)dp[j][k]=(dp[j][k]+((a[i]==b[j])?(sum[j][k]=(sum[j-1][k]+dp[j-1][k-1])%mod):(sum[j][k]=0)))%mod;
	printf("%d\n",dp[m][K]);return 0;
}

总结


运输计划 树链剖分+树上差分+LCA+二分

题目链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


学习了大佬题解,主要思路摘抄如下:
先LCA一遍,记下每个任务的起点,终点,公共祖先,所需时间
然后二分答案,统计不满足答案的任务tot,然后维护一个sum[i],
对于每个不满足条件的任务,sum[起点]++,sum[终点]++,sum[公共祖先]-=2,
并将它们的sum值传到父亲结点,最后看是否能找出某个点i,使sum[i]=tot并且
连到这个点的边权值>= 最大任务时间-答案,如果能,这个答案即为可行答案。

感觉就是标记每个点经过多少不满足条件的边,然后看这个点连向父节点的边权改为0后能不能满足条件
自己在模拟的时候打的是暴力

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
template<typename tp>inline void read(tp &x)
{
	int f=0;char ch=getchar();x=0;
	while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(f)x=-x;
}
const int N=3e5+10;
int n,m,tot=1,hd[N],maxn,dis[N],dep[N],fa[N],ans=0x7fffffff,sz[N],top[N],son[N],edge[N],INF,sum[N];
struct Edge{
	int v,nx,w;
}e[N<<1];
struct node{
	int u,v;
}ask[N];
struct Node{
	int u,v,anc,dis;
}lca[N];
inline void add(int u,int v,int w)
{
	e[++tot].v=v;
	e[tot].w=w;
	e[tot].nx=hd[u];
	hd[u]=tot;
}
namespace pts20{
	void dfs(int u,int f)
	{
		fa[u]=f;
		for(int i=hd[u];i;i=e[i].nx)
		{
			int v=e[i].v;if(v==f)continue;
			dis[v]=dis[u]+e[i].w;
			dfs(v,u);
		}
	}
	void dfs2(int u,int son)
	{
		for(int i=hd[u];i;i=e[i].nx)
		{
			int v=e[i].v;
			if(v==son)continue;
			if(v==fa[u]){maxn=max(maxn,e[i].w),dfs2(v,u);break;}
		}
	}
	inline void solve()
	{
		int st,ed;read(st);read(ed);
		dfs(st,0);dfs2(ed,ed);printf("%d\n",dis[ed]-maxn);
	}
}
namespace pts30{//在学校OJ能得30分 
	inline void dij(int st,int ed)
	{
		priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;while(!q.empty())q.pop();q.push(make_pair(0,st));
		memset(dis,0x3f,sizeof(dis));dis[st]=0;static int vis[N];memset(vis,0,sizeof(vis));
	    while(!q.empty())
	    {
	    	int u=q.top().second;q.pop();vis[u]=0;
	    	for(int i=hd[u];i;i=e[i].nx)
	    	{
	    		int v=e[i].v;
	    		if(dis[v]>dis[u]+e[i].w)
	    		{
	    			dis[v]=dis[u]+e[i].w;
	    			if(!vis[v])vis[v]=1,q.push(make_pair(dis[v],v));
				}
			}
		}
		maxn=max(maxn,dis[ed]);
	}
	inline void solve()
	{
		for(int i=1;i<=m;i++)
		    read(ask[i].u),read(ask[i].v);
		for(int i=2;i<=tot;i+=2)
		{
			int tmp=e[i].w;e[i].w=e[i^1].w=0;
			maxn=0;
			for(int j=1;j<=m;j++)
			    dij(ask[j].u,ask[j].v);
			ans=min(ans,maxn);
			e[i].w=e[i^1].w=tmp;
		}
		printf("%d\n",ans);
	}
}
namespace ac{
	void dfs1(int u,int f)
	{
		dep[u]=dep[f]+1;fa[u]=f;sz[u]=1;
		int maxson=-1;
		for(int i=hd[u];i;i=e[i].nx)
		{
			int v=e[i].v;
			if(v==f)continue;
			edge[v]=i;dis[v]=dis[u]+e[i].w;
			dfs1(v,u);sz[u]+=sz[v];
		    if(sz[v]>maxson)son[u]=v,maxson=sz[v];
		}
	}
	void dfs2(int u,int topf)
	{
		top[u]=topf;
		if(son[u])dfs2(son[u],topf);
		for(int i=hd[u];i;i=e[i].nx)
		{
			int v=e[i].v;
			if(v==son[u]||v==fa[u])continue;
			dfs2(v,v);
		}
	}
	inline int LCA(int x,int y)//树剖求LCA 
	{
		while(top[x]!=top[y])
		{
			if(dep[top[x]]<dep[top[y]])swap(x,y);//x顶端深度更深 
			x=fa[top[x]];//x跳到x所在链顶端上面一个点 
		}
		if(dep[x]>dep[y])swap(x,y);
		return x;
	}
	void dfs3(int u,int f)//求子树中差分数组和 
	{
		for(int i=hd[u];i;i=e[i].nx)
		{
			int v=e[i].v;
			if(v==f)continue;
			dfs3(v,u);sum[u]+=sum[v];
		}
	}
	inline bool check(int lim)
	{
		memset(sum,0,sizeof(sum));
		int p=0,cnt=0;
		for(int i=1;i<=m;i++)
		    if(lca[i].dis>lim)
		    {
		    	cnt++;
		    	sum[lca[i].u]++;
		    	sum[lca[i].v]++;
		    	sum[lca[i].anc]-=2;
		    	p=max(p,lca[i].dis-lim);
			}
		dfs3(1,1);
		for(int i=1;i<=n;i++)
		    if(sum[i]==cnt&&e[edge[i]].w>=p)return 1;
		return 0;
	}
	inline void solve()
	{
		dfs1(1,0);dfs2(1,1);
		for(int i=1;i<=m;i++)
		{
			read(lca[i].u);read(lca[i].v);
			lca[i].anc=LCA(lca[i].u,lca[i].v);
			lca[i].dis=dis[lca[i].u]+dis[lca[i].v]-2*dis[lca[i].anc];
			INF=max(INF,lca[i].dis);
		}
		int l=0,r=INF,ans=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid))ans=mid,r=mid-1;
			else l=mid+1;
		}
		printf("%d\n",ans);
	}
}
int main()
{
	//freopen("in.txt","r",stdin);
	read(n);read(m);
	for(int i=1,u,v,w;i<n;i++) 
	    read(u),read(v),read(w),add(u,v,w),add(v,u,w);
	//if(m==1)pts20::solve();
	//else pts30::solve();
	ac::solve();
	return 0;
}

总结

综合性很大的题。树剖求LCA的操作还没见过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值