NOIP2016Day2总结

就个人觉得,Day2比Day1良心,废话不多说,看题吧

T1 组合数问题

题目

题目传送门:luogu2822

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析

这道题告诉我们组合数的性质二:c[n][m]=c[n-1][m]+c[n-1][m-1]

(附:性质一:c[n][m]=c[n][n-m],c[n][0]=1)

咦?眼熟?这不是杨辉三角嘛!
在这里插入图片描述

用c[i][j]存组合数(杨辉三角)
最后再维护一个dp(存入有多少是k的倍数)前缀和就可以啦,(别忘了减去重复的)

看图:看颜色对应

在这里插入图片描述

递推式:

dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];

等等,+=?因为dp[i][j]本身对应的c[i][j]也可能整除k,还没加上本身的一个(也就是紫色框框);

放上丑丑的代码:

代码

/********************
User:Mandy
Language:c++
Problem:D2T1 problem
********************/

#include<bits/stdc++.h>
using namespace std;

const int maxn=2e3+5;

int n,m,c[maxn][maxn],dp[maxn][maxn],t,k;

template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char C=getchar();
	while(C<'0'||C>'9') {f|=(C=='-');C=getchar();}
	while(C>='0'&&C<='9') {x=(x<<1)+(x<<3)+(C^48);C=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("problem.txt","r",stdin);
}

void readdata()
{
	read(t);read(k);
}

void init()//先打表
{
	c[0][0]=1;c[1][0]=1;c[1][1]=1;
	for(int i=2;i<=2000;++i)
	{
		c[i][0]=1;
		for(int j=1;j<=i;++j)
		{
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
			if(!c[i][j]) ++dp[i][j];
			dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
		}
		dp[i][i+1]=dp[i][i];//注意继承,下一行计算dp[i+1][i+1]时要用
	}
}
/*
void debug()
{
	for(int i=0;i<=10;++i)
	{
		for(int j=0;j<=i;++j)
			printf("%d ",dp[i][j]);
		putchar('\n');
	}
	
}
*/
void work()
{
	init();
//	debug();
	while(t--)//打表后就可以求解啦
	{
		read(n);read(m);
		if(m>n)m=n;
		put(dp[n][m]);
		putchar('\n');
	}
}

int main()
{
//	docu();
	readdata();
	work();
	return 0;
}

T2 蚯蚓

题目

题目传送门:luogu2827
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

分析

65分
乍一看,一道排序的题,用优先队列嘛

可以直接用优先队列,不过只有65分(性价比很高了,用优先队列简单嘛)

弹出队首蚯蚓,切了后又压入队列,如果时间是 t 的倍数,输出切之前的的长度

还有一个小技巧,每秒除了被切的蚯蚓,其余的都要长长,那么也就可以看为被切后的蚯蚓相对缩小了,只是在输出与切蚯蚓时要加回原来的长度

切完后,按顺序输出排名是 t 的倍数的蚯蚓长度,就像这样:

	priority_queue<int>q;
    for(int i=1;i<=n;++i) read(u),q.push(u);
     
    for(int i=1;i<=m;++i)
    {
        u=q.top()+sum;
        if(!(i%t))printf("%d ",u);
        q.pop(); 
        sum+=qi;
        v=(int)(p*u);
        q.push(v-sum);q.push(u-v-sum); 
    }
    putchar('\n'); 
    int cnt=0; 
    while(!q.empty())
    {
        if((++cnt)%t==0)printf("%d ",q.top()+sum);
        q.pop();     
    } 

100分

注意到本题本身具有一定的单调性,先被切的蚯蚓一定长于后被切的蚯蚓(重点是都被切过了)

于是拿三个队列(我是手工队列)

q1[maxn]:存入未切的蚯蚓(读入后sort从大到小排序)
q2[maxm<<1]:存入切掉的蚯蚓较短的一段
q3[maxm<<1]:存入切掉的蚯蚓较长的一段

取蚯蚓的时候就比较三个队首

		int x=-inf,x1,x2,judge=0;
		if(l1!=r1) {x=q1[l1];judge=1;}
		if(l2!=r2&&q2[l2]>x) {x=q2[l2];judge=2;}
		if(l3!=r3&&q3[l3]>x) {x=q3[l3];judge=3;}
		switch(judge){
			case 1:{++l1;break;}
			case 2:{++l2;break;}
			default:{++l3;break;}
		}

切的时候加回原长,压入队列的时候又要剪掉(因为又多了一秒,减掉时多减一个q)

		int Q=q*i;
		x+=Q-q;
		x1=floor(x*p);x2=x-x1;
		x1-=Q;x2-=Q;
		q2[r2++]=min(x1,x2);
		q3[r3++]=max(x1,x2);
		if(i%t==0) {put(x);putchar(' ');}

代码

100分

/********************
User:Mandy
Language:c++
Problem:D2T2 earthworm
********************/

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;
const int maxm=7e6+5;
const int inf=2100000000;

int n,m,q,u,v,t;
int l1,r1,l2,r2,l3,r3;
int q1[maxn],q2[maxm<<1],q3[maxm<<1];
double p;

template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char c=getchar();
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("earthworm.txt","r",stdin);
}

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

void readdata()
{
	read(n);read(m);read(q);read(u);read(v);read(t);
	p=(double)u/v;
	for(int i=0;i<n;++i) read(q1[i]);
	sort(q1,q1+n,cmp);
}

void work()
{
	l2=r2=0;l3=r3=0;
	l1=0;r1=n;
	for(int i=1;i<=m;++i)
	{
		int x=-inf,x1,x2,judge=0;
		if(l1!=r1) {x=q1[l1];judge=1;}
		if(l2!=r2&&q2[l2]>x) {x=q2[l2];judge=2;}
		if(l3!=r3&&q3[l3]>x) {x=q3[l3];judge=3;}
		switch(judge){
			case 1:{++l1;break;}
			case 2:{++l2;break;}
			default:{++l3;break;}
		}
		int Q=q*i;
		x+=Q-q;
		x1=floor(x*p);x2=x-x1;
		x1-=Q;x2-=Q;
		q2[r2++]=min(x1,x2);
		q3[r3++]=max(x1,x2);
		if(i%t==0) {put(x);putchar(' ');}
	}
	putchar('\n');
	int i=0;
	while(l1!=r1||l2!=r2||l3!=r3)
	{
		++i;
		int x=-inf,judge=0;
		if(l1!=r1) {x=q1[l1];judge=1;}
		if(l2!=r2&&q2[l2]>x) {x=q2[l2];judge=2;}
		if(l3!=r3&&q3[l3]>x) {x=q3[l3];judge=3;}
		switch(judge){
			case 1:{++l1;break;}
			case 2:{++l2;break;}
			default:{++l3;break;}
		}
		if(i%t==0) {put(x+q*m);putchar(' ');}
	}
	putchar('\n');
}

int main()
{
//	docu();
	readdata();
	work();
	return 0;
}

T3 愤怒的小鸟

题目

题目传送门:luogu2831

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

分析

方法多样,可以去洛谷题解上看,讲的很详尽,我也深受启发:
传送门:luogu愤怒的小鸟题解

注意抛物线的二次项系数必须小于0

1. DFS

一看数据范围,那肯定在暴搜与状压中间选,先来暴搜吧

预备

先看各个变量的意义:

const int maxn=20;
const double range=1e-8;
//注意精度, 实数判等不能用“==” ,把误差控制在1e-8
int t,n,m;
int ind[maxn];
//independent-表示目前独立的猪的编号 
int ans;
struct node
{
	double x,y; 
}pig[maxn],par[maxn];
//parabola-抛物线,存入已用的抛物线的a,b
//pig-猪的坐标 

确定所带的参数

void dfs(int now,int sum,int lef)
//now-现在正在搜的猪的编号
//sum-已构造的抛物线的数量
//lef-前面剩余的未组队的单独猪的数量

两个判断函数:


bool judge(double x,double y)//x,y,是否相等
{
	return fabs(x-y)<range;
}

bool inpar(double x,double y,double a,double b)//x,y是否在y=a*x*x+b*x上
{
	double ny=a*x*x+b*x;
	return judge(ny,y);//注意double 
}

核心

剪枝+结果:

	if(sum+lef>=ans) return;
	/*最优性剪枝
	即使后面的每一只猪都被当前已构造的抛物线击中,
    或者与其它单独的猪组成抛物线,抛物线的总数量还是(sum+lef),不会减少,
    所以如果抛物线的总数量已经大于等于当前的最优解时,搜下去也不会
    比当前更优了,就不用继续搜下去了。*/
	if(now>n) {ans=sum+lef;return;}

暴搜的核心有三步

第一步:判断是否被已有的抛物线打中,若打中,就直接继续搜

	bool inpara=0;
	for (int i=1;i<=sum;++i) 
	if(inpar(x,y,par[i].x,par[i].y)){dfs(now+1,sum,lef);inpara=1;break;}
	//如果在抛物线上就搜下去 
	if(inpara) return;

第二步:从前面单独的猪中选一个组队

	for(int i=1;i<=lef;++i)
	{
		int u=ind[i];
		double x2=pig[u].x,y2=pig[u].y; 
		if(judge(x,x2)) continue;
		
		double a=(y*x2-y2*x)/(x*x2*(x-x2));
		double b=y/x-a*x;
		if(a>=0) continue;
		
		par[sum+1].x=a;
		par[sum+1].y=b;//不必回溯,会覆盖 
		for(int j=i;j<lef;++j) ind[j]=ind[j+1];//删除单独猪
		dfs(now+1,sum+1,lef-1);
		for(int j=lef;j>i;--j) ind[j]=ind[j-1];
		ind[i]=u;//回溯,加上这个单独猪
	}

第三步:暂时不与前面的猪组队,自己先单着,伺机与后面的猪组队

	ind[lef+1]=now;
	dfs(now+1,sum,lef+1);

2. 状压

感谢这篇博客给我的思路:
NOIP2016Day2T3愤怒的小鸟(状压dp) O(2n*n2)再优化

预备

先看变量:

int par[maxn][maxn],dp[1<<maxn];
//状态:1表示打到,0表示没打到
//par[i][j]-经过第i、j只猪的抛物线所经过的所有猪的状态
//dp[S]-打到的猪的状态为S时所需要的最少鸟数

两个判断函数同上

预处理:

处理出所有至少经过两只猪的抛物线

		for(int i=1;i<=n;++i)
			for(int j=i+1;j<=n;++j)
			{
				par[i][j]=0;
				double x=pig[i].x,y=pig[i].y,x2=pig[j].x,y2=pig[j].y;
				if(judge(x,x2)) continue;
				
				double a=(y*x2-y2*x)/(x*x2*(x-x2));
				if(a>=0) continue;
				double b=y/x-a*x;
				par[i][j]|=1<<(i-1);par[i][j]|=1<<(j-1);
				
				for(int k=j+1;k<=n;++k) if(inpar(pig[k].x,pig[k].y,a,b)) par[i][j]|=1<<(k-1);
			}

		for(int i=1;i<=MAXN;++i) dp[i]=inf;
		dp[0]=0;

核心

状态转移:

dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);
dp[i|par[j][k]]=min(dp[i]+1,dp[i|par[j][k]])

状压:

		for(int i=0;i<=MAXN;++i)//枚状态
			for(int j=1;j<=n;++j)//枚举找出第一个i状态里不包含的猪
				if((i&(1<<(j-1)))==0)//这里可以在while循环外打表存储,这里就可以直接判断 
				{
					if(dp[i|(1<<(j-1))]>dp[i]+1) dp[i|(1<<(j-1))]=dp[i]+1;
					for(int k=j+1;k<=n;++k)
					{
						double x=pig[j].x,y=pig[j].y,x2=pig[k].x,y2=pig[k].y;
						if(judge(x,x2)) continue;
						if(dp[i|par[j][k]]>dp[i]+1) dp[i|par[j][k]]=dp[i]+1;
					}
					break;
					//因为必须要有一条抛物线经过j,所以不跳过,后面直接从有j开始搜
					//如果跳过j后来还要补回来,重复算了
					//加一个break从 3565ms 到736ms 
				}

3. 玄学随机

最快的方法

用random_shuffle随机排猪的坐标,然后,挨个组队,算出所需的小鸟数,最后比较最优答案

方法思想简单,看代码就好

我觉得思想与状压有那么一丢丢的相似之处,但不同的是,状压是枚举状态,随机直接枚举未打中的猪

代码

1. DFS

/********************
User:Mandy
Language:c++
Problem:D2T1 bird
********************/
//这道题注意double不要赋成了int 
#include<bits/stdc++.h>
using namespace std;

const int maxn=20;
const double range=1e-8;
//注意精度, 实数判等不能用“==” 
int t,n,m;
int ind[maxn];
//independent-表示目前独立的猪的编号 
int ans;
struct node
{
	double x,y; 
}pig[maxn],par[maxn];
//parabola-抛物线,存入已用的抛物线的a,b
//pig-猪的坐标 
template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char c=getchar();
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("bird.txt","r",stdin);
}

void readdata()
{
	read(t);
}

bool judge(double x,double y)
{
	return fabs(x-y)<range;
}

bool inpar(double x,double y,double a,double b)
{
	double ny=a*x*x+b*x;
	return judge(ny,y);//注意double 
}

void dfs(int now,int sum,int lef)
{
	double x=pig[now].x,y=pig[now].y;

	if(sum+lef>=ans) return;
	/*最优性剪枝,因为即使后面的每一只猪都被当前已构造的抛物线击中,
    或者与其它单独的猪组成抛物线,抛物线的总数量还是(sum+lef),不会减少,
    所以如果抛物线的总数量已经大于等于当前的最优解时,搜下去也不会
    比当前更优了,就不用继续搜下去了。*/
	if(now>n) {ans=sum+lef;return;}
	
	bool inpara=0;
	for (int i=1;i<=sum;++i) 
	if(inpar(x,y,par[i].x,par[i].y)){dfs(now+1,sum,lef);inpara=1;break;}//如果在抛物线上就搜下去 
	if(inpara) return;
	
	for(int i=1;i<=lef;++i)
	{
		int u=ind[i];
		double x2=pig[u].x,y2=pig[u].y; 
		if(judge(x,x2)) continue;
		
		double a=(y*x2-y2*x)/(x*x2*(x-x2));
		double b=y/x-a*x;
		if(a>=0) continue;
		
		par[sum+1].x=a;
		par[sum+1].y=b;//不必回溯,会覆盖 
		for(int j=i;j<lef;++j) ind[j]=ind[j+1];
		dfs(now+1,sum+1,lef-1);
		for(int j=lef;j>i;--j) ind[j]=ind[j-1];
		ind[i]=u;	
	}

	ind[lef+1]=now;
	dfs(now+1,sum,lef+1);
}

void work()
{
	while(t--)
	{
		read(n);read(m);
		for(int i=1;i<=n;++i) scanf("%lf%lf",&pig[i].x,&pig[i].y);
		ans=100000;
		dfs(1,0,0);
		printf("%d\n",ans);
	}
}

int main()
{
//	docu();
	readdata();
	work();
	return 0;
}

2. 状压

/********************
User:Mandy
Language:c++
Problem:D2T1 bird
********************/
//这道题注意double不要赋成了int 
#include<bits/stdc++.h>
using namespace std;

const int maxn=20;
const int inf=666666;
const double range=1e-8;

int t,n,m,ans;
int par[maxn][maxn],dp[1<<maxn];
struct node
{
	double x,y; 
}pig[maxn];
//parabola-抛物线 
template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char c=getchar();
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("bird.txt","r",stdin);
}

bool judge(double x,double y)
{
	return fabs(x-y)<range;
}

bool inpar(double x,double y,double a,double b)
{
	double ny=a*x*x+b*x;
	return judge(ny,y);
}

void work()
{
	read(t);
	while(t--)
	{
		read(n);read(m);
		int MAXN=(1<<n)-1;
		for(int i=1;i<=n;++i) scanf("%lf%lf",&pig[i].x,&pig[i].y);
		
		for(int i=1;i<=n;++i)
			for(int j=i+1;j<=n;++j)
			{
				par[i][j]=0;
				double x=pig[i].x,y=pig[i].y,x2=pig[j].x,y2=pig[j].y;
				if(judge(x,x2)) continue;
				
				double a=(y*x2-y2*x)/(x*x2*(x-x2));
				if(a>=0) continue;
				double b=y/x-a*x;
				par[i][j]|=1<<(i-1);par[i][j]|=1<<(j-1);
				
				for(int k=j+1;k<=n;++k) if(inpar(pig[k].x,pig[k].y,a,b)) par[i][j]|=1<<(k-1);
			}
		
		for(int i=1;i<=MAXN;++i) dp[i]=inf;
		dp[0]=0;
		
		for(int i=0;i<=MAXN;++i)
			for(int j=1;j<=n;++j)
				if((i&(1<<(j-1)))==0)//这里可以在while循环外打表存储,这里就可以直接判断 
				{
					if(dp[i|(1<<(j-1))]>dp[i]+1) dp[i|(1<<(j-1))]=dp[i]+1;
					for(int k=j+1;k<=n;++k)
					{
						double x=pig[j].x,y=pig[j].y,x2=pig[k].x,y2=pig[k].y;
						if(judge(x,x2)) continue;
						if(dp[i|par[j][k]]>dp[i]+1) dp[i|par[j][k]]=dp[i]+1;
					}
					break;
					//因为必须要有一条抛物线经过j,所以不跳过,后面直接从有j开始搜
					//如果跳过j后来还要补回来,重复算了
					//加一个break从 3565ms 到736ms 
				}
		printf("%d\n",dp[MAXN]);
	}
}

int main()
{
//	docu();
	work();
	return 0;
}

3. 玄学随机

/********************
User:Mandy
Language:c++
Problem:D2T1 bird
********************/

//玄学随机 
//这道题注意double不要赋成了int 
#include<bits/stdc++.h>
using namespace std;

const int maxn=20;
const int inf=666666;
const double range=1e-8;

int t,n,m,ans,tot;
bool vis[maxn];
struct node
{
	double x,y; 
}pig[maxn];
//parabola-抛物线 
template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char c=getchar();
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("bird.txt","r",stdin);
}

bool judge(double x,double y)
{
	return fabs(x-y)<range;
}

bool inpar(double x,double y,double a,double b)
{
	double ny=a*x*x+b*x;
	return judge(ny,y);
}

void work()
{
	ans=666666;
	
	read(n);read(m);
	for(int i=1;i<=n;++i) scanf("%lf%lf",&pig[i].x,&pig[i].y);
	int times=80;//设大一点保险 
	while(times--)
	{
		memset(vis,0,sizeof(vis));tot=0; //注意初始化
		random_shuffle(pig+1,pig+n+1);
		
		for(int i=1;i<=n;++i)//枚举猪
			if(!vis[i])
			{
				vis[i]=1;
				for(int j=i+1;j<=n;++j)//枚举下一个未被打中的猪
				if(!vis[j])
				{
					double x=pig[i].x,y=pig[i].y,x2=pig[j].x,y2=pig[j].y;
					if(judge(x,x2)) continue;
					
					double a=(y*x2-y2*x)/(x*x2*(x-x2));
					double b=y/x-a*x;
					if(a>=0) continue;
					
					vis[j]=1;
					
					for(int k=j+1;k<=n;++k) if(inpar(pig[k].x,pig[k].y,a,b)) vis[k]=1;//看这条抛物线还可以打掉那只猪
					
					break;//一只猪只组一次队
				}
				++tot;//计量抛物线
			}
		ans=min(ans,tot);
		if(ans==1) break;
	}
	put(ans);
	putchar('\n');
}

int main()
{
//	docu();
	read(t);
	while(t--) work();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值