就个人觉得,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;
}