【清北学堂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> ...
随机推荐
- 错误:org.apache.catalina.LifecycleException: Protocol handler start failed
org.apache.catalina.LifecycleException: Protocol handler start failed at org.apache.catalina.connect ...
- 二、启用Docker支持
一.使用
- Jarvis OJ A Piece Of Cake
看图片的隐写术自闭,本来想看一看jarvisoj 的basic放松一下心情,结果一道题就做了一晚上qwq 首先看到这道题的时候想到的是凯撒密码(这其实是Google之后才知道这个名字的)枚举了26种位 ...
- Promise.all和Promise.race区别,和使用场景
一.Pomise.all的使用 常见使用场景 : 多个异步结果合并到一起 Promise.all可以将多个Promise实例包装成一个新的Promise实例.用于将多个Promise实例,包装成一个新 ...
- Git秘钥生成以及Gitlab配置
安装Git:详见http://www.cnblogs.com/xiuxingzhe/p/9300905.html 开通gitlab(开通需要咨询所在公司的gitlab管理员)账号后,本地Git仓库和g ...
- Asteroids POJ - 3041 匈牙利算法+最小点覆盖König定理
题意: 给出一个N*N的地图N 地图里面有K个障碍 你每次可以选择一条直线 消除这条直线上的所有障碍 (直线只能和列和行平行) 问最少要消除几次 题解: 如果(x,y)上有一个障碍 则把 ...
- Docker基本使用(二)
Docker 客户端 我们可以直接输入 docker 命令来查看到 Docker 客户端的所有命令选项. 可以通过命令 docker command --help 更深入的了解指定的 Docker 命 ...
- BZOJ 1912 巡逻(算竞进阶习题)
树的直径 这题如果k=1很简单,就是在树的最长链上加个环,这样就最大化的减少重复的路程 但是k=2的时候需要考虑两个环的重叠部分,如果没有重叠部分,则和k=1的情况是一样的,但是假如有重叠部分,我们可 ...
- [CEOI2007] 树的匹配Treasury
类型:树形 DP 传送门:>Here< 题意:给一棵树,你可以匹配有边相连的两个点,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配. 解题思路 首先树形Dp是很明显的,$f[i][ ...
- python中切片的理解
Python中什么可以切片 l Python中符合序列的有序序列都支持切片(slice) l 如:列表,字符,元祖 Python中切片的格式 l 格式:[start : end : step] ...