NOIP2015题解

NOIP2015题解

Day1

神奇的幻方 magic

模拟裸题。我在NOIP切掉的第一道题

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[50][50],x,y;
int main()
{
	scanf("%d",&n);
	x=1;y=(n+1)>>1;
	for(int i=1;i<=n*n;++i)
	{
		a[x][y]=i;x-=1;y+=1;
		if(x==0)
		{
			if(y>n)x=2,y=n;
			else x=n;
		}
		else if(y>n)y=1;
		else if(a[x][y])x+=2,y-=1;
		
	}
	for(int i=1;i<=n;++i,puts(""))
		for(int j=1;j<=n;++j)
			printf("%d ",a[i][j]);
	return 0;
}

信息传递 message

不难发现就是让你求基环森林的最小环,直接\(Tarjan\)了。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 200200
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
struct Line{int v,next;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,ans=1e9;
int dfn[MAX],low[MAX],tim,S[MAX],top;
bool ins[MAX];
void Tarjan(int u)
{
	dfn[u]=low[u]=++tim;S[++top]=u;ins[u]=true;
	for(int i=h[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!dfn[v])Tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		int sz=0,v;
		do{v=S[top--];ins[v]=false;++sz;}while(u!=v);
		if(sz>1)ans=min(ans,sz);
	}
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)Add(i,read());
	for(int i=1;i<=n;++i)if(!dfn[i])Tarjan(i);
	printf("%d\n",ans);
	return 0;
}

斗地主 landlords

很妙的题。而且我的这里写的是可以被\(Hack\)的。

发现出牌有两种关系,一种与点数无关,一种与点数相关。那么当对于点数无关的时候,我们贪心求一次解,点数相关的时候我们爆搜顺子。

这样子可以过这题,但是有问题。因为贪心是假的,假的原因是你要考虑一些牌是可以拆开打的,比如说你有三个炸弹,理论上说是出\(3\)次。事实上你可以把其中一个拆成两对,这样子\(2\)次就打出去了。所以把贪心换成\(dp\)就没有问题了。然而数据随机,贪心基本就行了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,ans;
int a[20],b[20];
void dfs(int st)
{
	for(int i=0;i<=14;++i)b[a[i]]+=1;
	for(int i=1;i<=14;++i)
		if(a[i]==4)
		{
			if(b[1]>=2)b[1]-=2;
			else if(b[2]>=2)b[2]-=2;
			else if(b[2])b[2]-=1;
		}
	for(int i=1;i<=14;++i)
		if(a[i]==3)
		{
			if(b[1])b[1]-=1;
			else if(b[2])b[2]-=1;
		}
	ans=min(ans,st+b[1]+b[2]+b[3]+b[4]);
	b[0]=b[1]=b[2]=b[3]=b[4]=0;
	for(int i=3;i<=10;++i)
		for(int j=i;j<=15;++j)
		{
			if(!a[j]){for(int k=i;k<j;++k)a[k]+=1;break;}
			a[j]-=1;if(j-i+1>=5)dfs(st+1);
		}
	for(int i=3;i<=12;++i)
		for(int j=i;j<=15;++j)
		{
			if(a[j]<2){for(int k=i;k<j;++k)a[k]+=2;break;}
			a[j]-=2;if(j-i+1>=3)dfs(st+1);
		}
	for(int i=3;i<=13;++i)
		for(int j=i;j<=15;++j)
		{
			if(a[j]<3){for(int k=i;k<j;++k)a[k]+=3;break;}
			a[j]-=3;if(j-i+1>=2)dfs(st+1);
		}
}
int main()
{
	freopen("landlords.in","r",stdin);
	freopen("landlords.out","w",stdout);
	int T=read();n=read();
	while(T--)
	{
		for(int i=0;i<=14;++i)a[i]=0;
		for(int i=1;i<=n;++i)a[read()]+=1,read();
		a[14]=a[1];a[1]=0;ans=n;dfs(0);
		printf("%d\n",ans);
	}
	return 0;
}

Day2

跳石头 stone

又想起我当年连这种题都不会做(雾

二分答案,直接扫一遍就好了。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 50050
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int L,n,m,S[MAX],top,d[MAX];
bool check(int s)
{
	S[top=0]=0;int mov=0;
	for(int i=1;i<=n;++i)
	{
		if(d[i]<s){++mov;continue;}
		if(top&&d[i]-S[top]<s){++mov;continue;}
		S[++top]=d[i];
	}
	while(top&&L-S[top]<s)--top,++mov;
	return mov<=m;
}
int main()
{
	L=read();n=read();m=read();
	for(int i=1;i<=n;++i)d[i]=read();
	if(!n){printf("%d\n",L);return 0;}
	int l=0,r=1e9,ret=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))l=mid+1,ret=mid;
		else r=mid-1;
	}
	printf("%d\n",ret);return 0;
}

子串 substring

\(dp\)题。

\(f[i][j][k][0/1]\)表示当前考虑\(A\)串的第\(i\)位,\(B\)串匹配了第\(j\)个字符,当前已经分了\(k\)段,当前\(i\)位置的字符是否在最后一段中的方案数。

转移不难,看看代码就知道了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MOD 1000000007
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
char A[1010],B[220];
int f[2][220][220][2];
int n,m,K;
int main()
{
	scanf("%d%d%d",&n,&m,&K);
	scanf("%s",A+1);scanf("%s",B+1);
	f[0][0][0][0]=1;
	for(int i=1,nw=1,pw=0;i<=n;++i,nw^=1,pw^=1)
	{
		memset(f[nw],0,sizeof(f[nw]));
		for(int j=0;j<=m;++j)
			for(int k=0;k<=K;++k)
			{
				add(f[nw][j][k][0],f[pw][j][k][0]);
				add(f[nw][j][k][0],f[pw][j][k][1]);
				if(A[i]==B[j])
				{
					add(f[nw][j][k][1],f[pw][j-1][k][1]);
					if(k)
					{
						add(f[nw][j][k][1],f[pw][j-1][k-1][0]);
						add(f[nw][j][k][1],f[pw][j-1][k-1][1]);
					}
				}
			}
	}	
	printf("%d\n",(f[n&1][m][K][0]+f[n&1][m][K][1])%MOD);
	return 0;
}

运输计划 transport

现在看是真的简单题。。。

二分答案,显然时间本来就小于二分值的就不用管,只考虑时间大于二分值的。那么显然它要改变路径上的一条边时间才会变小,那么树上差分找到可以改变所有计划的一条最长边。然后用最大的时间减去最长边边长和二分值比较判定就做完了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 300300
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
struct Line{int v,next,w;}e[MAX<<1];
int h[MAX],cnt=1,df[MAX];
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n,m,mx;
namespace LCA
{
	int dep[MAX],fa[MAX],top[MAX],size[MAX],hson[MAX],dis[MAX];
	void dfs1(int u,int ff)
	{
		size[u]=1;fa[u]=ff;dep[u]=dep[ff]+1;
		for(int i=h[u];i;i=e[i].next)
		{
			int v=e[i].v;if(v==ff)continue;
			dis[v]=dis[u]+e[i].w;df[v]=e[i].w;
			dfs1(v,u);size[u]+=size[v];
			if(size[hson[u]]<size[v])hson[u]=v;
		}
	}
	void dfs2(int u,int tp)
	{
		top[u]=tp;if(hson[u])dfs2(hson[u],tp);
		for(int i=h[u];i;i=e[i].next)
			if(e[i].v!=fa[u]&&e[i].v!=hson[u])dfs2(e[i].v,e[i].v);
	}
	int LCA(int u,int v)
	{
		while(top[u]^top[v])dep[top[u]]<dep[top[v]]?v=fa[top[v]]:u=fa[top[u]];
		return dep[u]<dep[v]?u:v;
	}
	int Dis(int u,int v){return dis[u]+dis[v]-2*dis[LCA(u,v)];}
}
struct Plan{int u,v,lca,d;}p[MAX];
int a[MAX];
void dfs(int u,int ff)
{
	for(int i=h[u];i;i=e[i].next)
		if(e[i].v!=ff)dfs(e[i].v,u),a[u]+=a[e[i].v];
}
bool check(int t)
{
	memset(a,0,sizeof(a));
	int tot=0;
	for(int i=1;i<=m;++i)
		if(p[i].d>t)
			++tot,a[p[i].u]+=1,a[p[i].v]+=1,a[p[i].lca]-=2;
	dfs(1,0);
	int mxt=0;
	for(int i=2;i<=n;++i)
		if(a[i]==tot)
			mxt=max(mxt,df[i]);
	return mx-mxt<=t;		
}
int main()
{
	n=read();m=read();
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read(),w=read();
		Add(u,v,w);Add(v,u,w);
	}
	LCA::dfs1(1,0);LCA::dfs2(1,1);
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read();
		p[i]=(Plan){u,v,LCA::LCA(u,v),LCA::Dis(u,v)};
		mx=max(mx,p[i].d);
	}
	int l=0,r=mx,ret=mx;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))r=mid-1,ret=mid;
		else l=mid+1;
	}
	printf("%d\n",ret);
	return 0;
}
posted @ 2018-11-08 20:23  小蒟蒻yyb  阅读(423)  评论(0编辑  收藏  举报