T1 组合数问题:
luoguP2822组合数问题
一句话题意:
有t组数据,如果给定n,m和k,求各组数据对于所有的0 <= i <= n,0 <= j <= min(i,m)有多少对 (i,j)满足 Cji是k的倍数。
由于k已经给出,0< m,n< 2000 因此可以直接跑杨辉三角,边跑边mod k 然后维护( 0 , 0 )到 ( i , j ) 中c[ i ][ j ]==0的前缀和即可
时间复杂度
O(n×m)
每次询问
O(1)
总复杂度
O(T×m×n)
代码如下:
c[0][0]=1;
for(int i=1;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
}
}
for(int i=1;i<=2000;i++)
{
for(int j=1;j<=2000;j++)
{
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
if(j<=i&&c[i][j]==0) sum[i][j]++;
}
}
while(t--)
{
int n=read(),m=read();
cout<<sum[n][m]<<"\n";
}
T2 蚯蚓:
题目贼长就不描述了
分析之后可以发现
每次切完取出的肯定要比前一次切的长度短
简单证明一下:
假设有a>b>a/2 每次切成两半 每秒加q
那么第一次应该取出的是a
切出来的就是a/2 a/2 b+q
下一次切b
切出来是 a/2+q 、a/2+q 、 (b+q)/2 、 (b+q)/2
显然a/2+q依然大于(b+q)/2
那么直接开三个队列 第一个排下序存初始的 第二第三个存被切成的两半
每次找队首的三个最大的
拿出来切掉
注意:
除了被切的每条每秒都增加q就相当于被切时-q最后再加上q*t
每次拿出来时都要加上q*(t-1)恢复原长再切再减q *t 放回去
取的时候要特别特别注意!!因为不是每个队里都有元素!而且有可能是负数!
for(int i=1;i<=n;i++)
{
a[i]=read();
}
sort(a+1,a+n+1);
for(int i=n;i>=1;i--)
{
q1.push(a[i]);
}
for(int i=1;i<=m;i++)
{
bool b1=0,b2=0,b3=0;
ll get=-19260817;
if(!q1.empty()){
get=q1.front();b1=1;
}
if(!q2.empty()&&(q2.front()>get||get==-19260817)){
get=q2.front();b2=1;b1=0;
}
if(!q3.empty()&&(q3.front()>get||get==-19260817)){
get=q3.front();b3=1;b2=0;b1=0;
}
if(b1)q1.pop();
if(b2)q2.pop();
if(b3)q3.pop();
get+=q*(i-1);
if(i%t==0)
cout<<get<<" ";
ll get1=get*u/v;
ll get2=get-get1;
get1-=q*i;
get2-=q*i;
q2.push(get1);
q3.push(get2);
}cout<<"\n";
for(int i=1;i<=n+m;i++)
{
bool b1=0,b2=0,b3=0;
ll get=-19260817;
if(!q1.empty()){
get=q1.front();b1=1;
}
if(!q2.empty()&&(q2.front()>get||get==-19260817)){
get=q2.front();b2=1;b1=0;
}
if(!q3.empty()&&(q3.front()>get||get==-19260817)){
get=q3.front();b3=1;b2=0;b1=0;
}
if(b1)q1.pop();
if(b2)q2.pop();
if(b3)q3.pop();
if(i%t==0)cout<<get+q*m<<" ";
}cout<<"\n";
T3 愤怒的小鸟
状压dp
非常惭愧地说我这是去年noip后
第一次接触状压dp
大概就是用二进制的每一位表示状态
比如10001表示选了第一个和第五个其他都没选
就不用再开多维数组记录状态了
对于这个题
先注意精度问题
由于开始没有精度要求 那么直接eps=1e-7
double equa(double x){
return (x>-eps)&&(x<eps);
}
预处理两只猪i j 路线上能同时打掉多少猪 记为bit[ i ] [ j ]
由两只猪坐标待定系数解二元一次方程得a b
然后把所有猪坐标带进去看符不符合符合就加进去
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
bit[i][j]=0;
if(x[i]!=x[j]){//如果同列显然不能同时打掉
double a=y[i]*x[j]-y[j]*x[i];
double b=y[j]*x[i]*x[i]-y[i]*x[j]*x[j];
double mu=x[i]*x[j]*(x[i]-x[j]);//左边除以分母改为右边乘分母
if(a*mu<0)
{
for(int k=1;k<=n;k++)
{
if(equa(a*x[k]*x[k]+b*x[k]-mu*y[k]))
{
bit[i][j]|=1<<(k-1);
}
}
}
}
}
}
之后就可以转移了
从0到(1 << n)-1转移
dp [ S ] 可以转移到dp[ S | bit [ i ] [ j ] ]
注意处理单个情况 不然可能推不到终点
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int k=0;k<(1<<n);k++)
for(int i=1;i<=n;i++)
{
dp[k|1<<(i-1)] = min(dp[k|1<<(i-1)],dp[k]+1);
//处理单个情况
for(int j=i+1;j<=n;j++)
{
if(!equa(x[i]-x[j]))
{
dp[k|bit[i][j]]=min(dp[k|bit[i][j]],dp[k]+1);
}
}
}
cout<<dp[(1<<n)-1]<<"\n";
可是事实证明这样会T三个点
于是用了无敌题解法
对于枚举的每种情况 先找到第一个0的位置 再从这个0的位置开始往后枚举 j
因为前面的1是显然不用再打一遍的
貌似优化不大 不过乘上上一层循环(1<
for(int k=0;k<(1<<n);k++){
int i=1;
while(k>>(i-1)&1)i++;
dp[k|1<<(i-1)]=min(dp[k|1<<(i-1)],dp[k]+1);//同样要处理单个情况
for(int j=i+1;j<=n;j++) dp[k|bit[i][j]]=min(dp[k|bit[i][j]],dp[k]+1);
}
165滚粗了