noip 2016 做题记录

玩具谜题

简单模拟,主要判断当前小人的方向与操作方向的异或值,若为1则顺时针移动,否则逆时针移动。

#include
using namespace std;

const int maxn=100005;
int n,m,cur=1;
bool d[maxn];
char s[maxn][15];

int main(){
	//freopen("input.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d%s",&d[i],s[i]);
	}
	while(m--){
		int o,p;
		scanf("%d%d",&o,&p);
		if(o^d[cur]){
			cur+=p;
			if(cur>n) cur-=n;
		}
		else{
			cur-=p;
			if(cur<1) cur+=n;
		}
	}
	printf("%s",s[cur]);
	return 0;
}

换教室

首先用floyd处理出两点之间的距离,且因为有重边,所以在读入时要进行判断。

容易想到用dp来计算期望。f[i][j][1/0]表示到 i 时间时,已经申请更换了 j 个教室,第 i 个时间段是否申请更换教室 所耗费的最小体力值。

计算顺序也很好判断,直接从1到n递推即可。对于 i 时间段,它的 f 值只与上个时间段有关。

对于f值的转移,本来我写了一大段文字,但因实在冗长,故将其删去。还是画图比较清晰。
设原课程为a[i],另一课程为b[i],换课成功概率为k[i]。
当 i 时间不申请换课,该时间的课程不变,为a[i]。该状态f[i][j][0]可由两个状态转移而来,一种是上个时间不申请,则上个节点一定为a[i-1]。
noip 2016 做题记录_第1张图片
二者所选课程的概率都为1,乘以d[a[i-1]][a[i]]即为期望体力。

若上个位置申请,则其有两种可能,一种失败,期望体力为 概率k[i-1]*路程d[a[i-1]][a[i]],一种成功,期望体力为概率1-k[i-1]*路程d[b[i-1]][a][i]]。noip 2016 做题记录_第2张图片
若 i 时间申请更换课程,同样由两种状态更新而来。
一种是上个时间不申请。
noip 2016 做题记录_第3张图片
一种是上个时间申请。这种情况要计算两个时间各自申请与否共四种情况分别的概率乘以路程。
noip 2016 做题记录_第4张图片
(话说我这图怎么越画越小了?)

刚开始做时,我只考虑了当前时间 i 的概率。但实际上申请换课的一个状态f[i][j][1]所表示的课程是不固定的,有成功和不成功两种可能,所以要分别计算概率。

#include
using namespace std;

const int maxn=2005;
const int inf=0x3f3f3f3f;
int n,m,p,q;
int d[maxn][maxn],a[maxn],b[maxn];
double k[maxn],f[maxn][maxn][2];

void floyd(){
	for(int i=1;i<=p;++i)
		for(int j=1;j<=p;++j)
			for(int k=1;k<=p;++k)
				d[j][k]=min(d[j][k],d[j][i]+d[i][k]);
}

int main(){
	//freopen("input.txt","r",stdin);
	scanf("%d%d%d%d",&n,&m,&p,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) scanf("%d",&b[i]);
	for(int i=1;i<=n;++i) scanf("%lf",&k[i]);
	memset(d,0x3f,sizeof(d));
	for(int i=1;i<=p;++i) d[i][i]=0;
	for(int x,y,z,i=1;i<=q;++i){
		scanf("%d%d%d",&x,&y,&z);
		d[x][y]=d[y][x]=min(d[x][y],z);
	}
	floyd();
	for(int i=1;i<=n;++i)
		for(int j=0;j<=m;++j) f[i][j][0]=f[i][j][1]=inf;
	f[1][0][0]=f[1][1][1]=0;
	for(int i=2;i<=n;++i){
		for(int j=0;j<=min(i,m);++j){
			f[i][j][0]=min(f[i-1][j][0]+d[a[i-1]][a[i]],f[i-1][j][1]+d[a[i-1]][a[i]]*(1.0-k[i-1])+d[b[i-1]][a[i]]*k[i-1]);
			if(j>0){
				double cmp1=f[i-1][j-1][0]+d[a[i-1]][a[i]]*(1.0-k[i])+d[a[i-1]][b[i]]*k[i];
				double cmp2=f[i-1][j-1][1]+d[a[i-1]][a[i]]*(1.0-k[i-1])*(1.0-k[i])+d[b[i-1]][a[i]]*k[i-1]*(1.0-k[i])+
							d[a[i-1]][b[i]]*(1.0-k[i-1])*k[i]+d[b[i-1]][b[i]]*k[i-1]*k[i];
				f[i][j][1]=min(cmp1,cmp2);
			}
		}
	}
	double ans=inf;
	for(int i=0;i<=m;++i)
		for(int j=0;j<=1;++j) 
			ans=min(ans,f[n][i][j]);
	printf("%.2f",ans);
	return 0;
}

组合数问题

开始以为每次 t 都要给定一个不同的 k ,没想出来。后来才看清 k 原来是不变的……话说我这不仔细读题的毛病该改改了。

数据范围2000*2000的组合数可直接爆算,组合数 f[i][j]=f[i-1][j-1]+f[i-1][j],若直接算组合数肯定会爆long long。想一想,以往数值过大我们怎么办?取模!

如何判断它为 k 的倍数?算组合数时对 k 取模,若该组合数取模后数值为0,则表明它为k的倍数,加入答案。

因为求的是所有0<=i<=n,0<=j<=min(i,m)有多少对(i , j)满足C(i , j)是 k 的倍数,即为(n,m)位置的左上方区域的答案总和。容易想到用二维前缀和维护答案。

#include
using namespace std;

typedef long long ll;
int t,k,sum,c[2005][2005];
int mul[2005][2005],ans[2005][2005];
int main()
{
    //freopen("in.txt","r",stdin);
    int n,m;
    scanf("%d%d",&t,&k);
    for(int i=0;i<=2000;i++) c[i][0]=1,c[i][i]=1;
    for(int i=1;i<=2000;i++)
    {
    	for(int j=1;j<=i;j++)
    	{
    		c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
    		ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];
    		if(!c[i][j]) ans[i][j]++;
        }
        ans[i][i+1]=ans[i][i];
    }
    while(t--)
    {
        scanf("%d%d",&n,&m);
        if(m>n) printf("%d\n",ans[n][n]);
        else printf("%d\n",ans[n][m]);
    }
    return 0;
}

蚯蚓

85分:模拟题意,容易想到用优先队列,每次取队首计算放回即可。
关于切断最长蚯蚓所得的两段,我居然没想到怎么处理乘以 p 的比例问题?因为不能直接每秒改变队内元素的值,所以要用一个sig记录共同生长的大小,取出队首时要先加上sig再乘以p。切得的两段放回队列时要与其他元素保持一致,所以要减去sig,同时还因为这一秒内这两条蚯蚓没有生长,还要减去一个q。

#include
using namespace std;

const int maxn=1e5+5;
int n,m,q,u,v,t,cnt,sig;
double p;

priority_queue<int>que;

int main(){
	//freopen("input.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
	p=(double)u/v;
	for(int x,i=1;i<=n;++i){
		scanf("%d",&x);
		que.push(x);
	}
	for(int i=1;i<=m;++i){
		int a,b,x=que.top()+sig;que.pop();
		if(i%t==0) printf("%d ",x);
		a=(double)x*p;b=x-a;
		que.push(a-sig-q),que.push(b-sig-q);
		sig+=q;
	}
	printf("\n");
	while(!que.empty()){
		++cnt;
		int x=que.top();que.pop();
		if(cnt%t==0) printf("%d ",x+sig);
	}
	return 0;
}

100分:关键点——发现此题中隐含的单调性。
85分算法超时主要是因为优先队列有一个log的复杂度,可不可以去掉这个log呢?
我们发现,对于两个蚯蚓 a、b ,设 a 先于 b 被剪断,它们按比例 p 剪得的两只蚯蚓长度 a1、a2;b1、b2(设 a1=a*p,a2=a-a1;b1、b2相同)有这样的大小关系:

a1>b1、a2>b2

首先我们知道,a 先于 b被剪断,则 a 长度必然大于 b,而按比例切断后的 a1、a2,b1、b2 也相应地有着这样的大小关系。而又因为 a1、a2 比 b1、b2 先被切出来,它们每秒钟各自生长一定长度 q ,期间 b 也在生长同样的长度 q ,但在乘上比例 p 之后我们发现b1、b2的生长速度为qp,q(1-p),其实是小于a1、a2的生长速度 q 的(建议手玩一下过程)。所以可得a1>b1、a2>b2。

由于a1、b1、c1……;a2、b2、c2……这两部分按相同比例切出的蚯蚓长度是单调的,故可直接用两个普通队列分别保存。为什么要用两个队列?因为它们二者混合比较后的的长度是不单调的,如,第一次a1最长,将其取出队列后切断。之后的b1、a2之间的长度关系就不确定了,所以要分别存储。
再加上原来还未被切过的蚯蚓,共三个队列,每次询问时将三个队列的队首都取出来比较取最大值即可。

#include
using namespace std;

int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

const int inf=0x3f3f3f3f;
const int maxn=1e5+5;
int n,m,k,u,v,t,sig,w[maxn];
double p;
queue<int>q1,q2,q3;

bool cmp(int a,int b){
	return a>b;
}

int main(){
	//freopen("input.txt","r",stdin);
	n=read(),m=read(),k=read(),u=read(),v=read(),t=read();
	p=(double)u/v;
	for(int x,i=1;i<=n;++i) scanf("%d",&w[i]);
	sort(w+1,w+1+n,cmp);
	for(int i=1;i<=n;++i) q1.push(w[i]);
	for(int i=1;i<=m;++i){
		int c1=-inf,c2=-inf,c3=-inf,a,b,c;
		if(!q1.empty()) c1=q1.front();
		if(!q2.empty()) c2=q2.front();
		if(!q3.empty()) c3=q3.front();
		if(c1>=c2&&c1>=c3) c=c1,q1.pop();
		else if(c2>=c1&&c2>=c3) c=c2,q2.pop();
		else c=c3,q3.pop();c+=sig;
		if(i%t==0) printf("%d ",c);
		a=(double)c*p;b=c-a;
		q2.push(a-sig-k),q3.push(b-sig-k);
		sig+=k;
	}
	putchar('\n');
	for(int i=1;i<=n+m;++i){
		int c1=-inf,c2=-inf,c3=-inf,c;
		if(!q1.empty()) c1=q1.front();
		if(!q2.empty()) c2=q2.front();
		if(!q3.empty()) c3=q3.front();
		if(c1>=c2&&c1>=c3) c=c1,q1.pop();
		else if(c2>=c1&&c2>=c3) c=c2,q2.pop();
		else c=c3,q3.pop();
		if(i%t==0) printf("%d ",c+sig);
	}
	return 0;
}

愤怒的小鸟

第一种做法:状压dp

f[i] 表示已被射中的猪表示为二进制下的 i 时所需的最少发射次数。

提前预处理出数组 l[i][j] ,二进制下表示这条经过 i、j 号猪的抛物线经过的所有猪。

每次从一个标号最小的且未被击中过的猪进行转移,因为这个猪迟早都要被打,不如提前打掉,否则后面会重复计算状态。

#include
using namespace std;

const double eps=1e-8;
const int inf=0x3f3f3f3f;
const int maxn=1<<20;
int q,n,m,l[20][20],f[maxn];
double x[20],y[20];

int lowbit(int x){
	int p=0;
	while(x&1) x>>=1,++p;
	return p+1;
}

int main(){
	//freopen("input.txt","r",stdin);
	scanf("%d",&q);
	while(q--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
		memset(l,0,sizeof(l));
		double a,b;
		for(int i=1;i<=n;++i){
			for(int j=i+1;j<=n;++j){
				b=(x[j]*x[j]*y[i]-x[i]*x[i]*y[j])/(x[i]*x[j]*x[j]-x[i]*x[i]*x[j]);
				a=(y[i]-x[i]*b)/(x[i]*x[i]);
				if(a>-eps) continue;
				for(int k=1;k<=n;++k){
					if(abs(a*x[k]*x[k]+b*x[k]-y[k])<=eps) l[i][j]|=(1<<(k-1));
				}
			}
		}
		memset(f,0x3f,sizeof(f));
		f[0]=0;
		for(int j,i=0;i<=(1<<n)-1;++i){
			j=lowbit(i);
			f[i|(1<<(j-1))]=min(f[i|(1<<(j-1))],f[i]+1);
			for(int k=1;k<=n;++k)
				f[i|l[j][k]]=min(f[i|l[j][k]],f[i]+1);
		}
		printf("%d\n",f[(1<<n)-1]);
	}
	return 0;
}

第二种做法:搜索

搜索的三个参数:cur 表示枚举到了第cur个猪,cnt 表示有多少条落单的猪不在抛物线上,sum 表示发射了几次鸟,即有 sum 条已有的抛物线。
搜索每只猪,对于该猪有三种情况:

  • 可被先前发射的鸟形成的抛物线直接打掉。若符合这种情况则跳过搜索其他两种可能。
  • 与先前落单的猪被一起打掉,即组成一条新的抛物线,可供击打后面的猪。
  • 落单,不与任何抛物线或其它猪组合。

剪枝:由于落单的猪已经选择互不组合,所以要打掉它们必须另外发射 cnt 次,再加上已有的抛物线数 sum 即为当前情况下的最小答案。若当前的最小答案已经大于之前已求得的答案,直接退出。即 if(sum+cnt>=ans) return;
一个细节,在求抛物线时若系数 a 大于等于零则直接跳过。两只猪的横坐标相同时也要特判并跳过。为避免精度误差,定义eps=1e-8来判断差值。

#include
using namespace std;

const double eps=1e-8;
int t,n,m,ans;
double x[20],y[20],a[20],b[20];
bool v[20];

void dfs(int cur,int cnt,int sum){
	if(sum+cnt>=ans) return;
	if(cur>n){
		ans=sum+cnt;
		return;
	}
	for(int i=1;i<=sum;++i){
		if(fabs(a[i]*x[cur]*x[cur]+b[i]*x[cur]-y[cur])<=eps){
			v[cur]=1;
			dfs(cur+1,cnt,sum);
			v[cur]=0;
			return;
		}
	}
	for(int i=1;i<cur;++i){
		if(v[i]) continue;
		b[sum+1]=(y[i]*x[cur]*x[cur]-x[i]*x[i]*y[cur])/(x[i]*x[cur]*x[cur]-x[i]*x[i]*x[cur]);
		a[sum+1]=(y[cur]-b[sum+1]*x[cur])/(x[cur]*x[cur]);
		if(fabs(x[i]-x[cur])<=eps||a[sum+1]>-eps) continue;
		v[i]=v[cur]=1;
		dfs(cur+1,cnt-1,sum+1);
		v[i]=v[cur]=0;
	}
	dfs(cur+1,cnt+1,sum);
}

int main(){
	//freopen("input.txt","r",stdin);
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(v,0,sizeof(v));//v[i]表示i猪是否落单
		ans=100;
		dfs(1,0,0);
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(Noip)