NOIP2016——纪念第一次水过的一等

这里就讲一下两天后四道题的做法吧。前两题,实在是比较简单(Day2T1,打个对拍,结果把暴力打错了,改错了= =,WA了60分QAQ)


Day1T3

为什么先是T3呢?因为T2太经典了,最后再讲):

其实是一个很裸的Floyd+概率Dp,但是期望类的Dp方程,没写过一道题,还真写不出来。Floyd就不用说了吧?先把两两点距算出来,然后考虑一个Dp,我们类似一个背包地设,dp[i][j][0]表示前i天,换j次,并且第i天不换的最小期望距离。那么dp[i][j][1]就表示前i天换j次,且第i天换的最小期望距离。

再设cc表示从c[i-1]到c[i]的距离,那么cd,dd,dc类似。

那么考虑一下转移:

dp[i][j][0]=min(dp[i-1][j][0]+cc,dp[i-1][j-1][1]+p[i-1]*dc+(1-p[i-1])*cc)这个期望一定要两个都要考虑啊(所以对于数学蒟蒻就有点坑)

!前方高能!

dp[i][j][1]=min(dp[i-1][j-1][0]+p[i]*cd+(1-p[i])*cc,dp[i-1][j-1][1]+p[i-1]*p[i]*dd+(1-p[i-1])*p[i]*cd+p[i-1]*(1-p[i])*dc+(1-p[i-1])*(1-p[i])*dd);

这个方程写下来差点憋死了= =

从这里我们也可以看到一点期望类转移方程的套路,就是这一步不仅要考虑当前的选择概率,同时也要考虑前一次选择的概率,应该不会有两种以上的选择,那就是后面有8*3个常数项(可怕)

那么这道题就差不多了。代码放在最下面。


Day2T3

怎么又是T3,T2又死哪去了?):

其实还是一个非常裸的状态压缩Dp,这次NOIP看似整体难度非常高,但实际上只是把题目难度次序重新编排了一下,导致大多数人被坑死在第二题。第三题都是非常良心的Dp,如果不给第二题,应该会有许多人都可以想到两天T3的正解。

这个n这么小,最大只有18,所以90%跟二进制有关系,事实上这个推断总是成立的。不知道二进制Dp的出门左转Baidu。

设dp[S]表示已经打了的猪的的序号组成的集合,那么我们枚举一个i表示这一次要打个猪,然后再枚举一个j,表示这一次把i和j一起打掉,那么预处理一个bit数组使得bit[i][j]表示以i和j的坐标确定的抛物线可以打掉的所有的猪,那么就由dp[S]+1转移到了dp[S|bit[i][j]]了,最后的答案就是dp[2^n-1].

考试的时候,策略上严重失误。我两天都把两个小时砸在第二题上了,然后第二天还好一点,T2一个线段树拿到了应有的暴力分,而第一天T2有60分的骗分都没打,只弄了个25的纯暴力,太可惜了。然后两天的T3几乎BoomZero(BaoLing),这明明属于普及压轴题的难度,我却没有仔细看。可叹。


Day2T2

这顺序有点乱):

这个我当时觉得堆也是可以打得,但是把q做一个懒处理,可能会出问题,所以就打了个线段树,直接暴力搞。但这样就离正解远了。做堆的时候,其实可以发现,我们完全无需可以维护一个堆,那么期望复杂度就是O((n+m)log(n+m)),我们可以分成三个堆,分别维护原序列,分的第一段,分的第二段,那么每次就是在三个堆顶中取一个最大的分,然后插入到后面两个堆中去,期望复杂度O(3nlogn),这么做当时考场上就有人想到这个小优化,并且这么写了,100分(太可怕了,他是卡常过的)。按照理论复杂度,这个是过不掉的,但是这个理论复杂度只是表面现象,下面可以证明这个做法期望复杂度是可以做到O(3n)的!

首先讲一下对q这个类似线段树区间修改懒标记的处理,

我们如果我们首先把初始序列排序使得:

a1>=a2>=a3>=a4.....>=an

那么我们先切a1,分成两个b1=a1*p,c1=a1-b1

不妨设b1>c1(如果b1<c1,swap一下就好了)

我们设置一个addv表示加了多少次q,那么我们每次向堆中找出一个最长的,先把它加上addv(因为我们之前一直没有给它加q,所以这里我们一次性加上),把它输出,然后把addv+=q,再把它分成两段,把两段减去减去addv,然后插入到新的堆中去。

为什么这么处理是正确的呢?我们考虑一下切断的影响就好了——使得这两段在这个1s中没有增加长度,所以我们维护一个addv后,我们只需令这两段都减去q,那么较其他段,他们就是少加了一次q,可以再自己仔细想一想。

但是,为了证明上述做法的结果是O(n)的,我们下面的论述中,不引入懒优化!

这里,如果第二次我们切的是上述的b1(即b1>a2+q),分成的两段显然小于b1和c1,

如果我们切的是a2+q,那么就有b1+=q,c1+=q,b2=(a2+q)*p,c2=a2+q-b2(同理,不妨设b2>c2)

那么b2=(a2+q)*p=a2*p+q*p<=a1*p+q=b1。(a2<a1,p<=1)

综上可知,我们切的线段长度是单调不上升的,那么我们上面实际上只有原始序列的堆是O(logn)的效率取出,而剩下的两个堆,就是堆顶是最长的,并且后面的都是“排序”好了的,那么问题就简单了,我们维护三个单调队列就好了。

思路到这里就十分清晰了,具体实现可参考下面的代码。


Day1T2

终于来到了最精彩的一题,这恐怕是第一道上了BZOJ的NOIP题,也是第一道在各大题库中,基本都是省选+的难度标签。

这题考察了三个技巧的嵌套,即树上差分+LCA+桶形线段树(也可以是树链剖分)。

这三个技巧我在考场上都想到了,但是都没有同时想到,没有想到把他们放在一起用,就是说,我先想到了LCA,发现做不出来,然后就考虑弄个树链剖分,发现还是不行,于是另寻它法,我接着尝试直接在S和T打+1的标记,然后在LCA打-1的标记,发现并没有什么用,然后我有苦苦思索了2个小时,最终放弃,骗分都没打全。

首先考虑LCA,因为毕竟是一个树上的路径,所以我们把其拆分成S到LCA,然后LCA到T的两条链考虑就简单多了。

这题最重要的一点就是不能局限在枚举路径所能产生的贡献的观察员,这样复杂度始终是O(nm)的!

我们考虑在一个路径S到LCA中,一个观察员i(i在S到LCA的路径上,不然无意义)满足什么条件才能观察到这个路径。

易有:dep[S]=dep[i]+w[i](即从S开始,刚好在w[i]的时间到达i,要知道,在树上,同一链上两点之间的深度之差就是两点之间的距离)

而dep[i]+w[i]是一个定值!!!

这是一个定值!这是一个定值!

那么我们就是统计在i的子树中,有多少个路径起点S满足dep[S]=dep[i]+w[i](这时就属于树链剖分的模板题了,但也需要差分,并且过于复杂,没有桶形线段树好)!那么我们考虑设立一个桶root[x],表示当x=dep[i]+w[i]时,i的子树中路径起点满足上述条件的个数即为root[x]。可这些S并不都是有贡献的(可能没到i就走转折方向上去了),那怎么消除LCA作为终点的影响呢?

那么我们就需要差分,我们需要考虑如何将树上的点转化为一个区间从而进行差分。一种当然就是树链剖分,另一种就是树最神奇的DFS序!我们对这个树进行一个dfs遍历(先序遍历),然后我们记录第一次到一个节点的时间戳in[u],记录离开这个点,去往它的兄弟的时间戳out[u],那么这个节点的子树的in时间戳就一定是[in[u],out[u]]这段连续的区间,那么我们把上述的桶改为一个线段树,这个线段树的写法十分类似于主席树(可以看看我的其他blog),需要动态的建点(不然绝对MLE),对于一个路径,我们在root[dep[S]]这个线段树中,在in[S]这个点+1,在in[LCA]这个点-1(in[S]>in[LCA]),那么如果一个点i,其in[i]<in[LCA],即LCA是i的子树中的节点,那么这个路径显然对i不会产生贡献。如果其in[S]>in[i]>in[LCA],那么这个路径可能会对i产生贡献,我们只需查询root[dep[i]+w[i]]这个线段树中in[i]~out[i]这段区间和就好了。如果其in[i]>in[S],那么i就是S子树中的一个节点或是S的兄弟节点,总之这个路径对i也不会有贡献。

对于LCA~T的处理类似,这段路径上会有贡献的i,只会是:

dep[S]+dep[i]-2*dep[LCA]=w[i]

即dep[S]-2*dep[LCA]=w[i]-dep[i]

与S~LCA处理类似。


参考代码:

Day1T2:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define re register
#define REP(i,a,b) for (re int i=(a);i<=(b);++i)
#define For(i,x) for (re int i=(x);i;i=e[i].next)
#define clear(a) memset((a),0,sizeof((a)))
using namespace std;
const int N=304001;
int dfs_clock=0,EdgeCnt=0;
int n,m,Bit,Tot=0;
struct Edge{
	int to,next;
}e[N<<1];
int dep[N],f[N][25],in[N],out[N];
int root[N*3],a[N],w[N],ret[N];
struct SegTree{
	int L,R,sum;
}T[N*25];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
inline void adde(int u,int v){
	int &p=EdgeCnt;
	e[++p].to=v;e[p].next=a[u];a[u]=p;
	e[++p].to=u;e[p].next=a[v];a[v]=p;
}
inline int query(int u,int v){
	if (dep[u]<dep[v])swap(u,v);
	for (re int j=21;j>=0;j--)
		if (dep[f[u][j]]>=dep[v])u=f[u][j];
	if (u==v)return u;
	for (re int j=21;j>=0;j--)
		if (f[u][j]!=f[v][j])u=f[u][j],v=f[v][j];
	return f[u][0];
}
inline void adpre(int u){
	in[u]=++dfs_clock;
	For(p,a[u]){
		int v=e[p].to;
		if (v!=f[u][0]){
			f[v][0]=u;
			dep[v]=dep[u]+1;
			adpre(v);
		}
	}
	out[u]=dfs_clock;
}
struct Question{
	int s,t,Lca,Q;
	inline void in(){
		s=read(),t=read(),Lca=query(s,t);
		Q=dep[s]-2*dep[Lca];
	}
}q[N];
inline void insert(int &p,int ql,int x,int l=1,int r=n){
	if (!ql)return;if (!p)p=++Tot;
	T[p].sum+=x;
	if (l==r)return;
	int mid=(l+r)>>1;
	if (ql<=mid)insert(T[p].L,ql,x,l,mid);
		else insert(T[p].R,ql,x,mid+1,r);
}
inline int count(int p,int ql,int qr,int l,int r){
	if (!p)return 0;
	if (ql<=l && r<=qr)return T[p].sum;
	int mid=(l+r)>>1,s=0;
	if (ql<=mid)s=count(T[p].L,ql,qr,l,mid);
	if (mid<qr)s+=count(T[p].R,ql,qr,mid+1,r);
	return s;
}
int main(){
	n=read(),m=read();
	REP(i,1,n-1)adde(read(),read());
	REP(i,1,n)w[i]=read();
	dep[1]=1;adpre(1);
	REP(j,1,21)REP(i,1,n)f[i][j]=f[f[i][j-1]][j-1];
	REP(i,1,m)q[i].in();
	REP(i,1,m){
		int x=dep[q[i].s];
		insert(root[x],in[q[i].s],1);
		insert(root[x],in[f[q[i].Lca][0]],-1);
	}
	REP(i,1,n)ret[i]=count(root[dep[i]+w[i]],in[i],out[i],1,n);
	Bit=2*n;Tot=0;clear(root);clear(T);
	REP(i,1,m){
		int x=q[i].Q+Bit;
		insert(root[x],in[q[i].t],1);
		insert(root[x],in[q[i].Lca],-1);
	}
	REP(i,1,n)ret[i]+=count(root[w[i]-dep[i]+Bit],in[i],out[i],1,n);
	REP(i,1,n)printf("%d ",ret[i]);
	return 0;
}


Day1T3:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define re register
#define REP(i,a,b) for (re int i=(a);i<=(b);++i)
using namespace std;
typedef double db;
const db INF=1e8;
const int N=2010;
db p[N],dp[2][N][2];
int n,m,V,E,c[N],d[N];
int s[N][N],oo;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
int main(){
	n=read(),m=read(),V=read(),E=read();
	REP(i,1,n)c[i]=read();
	REP(i,1,n)d[i]=read();
	REP(i,1,n)scanf("%lf",&p[i]);
	memset(s,0x3f,sizeof(s));
	oo=s[0][0];
	REP(i,1,E){
		int u=read(),v=read(),w=read();
		s[u][v]=min(s[u][v],w);
		s[v][u]=s[u][v];
	}
	REP(k,1,V)REP(i,1,V)REP(j,1,V)
	if (s[i][k]!=oo && s[k][j]!=oo)s[i][j]=min(s[i][j],s[i][k]+s[k][j]);
	REP(i,1,V)s[i][i]=0;
	REP(i,0,1)REP(j,0,m)dp[i][j][0]=dp[i][j][1]=INF;
	dp[1][0][0]=dp[1][1][1]=0;
	REP(i,2,n){
		int cur=i&1,pre=cur^1,lc=c[i-1],ld=d[i-1],nc=c[i],nd=d[i];
		db lp=p[i-1],np=p[i],cc=(db)s[lc][nc],cd=(db)s[lc][nd],dc=(db)s[ld][nc],dd=(db)s[ld][nd];
		db r=lp*np*dd+(1.0-lp)*np*cd+lp*(1.0-np)*dc+(1.0-lp)*(1.0-np)*cc;
		dp[cur][0][0]=dp[pre][0][0]+cc;
		REP(j,1,m){
			if (j>i)break;
			dp[cur][j][0]=min(dp[pre][j][0]+cc,dp[pre][j][1]+lp*dc+(1-lp)*cc);
			dp[cur][j][1]=min(dp[pre][j-1][0]+np*cd+(1-np)*cc,dp[pre][j-1][1]+r);
		}
	}
	db ans=INF;
	REP(i,0,m)ans=min(ans,min(dp[n&1][i][0],dp[n&1][i][1]));
	printf("%.2f",ans);
	return 0;
}


Day2T2:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define re register
#define For(i,a,b) for (re int i=(a);i<=(b);i++)
#define in(a) (a)=read()
using namespace std;
template<class T>inline bool ChkMax(T &a,int b){return a<b?a=b,1:0;}
inline bool cmp(int a,int b){return a>b;}
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=7e6+6;
int c,n,m,q,u,v,t,f[N],g[N],h[N];
ll addv=0,p1=1,p2=1,p3=1,t2=0,t3=0,x;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
inline void calc(){
	x=-INF,c=0;
	if (p1<=n  && ChkMax(x,h[p1]))c=1;
	if (p2<=t2 && ChkMax(x,f[p2]))c=2;
	if (p3<=t3 && ChkMax(x,g[p3]))c=3;
	x+=addv;
	p1+=c==1;p2+=c==2;p3+=c==3;
}
int main(){
	in(n),in(m),in(q),in(u),in(v),in(t);
	For(i,1,n)in(h[i]);
	sort(h+1,h+n+1,cmp);
	For(i,1,m){
		calc();addv+=q;
		ll y=x*u/v,z=x-y;
		if (y<z)swap(y,z);
		f[++t2]=y-addv;g[++t3]=z-addv;
		if (i%t==0)printf("%lld ",x);
	}
	puts("");
	int tn=0,tot=n-p1+t2-p2+t3-p3+3;
	while (tot--){
		calc();
		if ((++tn)%t==0)printf("%lld ",x);
	}
	return 0;
}


Day2T3:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define re register
#define REP(i,a,b) for (re int i=(a);i<=(b);++i)
using namespace std;
template<class T>inline void ChkMin(T &a,T b){if (b<a)a=b;}
typedef double db;
const db eps=1e-7;
const int N=20;
int n,m,bit[N][N],dp[1<<N];
db x[N],y[N];
int main(){
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d",&n,&m);
		REP(i,1,n)scanf("%lf%lf",&x[i],&y[i]);
		REP(i,1,n)REP(j,i+1,n){
			db f=x[i]*x[j]*(x[i]-x[j]);
			db a=x[j]*y[i]-x[i]*y[j];
			db b=x[i]*x[i]*y[j]-x[j]*x[j]*y[i];
			bit[i][j]=0;
			if (a*f<0){
				REP(k,1,n)
				if (fabs(a*x[k]*x[k]+b*x[k]-f*y[k])<eps)bit[i][j]|=1<<(k-1);
			}
		}
		memset(dp,0x3f,sizeof(dp));
		dp[0]=0;
		REP(k,0,(1<<n)-1){
			int i=1;
			while ((k>>(i-1))&1)i++;
			ChkMin(dp[(1<<(i-1))|k],dp[k]+1);
			REP(j,i+1,n)ChkMin(dp[k|bit[i][j]],dp[k]+1);
		}
		printf("%d\n",dp[(1<<n)-1]);
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值