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;
}