NOIP解题报告

NOIP解题报告


day1

T1 

题目链接

其实就是一个简单的模拟,只要分清方向,然后取模的时候细心一点就可以啦。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 110003
using namespace std;
struct data{
	int x,num;
	char s[30];
}a[N];
int n,m;
int main()
{
	freopen("toy.in","r",stdin);
	freopen("toy.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) {
		scanf("%d%s",&a[i].x,&a[i].s);
		a[i].num=i;
	}
	int now=1;
	for (int i=1;i<=m;i++) {
		int x,y; scanf("%d%d",&x,&y);
		if (a[now].x==0) {
			if (x==0) now=((now-y-1)%n+n)%n+1;
			else now=(now+y-1)%n+1;
		}
		else {
			if (x==0)  now=(now+y-1)%n+1;
			else now=((now-y-1)%n+n)%n+1;
		}
	}
	printf("%s\n",a[now].s);
}


T2

题目链接

这道题应该是今年noip最难的一道题吧,放在day1T2居心何在啊。

这道题小数据可以O(n^2)暴力,对于每一个点以他为根建树,然后找寻与根深度相距w[i]的s。

正解应该是lca+标记,时间复杂度O(nlogn),主要时间用在lca求最近公共祖先上。

设第i个人起终点的lca(s[i],t[i])为lca[i],点i深度为deep[i]
考虑可能对点u有贡献的第i个跑步者,他的lca[i]肯定在u或者u上方,否则不经过u点
基于此前提,只有两种情况(有重合部分):
①s[i]∈subtree[u]  这时需要deep[s[i]]-w[u]=deep[u] deep[s[i]]=w[u]+deep[u]
②t[i]∈subtree[u]  deep[s[i]]+deep[t[i]]-2*deep[lca[i]]-(deep[t[i]]-deep[u])=w[u] 整理得deep[s[i]]-2*deep[lca[i]=w[u]-deep[u]
观察之后不难发现右边的式子是定值,也就是说对当前点有贡献的点都满足同一条件,观察左式可以发现条件可以直接计算。
所以我们考虑用cnt[0][deep[s[i]],cnt[1][deep[s[i]]-2*deep[lca[i]]来记录到某个点位置数值为deep[s[i]]/deep[s[i]]-2*deep[lca[i]的点得个数。
但是统计到当前点的时候可能有一部分不属于当前点的子树,根据dfs序,子树的遍历一定是一段连续的区间,那么我们可以记录下在遍历这个子树之前deep[s[i]]/deep[s[i]]-2*deep[lca[i]的点的个数
遍历完子树,直接对应相减得到答案,即可。
还有一个问题,就是我们上面说lca[i]在当前点或者上方,才有贡献,也就是说我们需要撤销加入的标记。
对于每个点继续s[i]在该点的点,t[i]在该点的点,lca[i]在该点的点,访问到该点是用s[i],t[i]更新cnt数组,计算完成后,将lca[i]的影响撤销,注意撤销的时候lca[i]对应的s[i]和t[i]的影响都要撤销。
还有一个特殊情况需要注意u为第i人的lca时会导致s[i]与t[i]各对ans[u]贡献一次,这种情况需要特判
注意因为deep[s[i]]-2*deep[lca[i]可能为负值,所以需要在计算时+300000防止数组越界

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 600003
#define base 300000
using namespace std;
int s[N],t[N],lc[N],f[N][20],mi[20],w[N],ans[N];
int point[N],next[N],v[N],n,m,tot,deep[N];
int nxt[5][N],head[5][N],c[5][N],tt[5],a[N],b[N],cnt[3][N];
void add(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void build(int x,int y,int &tot,int head[N],int nxt[N],int c[N])
{
	tot++; nxt[tot]=head[x]; head[x]=tot; c[tot]=y;
}
void dfs(int x,int fa)
{
	deep[x]=deep[fa]+1;
	for (int i=1;i<=19;i++){
		if (deep[x]-mi[i]<0) break;
		f[x][i]=f[f[x][i-1]][i-1];
	}
	for (int i=point[x];i;i=next[i]){
		if (v[i]==fa) continue;
		f[v[i]][0]=x;
		dfs(v[i],x);
	}
}
int lca(int x,int y)
{
	if (deep[x]<deep[y]) swap(x,y);
	int k=deep[x]-deep[y];
	for (int i=0;i<=19;i++)
	 if (k>>i&1) x=f[x][i];
	if (x==y) return y;
	for (int i=19;i>=0;i--) 
	 if (f[x][i]!=f[y][i]) {
	 	x=f[x][i];
	 	y=f[y][i];
	 }
	return f[x][0];
}
void solve(int x,int fa)
{
	a[x]=cnt[0][deep[x]+w[x]+base];
	b[x]=cnt[1][w[x]-deep[x]+base];
	for (int i=head[1][x];i;i=nxt[1][i]) cnt[0][deep[x]+base]++;
	for (int i=head[2][x];i;i=nxt[2][i]) cnt[1][deep[s[c[2][i]]]-2*deep[lc[c[2][i]]]+base]++;
	for (int i=point[x];i;i=next[i]){
		if (v[i]==fa) continue;
		solve(v[i],x);
	}
	ans[x]+=cnt[0][deep[x]+w[x]+base]; ans[x]+=cnt[1][w[x]-deep[x]+base];
	ans[x]=ans[x]-a[x]-b[x];
	for (int i=head[3][x];i;i=nxt[3][i])
	{
	 cnt[0][deep[s[c[3][i]]]+base]--,cnt[1][deep[s[c[3][i]]]-2*deep[lc[c[3][i]]]+base]--;
	 if (deep[s[c[3][i]]]==deep[x]+w[x]) ans[x]--;
    }
}
int main()
{
	freopen("running.in","r",stdin);
	freopen("running.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;i++){
		int x,y; scanf("%d%d",&x,&y);
		add(x,y);
	}
	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
	mi[0]=1;
	for (int i=1;i<=19;i++) mi[i]=mi[i-1]*2;
    dfs(1,0);
    for (int i=1;i<=m;i++){
    	scanf("%d%d",&s[i],&t[i]);
    	lc[i]=lca(s[i],t[i]);  
    	build(s[i],i,tt[1],head[1],nxt[1],c[1]);
    	build(t[i],i,tt[2],head[2],nxt[2],c[2]);
    	build(lc[i],i,tt[3],head[3],nxt[3],c[3]);
	}
	solve(1,0);
	for (int i=1;i<=n;i++) printf("%d ",ans[i]);
	printf("\n");
}


T3

题目链接

概率与期望dp

这道题暴力给的很良心,写的好可以得到80+

那么正解是神马呢?

先跑一遍 Floyd 算出任意点对之间的最短路 D(i,j)。
dp[i][j][k] 为当前考虑到第 i 个时间段,已经用了 j 次换教室机会以及第 i 个时间段申
请情况为 k(k=1表示申请)时期望值的最小值。
每一次枚举第 i + 1 个时段是否申请换教室然后进行转移,考虑计算第 i 时刻到第 i + 1 时
刻的期望 E(d):
1) 都未更换,那么 E(d)=D(c[i],c[i+1])。
2) 只有 i 申请更换,那么 E(d)=(1-k[i])*D(c[i],c[i+1])+k[i]*D(d[i],c[i+1])
3) 只有 i + 1 申请更换,那么 E(d)=(1-k[i+1])D(c[i],c[i+1])+k[i+1]D(c[i],d[i+1])
4) 都申请更换,那么 E(d)= (1-k[i])(1-k[i+1])D(c[i],c[i+1])+k[i](1-k[i])D(d[i],c[i+1])+(1-k[i])k[i+1]D(c[i],d[i+1])+k[i]k[i+1]D(d[i],d[i+1])
如果每次都计算E(d)常数非常大,所以可以预处理一下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 313
using namespace std;
int dis[N][N],len,cnt,d[2003],c[2003],n,m,p,q;
double minn,sum,k[2003],dp[2003][2003][3],qw[2003][5];
bool f;
int main()
{
	freopen("classroom.in","r",stdin);
	//freopen("classroom.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&p,&q);
	for (int i=1;i<=n;i++) scanf("%d",&c[i]);
	for (int i=1;i<=n;i++) scanf("%d",&d[i]);
	f=true;
	for (int i=1;i<=n;i++) 
	 scanf("%lf",&k[i]);
	memset(dis,127/3,sizeof(dis));
	minn=(double)1000000000;
	for (int i=1;i<=q;i++) {
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		dis[x][y]=min(dis[x][y],z);
		dis[y][x]=min(dis[y][x],z);
	}
	for (int i=1;i<=p;i++) dis[i][i]=0;
	for (int k=1;k<=p;k++)
	 for (int i=1;i<=p;i++)
	  for (int j=1;j<=p;j++)
	   if (k!=j&&i!=j&&i!=k)
	    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	memset(dp,127,sizeof(dp));
	dp[1][0][0]=0;
	dp[1][1][1]=0; 
	for (int i=1;i<n;i++) {
		qw[i+1][1]=dis[c[i]][c[i+1]];
		qw[i+1][2]=k[i]*dis[d[i]][c[i+1]]+(1-k[i])*dis[c[i]][c[i+1]];
		qw[i+1][3]=k[i]*k[i+1]*dis[d[i]][d[i+1]]+k[i]*(1-k[i+1])*dis[d[i]][c[i+1]]+(1-k[i])*k[i+1]*dis[c[i]][d[i+1]]+(1-k[i])*(1-k[i+1])*dis[c[i]][c[i+1]];
		qw[i+1][4]=(1-k[i+1])*dis[c[i]][c[i+1]]+k[i+1]*dis[c[i]][d[i+1]];
	}
	for (int i=1;i<n;i++)
	 for (int j=0;j<=min(m,i+1);j++)
	   {
	   	 if (j<=i) 
		  dp[i+1][j][0]=min(dp[i][j][1]+qw[i+1][2],dp[i][j][0]+dis[c[i]][c[i+1]]);
	   	 if (j-1>=0&&j-1<=i) 
		  dp[i+1][j][1]=min(dp[i][j-1][0]+qw[i+1][4],dp[i][j-1][1]+qw[i+1][3]);
	   }
	double ans=1000000000;
	for (int i=0;i<=m;i++) {
		double t=min(dp[n][i][1],dp[n][i][0]);
		ans=min(ans,t);
	}
	printf("%0.2lf\n",ans);
}



day2

T1

题目链接

这道题用c[i][j]=(c[i-1][j]+c[i-1][j-1])%k O(n^2)计算出组合数,然后用二维前缀和维护举证中0的个数,然后直接查询就能得到答案。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 2003
using namespace std;
int c[N][N],t,k,n,m,sum[N][N];
int main()
{
	freopen("problem.in","r",stdin);
	//freopen("problem.out","w",stdout);
	scanf("%d%d",&t,&k);
	for(int i=0;i<=2000;i++)  c[i][0]=1;
	for (int i=1;i<=2000;i++)
	 for (int j=1;j<=i;j++)
	  c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
	for (int i=0;i<=2000;i++)
	 for (int j=i+1;j<=2000;j++) c[i][j]=-1;
	for (int i=0;i<=2000;i++)
	 for (int j=0;j<=2000;j++){
	 	 if (c[i][j]==0) sum[i][j]++;
	 	 if (i-1>=0)  sum[i][j]+=sum[i-1][j];
		 if (j-1>=0)  sum[i][j]+=sum[i][j-1];
		 if (i-1>=0&&j-1>=0) sum[i][j]-=sum[i-1][j-1];
	 }
	for (int i=1;i<=t;i++){
		scanf("%d%d",&n,&m);
		printf("%d\n",sum[n][m]);
	}
}

T2

题目链接

这道题用优先队列大概可以得到60-80不等。

正解就是开三个普通的队列,a存储最初的蚯蚓的长度(需要先排序),b存储[px],c存储x-[px]

那么每次选择三个队列中未被分割的最长的蚯蚓进行分割,然后将砍成两半的蚯蚓分别扔到b,c队列中。每次都这样做显然三个队列中蚯蚓的长度是单调不增的,那么对于剩余的蚯蚓+q,我们对于每次要扔进队列的蚯蚓-q,这样大小关系是不变的。最后查询的答案+m*q

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 7000300 
using namespace std;
int n,m,t,q,v,u;
int ans[N*2],a[N],b[N],c[N],num[3],h[3];
int cmp(int x,int y)
{
	return x>y;
}
int calc()
{
	int maxn=-0x7fffffff;
	if (h[0]<=num[0]) maxn=max(maxn,a[h[0]]);
	if (h[1]<=num[1]) maxn=max(maxn,b[h[1]]);
	if (h[2]<=num[2]) maxn=max(maxn,c[h[2]]);
	if (maxn==a[h[0]]&&h[0]<=num[0]) ++h[0];
	else if (maxn==b[h[1]]&&h[1]<=num[1]) ++h[1];
	else ++h[2];
	return maxn;
}
int main()
{
	freopen("earthworm.in","r",stdin);
	//freopen("earthworm.out","w",stdout);
	scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t); 
	memset(a,0,sizeof(a));
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1,cmp); 
	h[0]=h[1]=h[2]=1; num[0]=n;
	double p=(double)u/v; 
	for (int i=1;i<=m;i++) {
		ans[i]=calc()+(i-1)*q; 
		int len=floor((double)ans[i]*p);  int len1=ans[i]-len;
		len-=i*q; len1-=i*q; 
		b[++num[1]]=len;
		c[++num[2]]=len1;
	}
	for (int i=t;i<=m;i+=t) 
	{
	  printf("%d",ans[i]);
	  if ((i/t+1)*t<=m) printf(" ");
    }
    printf("\n");
	for (int i=1;i<=n+m;i++){
		int ans1=calc()+m*q;
		if (i%t==0) {
		  printf("%d",ans1);
		  if ((i/t+1)*t<=m+n) printf(" ");
	    }
	}
}

T3

题目链接

这道题是个状压dp.

因为至少需要两点才能确定一条抛物线,那么我们预处理出每两点确定的抛物线都经过了那些点,将该信息进行状压记为s[i][j]

dp[i]表示打掉i状态的猪最少需要的打的次数,然后i=0...(1<<n)-1依次进行转移。

还需要枚举两只猪确定一条抛物线dp[i|s[u][v]]=min(dp[i|s[u][v]],dp[i]+1)

这样的时间复杂度是O(2^n*n^2)

因为所有的猪都要被消灭掉,所我们固定u为未被消灭的编号最小的猪,然后枚举v,时间复杂度为O(2^n*n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100
#define esp 1e-9
using namespace std;
double x[N],y[N];
int n,m,t,ans,vis[N],cnt,s[N][N],dp[1<<20];
double calc(int i,int j)
{
	double x1=x[i]*x[i]; double x11=x[i];
	double x2=x[j]*x[j]; double x22=x[j];
	double y1=y[i]*x22; double y2=y[j]*x11;
	x1*=x22; x2*=x11;
	if (x1-x2==0) return 1;
	return (y1-y2)/(x1-x2);
}
bool pd(int i,double a,double b)
{
	if (abs(x[i]*x[i]*a+b*x[i]-y[i])<=esp) return 1;
	else return 0;
}
void solve()
{
    for (int i=1;i<=n-1;i++)
     for (int j=i+1;j<=n;j++)
     {
       	 double a=calc(i,j);
       	 if (a>=0) continue;
       	 double b=(y[i]-x[i]*x[i]*a)/x[i];
       	 for (int k=1;k<=n;k++)  
       	  if (pd(k,a,b)) s[i][j]|=(1<<(k-1));
       }
}
int main(){
	freopen("testdata.in","r",stdin);
	//freopen("angrybirds.out","w",stdout);
	scanf("%d",&t);
	for (int T=1;T<=t;T++){
		scanf("%d%d",&n,&m);
		for (int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
		ans=n;
		if (m==1)  ans=ceil(n/3+1);
		memset(dp,127/3,sizeof(dp));
		int inf=dp[0];
		memset(s,0,sizeof(s));
		solve();
		dp[0]=0;
		dp[(1<<n)-1]=n;
		for (int i=0;i<(1<<n);i++)
		 if (dp[i]!=inf) {
		    int size=0;
		    for (int j=0;j<n;j++) if (!((i>>j)&1)) size++;
		    dp[(1<<n)-1]=min(dp[(1<<n)-1],dp[i]+size);
		    int pos=0;
		 	for (int j=n;j>=1;j--)
		 	 if (!((i>>(j-1))&1)) {
		 	 	pos=j;
		 	 	break;
			  }
		    for (int k=1;k<=n;k++)  dp[i|s[k][pos]]=min(dp[i|s[k][pos]],dp[i]+1);
		 }	
		printf("%d\n",dp[(1<<n)-1]);
	}
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值