【解题报告】NOIP2015

【解题报告】NOIP2015

Day1

T1 神奇的幻方

思路

这不就是按照题目所给信息模拟一下咩?

过了

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int s[50][50];
int main()
{
	cin>>n;
	int x=1,y=(n+1)/2;
	for(int i=1;i<=n*n;i++)
	{
		s[x][y]=i;
		if(!s[(x-2+n)%n+1][y%n+1])
		x=(x-2+n)%n+1,y=y%n+1;
		else x=x%n+1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		cout<<s[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}

T2 信息传递

并查集求最小环并利用地址传参

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxn=2000005;
int n;
int fa[maxn];
int ans=99999999;
void init()
{
	for(int i=1;i<=n;i++)
	fa[i]=i;
}
int get(int x,int &cnt)//第一次知道并查集求最小环 
{
	cnt++;
	if(x==fa[x])
	return x;
	else
	return get(fa[x],cnt);
}
int main()
{
	cin>>n;
	init();
	for(int i=1;i<=n;i++)
	{
		int cnt=0;
		int x;
		cin>>x;
		if(get(x,cnt)==i)
		ans=min(ans,cnt);
		else
		fa[i]=x;
	}
	cout<<ans<<endl;
	return 0;
}

T3 斗地主

思路

巨大模拟,狗都不做

发现出顺子可以出出来的牌,那么我们就从顺子作为第一个搜索

然后四带二,三带二,三带一,上单

这样我们就按照这个顺序依次搜索+回溯

就写完了,就是这个代码难度是真的大

到现在我还是不想写这个恶心的代码

随便嫖一篇过来(版权联系

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#define int long long
using namespace std;
int T,n,ans;
int a[20];
void dfs(int x)
{
	if(x>=ans) return ;
	int k=0;
	for(int i=3;i<=14;i++)
	{
		if(a[i]==0) k=0;
		else
		{
			k++;
			if(k>=5)
			{
				for(int j=i;j>=i-k+1;j--)
				a[j]--;
				dfs(x+1);
				for(int j=i;j>=i-k+1;j--)
				a[j]++;
			}
		}
	}
	
	k=0;
	for(int i=3;i<=14;i++)
	{
		if(a[i]<=1) k=0;
		else
		{
			k++;
			if(k>=3)
			{
				for(int j=i;j>=i-k+1;j--)
				a[j]-=2;
				dfs(x+1);
				for(int j=i;j>=i-k+1;j--)
				a[j]+=2;
			}
		}
	}
	
	k=0;
	for(int i=3;i<=14;i++)
	{
		if(a[i]<=2) k=0;
		else
		{
			k++;
			if(k>=2)
			{
				for(int j=i;j>=i-k+1;j--)
				a[j]-=3;
				dfs(x+1);
				for(int j=i;j>=i-k+1;j--)
				a[j]+=3;
			}
		}
	}
	
	for(int i=2;i<=14;i++)
	{
		if(a[i]<=3)
		{
			if(a[i]<=2) continue;
			a[i]-=3;
			for(int j=2;j<=15;j++)
			{
				if(a[j]<=0||j==i) continue;
				a[j]--;
				dfs(x+1);
				a[j]++;
			}
			for(int j=2;j<=14;j++)
			{
				if(a[j]<=1||j==i) continue;
				a[j]-=2;
				dfs(x+1);
				a[j]+=2;
			}
			a[i]+=3;
		}
		else
		{
			a[i]-=3;
			for(int j=2;j<=15;j++)
			{
				if(a[j]<=0||j==i) continue;
				a[j]--;
				dfs(x+1);
				a[j]++;
			}
			for(int j=2;j<=14;j++)
			{
				if(a[j]<=1||j==i) continue;
				a[j]-=2;
				dfs(x+1);
				a[j]+=2;
			}
			a[i]+=3;
			
			a[i]-=4;
			for(int j=2;j<=15;j++)
			{
				if(a[j]<=0||j==i) continue;
				a[j]--;
				for(int k=2;k<=15;k++)
				{
					if(a[k]<=0||k==j) continue;
					a[k]--;
					dfs(x+1);
					a[k]++;
				}
				a[j]++;
			}
			for(int j=2;j<=14;j++)
			{
				if(a[j]<=1||j==i) continue;
				a[j]-=2;
				for(int k=2;k<=14;k++)
				{
					if(a[k]<=1||k==j) continue;
					a[k]-=2;
					dfs(x+1);
					a[k]+=2; 
				}
				a[j]+=2;
			}
			a[i]+=4;
		}
	}
	
	for(int i=1;i<=15;i++)
	{
		if(a[i])
		x++;
	}
	ans=min(ans,x);
}
signed main()
{
	cin>>T>>n;
	while(T--)
	{
		ans=1e9;
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;i++)
		{
			int x,y;
			cin>>x>>y;
			if(x==0) 
			a[15]++;
			else if(x==1)
			a[14]++;
			else
			a[x]++;
		}
		dfs(0);
		cout<<ans<<'\n';
	}
	return 0;
}

Day2

T1 跳石头

思路

二分模板题目

我们对于两个石头的距离进行二分

如果对于某个二分的距离,可以在 \(m\) 个石头之内使得大于这个二分的距离

那么我们就可以完成

这样,我们尝试更长的距离是否能完成

二分一下下就好啦

主要是要注意二分模板

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#define int long long 
using namespace std;
const int maxn=50005; 
int L,n,m;
int d[maxn];
int mx=-1e9;
int ans=0;
bool check(int x)//移走m块石头可以使最短距离大于等于x 
{
	int cnt=0;
	int lst=0;
	for(int i=1;i<=n+1;i++)
	{
		if(d[i]-d[lst]<x)
		cnt++;
		else
		lst=i;
	}
	return cnt<=m;
}
signed main()
{
	cin>>L>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>d[i],mx=max(mx,d[i]);
	d[n+1]=L;
	int l=0,r=L;
	while(l<=r)//我不知道为什么不这么写二分就会炸成TLE
	{
		int mid=(l+r)>>1;
		if(check(mid)) 
		l=mid+1,ans=mid;
		else
		r=mid-1; 
	}
	cout<<ans<<'\n';
	return 0;
}

T2 子串

思路

不会,参考一下别人

\(f[i][j][k]\) 表示 A 的前 \(i\) 个字符中,使用了 \(k\) 个子串,匹配 B 串的前 \(j\) 个字符

所以有 \(f[i][j][k]=f[i-1][j-1][k-1]+f[i-1][j-1][k]\)

意思是,A 的前 \(i\) 个字符中,使用了 \(k\) 个子串,匹配 B 串的前 \(j\) 个字符的方案数量等于在 A 的前 \(i-1\) 个字符里面,使用了 \(k-1\) 个子串,匹配了 B串的前 \(j-1\) 个字符的方案数量加上 在 A 的前 \(i-1\) 个字符里面,使用了 \(k\) 个子串,匹配了 B串的前 \(j-1\) 个字符的方案数量

简单地来说,就是看最后一个字符是否使用

但是我们无法解决前一个字符和前面有没有连起来以及有没有使用的情况

所以

新的状态转移方程

\[f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1] \\ when \space a[i]==b[j] \\ f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1] \]

然后滚动数组优化一下就可以DP了

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
const int MOD=1000000007; 
int n,m,k;
char a[1005],b[205];
int f[205][205][3];
int main()
{
	cin>>n>>m>>k;
	scanf("%s%s",a+1,b+1);
	f[0][0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>0;j--)
		{
			if(a[i]==b[j])
			{
				for(int x=min(j,k);x>0;x--)
				{
					f[j][x][1]=(f[j-1][x][1]+f[j-1][x-1][0])%MOD;
					f[j][x][0]=(f[j][x][0]+f[j][x][1])%MOD;
				}
			}
			else
			for(int x=1;x<=m;x++)
			f[j][x][1]=0;
		}
	}
	cout<<f[m][k][0]%MOD<<endl;
	return 0;
}

T3 运输计划

思路

非常毒瘤的题目

LCA+树上差分+二分

先对于每条路径处理一下它的长度,起点终点

二分一个最小路径的长度

在中间用差分,用于删去这一条边,找到最小的一条路径,看它是不是符合答案的,如果是的话,如果不是的话,分别处理

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#define int long long
using namespace std;
const int maxn=300005;
struct edge{
	int e,next,val;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e,int val)
{
	en++;
	ed[en].next=first[s];
	first[s]=en;
	ed[en].e=e;
	ed[en].val=val;
}
struct node{
	int u,v,lca,dist;
}p[maxn<<1];


int n,m,s;


bool vis[maxn];
int fa[maxn][25],dep[maxn];
int dis[maxn];
int num[maxn],tot=0;
void dfs(int x,int pa,int depth)
{
	tot++;
	num[tot]=x;
	dep[x]=depth;
	vis[x]=true;
	for(int i=1;i<25;i++)
	fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=first[x];i>0;i=ed[i].next)
	{
		int v=ed[i].e;
		if(!vis[v])
		{
			fa[v][0]=x;
			dis[v]=dis[x]+ed[i].val;
			dfs(v,x,depth+1);
		}
	}
}
int get_lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	int t=dep[x]-dep[y];
	for(int i=0;i<25;i++)
		if((1<<i)&t) x=fa[x][i];
	if(x==y) return x;
	for(int i=24;i>=0;i--)
	{
		if(fa[x][i]!=fa[y][i])
		x=fa[x][i],y=fa[y][i];
	}
	return fa[x][0];
}
int tmp[maxn];
bool check(int x)
{
	int cnt=0,ans=0;
	memset(tmp,0,sizeof(tmp));
	for(int i=1;i<=m;i++)
	{
		if(p[i].dist>x)
		{
			tmp[p[i].u]++;
			tmp[p[i].v]++;
			tmp[p[i].lca]-=2;
			ans=max(ans,p[i].dist-x);
			cnt++;
		}
	}
	if(cnt==0) return true;
	for(int i=n;i>=1;i--) 
	tmp[fa[num[i]][0]]+=tmp[num[i]];
	for(int i=2;i<=n;i++) 
		if(tmp[i]==cnt&&dis[i]-dis[fa[i][0]]>=ans) 
		return true;
	return false;
}


signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n-1;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		add_edge(x,y,z);
		add_edge(y,x,z);
		s+=z;
	}
	dis[0]=0;
	fa[0][0]=0;
	dep[0]=1;
	dfs(1,0,1);
	for(int i=1;i<=m;i++)
	{
		cin>>p[i].u>>p[i].v;
		p[i].lca=get_lca(p[i].u,p[i].v);
		p[i].dist=dis[p[i].u]+dis[p[i].v]-2*dis[p[i].lca];
	}
	int l=0,r=s;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) 
		r=mid;
		else 
		l=mid+1;
	}
	cout<<l<<'\n';
	return 0;
}
本博文为wweiyi原创,若想转载请联系作者,qq:2844938982
原文地址:https://www.cnblogs.com/wweiyi2004/p/15546500.html