【清北学堂2018-刷题冲刺】Contest 3
比较数学的一场,难度稍大。
Task 1:数数
【问题描述】
fadbec 很善于数数,⽐如他会数将a 个红球,b 个黄球,c 个蓝球,d个绿球排成⼀列,求出任意相邻不同⾊的方案数⽬。
现在R 君不知道fadbec 数的对不对,想让你也算⼀算。
由于数字⽐较⼤,所以请输出除以109 + 7 的余数。
【输入格式】
⼀⾏四个正整数a,b,c,d。
【输出格式】
输出包含⼀个整数,表⽰答案。
【样例输入1】
1 1 1 2
【样例输出1】
36
【数据规模及约定】
对于前30% 的数据,1 <=a; b; c; d <= 3。
对于前100% 的数据,1 <= a; b; c; d <= 30。
直观想法:暴力搜索,判断不同,有30分。
稍加改造:只有四种球,取s个数的时候对应的有多种不同状态,按状态转移进行DP,注意空间和时间优化,100pts
比较简单,不做过多解释。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ns s&1
#define ls ~s&1
#define lint long long
using namespace std;
const lint modd=1000000007;
lint a,b,c,d,f[2][31][31][31][5];
inline lint mod(lint x){
return x%modd;
}
inline lint upd(lint s,lint i,lint j,lint k,lint p){
lint sum=0;
for(int pp=0;pp<=4;++pp){
if(pp==p)continue;
sum=mod(sum+f[s][i][j][k][pp]);
}
return mod(sum);
}
int main(){
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
f[0][0][0][0][0]=1;
lint sum=a+b+c+d;
for(int s=1;s<=sum;++s){
memset(f[ns],0,sizeof(f[ns]));
lint maxi=min(a,sum);
for(int i=0;i<=maxi;++i){
lint maxj=min(b,sum-i);
for(int j=0;j<=maxj;++j){
lint maxk=min(c,sum-i-j);
for(int k=0;k<=maxk;++k){
lint l=s-i-j-k;
if(l<0||l>d)continue;
// for(int p=1;p<=4;++p){
if(i>0)f[ns][i][j][k][1]=mod(f[ns][i][j][k][1]+upd(ls,i-1,j,k,1));
if(j>0)f[ns][i][j][k][2]=mod(f[ns][i][j][k][2]+upd(ls,i,j-1,k,2));
if(k>0)f[ns][i][j][k][3]=mod(f[ns][i][j][k][3]+upd(ls,i,j,k-1,3));
if(l>0)f[ns][i][j][k][4]=mod(f[ns][i][j][k][4]+upd(ls,i,j,k-0,4));
// }
// printf("s=%lld ns=%lld a=%lld b=%lld c=%lld %lld %lld %lld %lld\n",s,ns,i,j,k,f[ns][i][j][k][1],f[ns][i][j][k][2],f[ns][i][j][k][3],f[ns][i][j][k][4]);
}
}
}
}
lint ans=mod(mod(f[sum&1][a][b][c][1]+f[sum&1][a][b][c][2])+mod(f[sum&1][a][b][c][3]+f[sum&1][a][b][c][4]));
printf("%lld\n",ans);
return 0;
}
Task 2:数组
【问题描述】
fabdec 有⼀个长度为n 的数组a[](下标1-n), 初始时都是0。
fabdec 随机了⼀个1 到n 的随机数x,并且把a[x]++。
fabdec 重复了m 次这样的操作,然后数了⼀下数组⾥⼀共有k 个位置为奇数。
fabdec 现在想问执⾏m 次操作,总共能⽣成多少种不同的数组使得恰好有k 个位置是奇数?(两个数组不同当且仅当两个数组存在某个位置数组的值不相同)
因为这个数字会很⼤,所以只需输出这个答案除以109 + 7 的余数。
【输入格式】
⼀⾏三个整数,n,m,k。
【输出格式】
输出包含⼀个整数,表⽰答案。
【样例输入】
2 3 1
【样例输出】
4
【数据规模及约定】
对于前20% 的数据,1 <= n;m <= 4。
对于前50% 的数据,1 <= n;m <= 2000。
对于前100% 的数据,1 <= n;m <= 100000, 0 <= k <= n。
考虑方法:
直接硬搞搜索,20pts
直接DP,50pts
正解的想法比较巧妙:
奇数的本质其实就是二进制位下最低位为1,所以k位位奇数,那就在k位上添加上一个1,选择方法有C(n,k)种。剩下的数拆分成多个2的形式,随意分配到每一位上,计算分配类型总数即可。
由于数学不好,我通过打表得到结论:对于ss=(m-k)/2个2可重复地填到n位上,选择方法有C(n+ss-1,ss)种。
- 根据乘法原理,答案就是C(n,k)*C(n+ss-1,ss),注意要大力取模。
- 考虑组合数的计算:因为模数是质数,而且n范围比较大,考虑线性求阶乘逆元。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 400010
#define lint long long
using namespace std;
const lint mod=1000000007;
lint n,m,k,fac[MAXN],inv[MAXN];
//put k into n positions;
inline lint __pow(lint x,lint y){//x^y
lint s=1;
while(y!=0){
if(y&1){
y^=1;
s=x*s%mod;
}
y>>=1;
x=x*x%mod;
}
return s;
}
void get_fac(lint maxn){
fac[0]=1;
for(lint i=1;i<=maxn;++i){
fac[i]=i*fac[i-1]%mod;
}
inv[maxn]=__pow(fac[maxn],mod-2);
// printf("inv=%lld\n",inv[n+k]);
for(lint i=maxn;i>=2;--i){
inv[i-1]=inv[i]*i%mod;
}
}
inline lint C(lint x,lint y){
return ((fac[x]*inv[y])%mod)*inv[x-y]%mod;
}
int main(){
freopen("array.in","r",stdin);
freopen("array.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&k);
if((m-k)&1){
puts("0");
return 0;
}
lint ss=(m-k)>>1;
lint maxn=max(max(n+ss-1,ss),max(n,k));
get_fac(maxn);
// for(int i=1;i<=n+k;++i)printf("%d ",fac[i]);puts("");
lint ans=C(n+ss-1,ss)*C(n,k)%mod;
printf("%lld",ans);
return 0;
}
Task 3:子集【问题描述】
R 君得到了⼀个集合,⾥⾯⼀共有n 个正整数,R 君对这个集合很感兴趣,通过努⼒钻研,发现了这个集合⼀共有2n 个⼦集。
现在R 君又对这个集合的⼦集很感兴趣。
定义⼀个集合的权值是这个集合内所有数字的和的话,那么R 君想问问你,这个集合的权值第K ⼩⼦集是多⼤。
ps. 涉及到较少数字的long long 输⼊输出,建议使用cin/cout。
【输入格式】
第⼀⾏两个正整数n,k。
接下来⼀⾏n 个正整数,表⽰集合内元素。
【输出格式】
输出⼀个数字,表⽰集合的权值第K ⼩⼦集的权值。
【样例输入】
2 3
1 2
【样例输出】
2
6
【数据规模及约定】
- 对于前20% 的数据,1 <= n <= 15。
- 对于前40% 的数据,1 <= n <= 22。
- 对于前100% 的数据,1 <= n <=35, 1 <= k <= 2^n,1 <= 集合元素<=1000000000
朴素算法直接dfs构造所有子集,排序求解即可,40pts。
正解需要使用meet-in-the-middle的思想。正向搜索搜索树的大小是235的,是不可接受的。如果两端同时开始搜索,就可以让搜索树大小变成216+215。但是对于中间合并的时候要仔细考虑,避免复杂度退化成216^2。所以想到双向搜索后,本题的核心问题就成为了怎么把分开的两个小集合最终合并成一个大集合。
这里我们考虑二分答案,二分第k大集合的数值,判断该数值不小于的集合数与k的比较。其中判断函数中的方法类似于“悬线法”的思想,把子集排序后用两个指针来记录答案总数。
顺带一提,预处理拆分的子集构造还有一种更容易写的方法-使用lowbit构造。但是因为本蒟蒻不会+难理解+实测更慢,在这里本蒟蒻选择搜索构造。
#include<cstdio>
#include<iostream>
#include<algorithm>
#define lint long long
using namespace std;
const int MAXN=(1<<18)+5;
lint n,k,sum,arr[40],cnt_1,cnt_2,s1[MAXN],s2[MAXN];
void dfs_1(lint pos,lint val,lint ed){
s1[++cnt_1]=val;
for(lint i=pos;i<=ed;++i){
dfs_1(i+1,val+arr[i],ed);
}
}
void dfs_2(lint pos,lint val,lint ed){
s2[++cnt_2]=val;
for(lint i=pos;i<=ed;++i){
dfs_2(i+1,val+arr[i],ed);
}
}
inline bool judge(lint x){
lint p1=1,p2=1,ans=0;
while(s1[p1+1]+s2[p2]<=x&&p1+1<=cnt_1)p1++;
// printf("p1=%lld p2=%lld\n",p1,p2);
while(p2<=cnt_2){
while(s2[p2]+s1[p1]>x&&p1>=0)p1--;
if(p1<0)break;
// printf("ans+=%d\n",p1);
ans+=p1;
p2++;
}
// printf("x=%lld ans=%lld\n",x,ans);
return ans>=k;
}
int main(){
freopen("subset.in","r",stdin);
freopen("subset.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;++i){
cin>>arr[i];
sum+=arr[i];
}
lint bg_1=1,bg_2=n/2+1;
lint ed_1=n/2,ed_2=n;
dfs_1(bg_1,0,ed_1);
dfs_2(bg_2,0,ed_2);
/*
思路:
- 把集合分成两半
- 预处理这两个集合的所有子集
- 二分答案
*/
sort(s1+1,s1+1+cnt_1);
sort(s2+1,s2+1+cnt_2);
lint l=1,r=sum;
while(r>l+1){
lint mid=(l+r)>>1;
if(judge(mid)){
r=mid;
}else{
l=mid;
}
}
printf("%lld",r);
}
【清北学堂2018-刷题冲刺】Contest 3的更多相关文章
- 2017 清北济南考前刷题Day 7 afternoon
期望得分:100+100+30=230 实际得分:100+100+30=230 1. 三向城 题目描述 三向城是一个巨大的城市,之所以叫这个名字,是因为城市中遍布着数不尽的三岔路口.(来自取名力为0的 ...
- 2017 清北济南考前刷题Day 1 afternoon
期望得分:80+30+70=180 实际得分:10+30+70=110 T1 水题(water) Time Limit:1000ms Memory Limit:128MB 题目描述 LYK出了道水 ...
- 2017 清北济南考前刷题Day 3 morning
实际得分:100+0+0=100 T1 右上角是必败态,然后推下去 发现同行全是必胜态或全是必败态,不同行必胜必败交叉 列同行 所以n,m 只要有一个是偶数,先手必胜 #include<cstd ...
- 2017 清北济南考前刷题Day 3 afternoon
期望得分:100+40+100=240 实际得分:100+40+100=240 将每个联通块的贡献乘起来就是答案 如果一个联通块的边数>点数 ,那么无解 如果边数=点数,那么贡献是 2 如果边数 ...
- 2017 清北济南考前刷题Day 4 afternoon
期望得分:30+50+30=110 实际得分:40+0+0=40 并查集合并再次写炸... 模拟更相减损术的过程 更相减损术,差一定比被减数小,当被减数=减数时,停止 对于同一个减数来说,会被减 第1 ...
- 2017 清北济南考前刷题Day 7 morning
期望得分:100+50+20=170 实际得分:10+50+20=80 1. 纸牌 题目描述 在桌面上放着n张纸牌,每张纸牌有两面,每面都写着一个非负整数.你的邪王真眼可以看到所有牌朝上的一面和朝下的 ...
- 2017 清北济南考前刷题Day 6 afternoon
期望得分:100+100+30=230 实际得分: 正解: 枚举最高的位,这一位m是1但实际用了0 然后剩余的低位肯定是 正数就用1,负数用0 考场思路:数位DP #include<cstdio ...
- 2017 清北济南考前刷题Day 6 morning
T1 贪心 10 元先找5元 20元 先找10+5,再找3张5 #include<cstdio> using namespace std; int m5,m10,m20; int main ...
- 2017 清北济南考前刷题Day 5 afternoon
期望得分:100+100+30=230 实际得分:0+0+0=30 T1 直接模拟 #include<cstdio> #include<iostream> using name ...
- 2017 清北济南考前刷题Day 5 morning
期望得分:100+100+0=200 实际得分: 坐标的每一位不是0就是1,所以答案就是 C(n,k) #include<cstdio> #include<iostream> ...
随机推荐
- 一、Dev
一.获取选中的表格 // MessageBox.Show(gridview_Parent.GetFocusedDataRow()["series"].ToString());//获 ...
- 今日头条移动app广告激活数据API对接完整Java代码实现供大家参考》》》项目随记
这是自毕业后的第一篇博客,希望自己今后能养成写博客的一个好习惯.最近公司为了加速APP推广,采取在外部平台(如:今日头条)进行广告投放的方式,进行用户引流.因此我们需要对广告的激活数据进行一个检测,跟 ...
- 【python练习题】程序12
#题目:判断101-200之间有多少个素数,并输出所有素数. #判断素数的方法:用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数. from math import ...
- Web API 2 Entity Framework 使用 Procedure
Recently I worked on a project, which I started as code first and then I forced to switch to Databas ...
- myclipse里有感叹号的问题,希望可以帮到各位
今天,我在myeclipse中导入一个项目的时候就发现一个问题:项目有红色感叹号,并且项目有红色错误提示.我首先看了这个红色错误提示的地方,发现这个根本不应该报错,想必是这个红色感叹号的原因. 于 ...
- mpvue——引入antv-F2图表
踩坑中~ 官方文档 https://www.yuque.com/antv/f2/intro 毕竟不像echarts接触过,所以还是先看看文档较好 github https://github.com/s ...
- HTML协议
一,HTML协议 简介 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式.协作式和超媒体信息系统的应用层协议.HTTP是万维网的数据通信的 ...
- Django+Xadmin打造在线教育系统(二)
基于xadmin的后台管理 先使用pip进行安装xadmin及其依赖包 pip install django-adminx 安装完成后卸载xadmin,保留依赖包即可 pip uninstall dj ...
- POI如何自动调整Excel单元格中字体的大小
问题 目的是要将Excel中的文字全部显示出来,可以设置对齐格式为[缩小字体填充],但是这样的话只能展示出一行数据,字体会变得很小.还有一种办法,设置对齐格式为[自动换行],然后让单元格中的字体自动调 ...
- 基于 __new__ 方法的单例模式
单例模式定义 首次实例化创建实例化对象 之后的每次实例化都用最初的实例化对象 即单实例模式 __new__ 的原理 __new__ 方法可以在 __init__ 方法执行 这样可以在初始化之前进行一系 ...