2017计算机系夏令营
题解参考:
2017 华东师范计算机系暑期夏令营机考
A. 不等式
Problem #3304 - ECNU Online Judge
有点像贪心算法
选一个刚刚好在条件范围里的b[i]作为候选,【这个“刚刚好”是指选一个符合这个条件的最极限的值】
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
string x;string op[203];int a[203];int b[203];
int n;cin>>n;
for(int i=0;i<n;i++){
cin>>x>>op[i]>>a[i];
if(op[i]==">")b[i]=a[i]+1;//b[i]是ok的数字,a[i]是题目里的数字
else if(op[i]=="<")b[i]=a[i]-1;
else b[i]=a[i];
}
int ans=0;
for(int i=0;i<n;i++){//遍历每一个b[i]
int cnt=0;
for(int j=0;j<n;j++){//判断第j个命题是否能满足
if(op[j]=="="&&b[i]==a[j])cnt++;
else if(op[j]==">="&&b[i]>=a[j])cnt++;
else if(op[j]=="<="&&b[i]<=a[j])cnt++;
else if(op[j]==">"&&b[i]>a[j])cnt++;
else if(op[j]=="<"&&b[i]<a[j])cnt++;
}
ans=max(ans,cnt);//取cnt的最大值
}
cout<<ans<<'\n';
return 0;
}
B. 1 的个数最多的整数
Problem #3303 - ECNU Online Judge
暴力
首先把a变成二进制,之后从低位到高位遍历,如果这一位是0,那么看看能不能变成1(变成1会不会超过b,如果不会就记录)
为什么要从低位开始遍历?如果从高位开始遍历的话,有些低位的1就选不到了
注意:不要新开一个ans=0,之后判断条件为ans+tmp<=b;因为假设a=0001,b=1001(左边是低位)这样ans=1110的时候(这时ans<b)因为a[3]=1,所以ans+8>b就不合法了
所以直接在a上面做加法就可以了
注意:如果用数组存放a和b的二进制,记得定义在T次循环的内部
注意:(1<<i)默认是int,要(long long)1<<i 才是long long的状态
代码
#include<bits/stdc++.h>
using namespace std;
long long a,b;long long tmpa,tmpb;
int main()
{
int T;cin>>T;
for(int t=1;t<=T;t++){
int sa[70]={0};int sb[70]={0};
cin>>a>>b;
int st1=0,st2=0;
tmpa=a,tmpb=b;
while(tmpa){
sa[st1]=tmpa%2;tmpa/=2;st1++;
}
while(tmpb){
sb[st2]=tmpb%2;tmpb/=2;st2++;
}
long long tmp=1;
for(int i=0;i<st2;i++){
if(sa[i]==0&&a+tmp<=b)//表示可以变成1看看
a+=tmp;
tmp=tmp*2;
}
cout<<"Case "<<t<<": "<<a<<'\n';
}
}
C. 打印
打印n个相同的字符,插入或删除一个花费x,复制花费y
动态规划
首先dp[0]=0,dp[1]=x,dp[2]=min(dp[1]+x,dp[1]+y),dp[3]=min(dp[2]+x,dp[1]+y+x)
当 i 为双数时,dp[i]=min(dp[i-1]+x,dp[i/2]+y)
当 i 单数时,dp[i]=min(dp[i-1],dp[(i-1)/2]+y,dp[(i+1)/2]+y)+x
注意:i为单数的时候,可以先(i-1)/2,再插入一个x;也可以(i+1)/2,再删除一个x
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[10000007];
int main()
{
ll n,x,y;cin>>n>>x>>y;dp[1]=x;
for(int i=2;i<=n;i++){
if(i%2)dp[i]=min(dp[i-1],min(dp[(i-1)/2]+y,dp[(i+1)/2]+y))+x;
else dp[i]=min(dp[i-1]+x,dp[i/2]+y);
}
cout<<dp[n];
return 0;
}
D. 十亿分考
Problem #3305 - ECNU Online Judge
完全不会orz
看标答有用随机数的,有用连分数的
代码 随机数
随机 重要的是eps来确定精度,但是如果精度太高会超时(例如1e-16),如果精度太低会WA(例如5e-16)
for循环来鲁棒之类的(?)
#include<bits/stdc++.h>
using namespace std;
int main()
{
long double eps=5e-16;
long double a;cin>>a;
srand(time(0));
long long p,q;
while(1){
q=rand()%1000000001;
long long mid=q*a;
for(long long p=mid-15;p<=mid+15;p++){
long double chp=(long double)p/q-a;
if(fabs(chp)<eps){
cout<<p/__gcd(p,q)<<" "<<q/__gcd(p,q)<<'\n';
return 0;
}
}
}
}
代码 连分数
题解 2017 华东师范计算机系暑期夏令营机考_十亿分考
连分数OI wiki连分数 - OI Wiki (oi-wiki.org)
任何有理数都可以精确地以两种方式表示为连分数:
Q:怎么从一个小数a得到a0,a1,a2,……an的表示呢?
a0=(int)1/a r0=(double)1/a0-(double)a0
a1=(int)1/r0 r1=(double)1/a1-(double)a1
……
an=(int)1/rn-1 rn=(double)1/an-(double)an
Q:怎么从a0,a1,a2序列中算出原来的分子分母?
这就是从最小的分式开始向上,最小的分式看作0,也就是p=0,q=1;向上p=1,q=cnt[n-1];之后来到了新的分式,这时上下乘以q通分,分母是cnt[n-2]*q+原来的分子p,因为新来的分子永远是1,所以新的p=q;最后遍历完毕得到p和q
注意当输入的a为0的时候要特判!!!
#include<bits/stdc++.h>
using namespace std;
vector<long long> cnt;
long double cal(){
long double tmp=0;
for(int i=cnt.size()-1;i>=0;i--){
tmp+=cnt[i];
tmp=1/tmp;
}
return tmp;
}
int main()
{
long double a;cin>>a;
if(!a){cout<<0<<" "<<1<<'\n';return 0;}
long double tmp=a;
while(1){
long long v=1/tmp;
cnt.push_back(v);
tmp=(long double)1/tmp-(long double)v;
if(fabs(a-cal())<4e-16)break;
}
long long p=0,q=1;
for(int i=cnt.size()-1;i>=0;i--){
p+=cnt[i]*q;
swap(p,q);
}
cout<<p/__gcd(p,q)<<" "<<q/__gcd(p,q)<<'\n';
return 0;
}
E. 有钱人买钻石
Problem #3306 - ECNU Online Judge
如果是动态规划的话 p是1e8 太大了
dfs做,重点在于
1. 从单价为1的硬币开始dfs
2. 硬币枚数从高到低枚举,因为方案可行后就可以退出来了
3. high的值为(p-sum)/y[id],low的值为high-25
【注意 int low=max(0,(p-sum)/y[id]-25); 的写法是错误的,因为n[id]可能没有那么多】所以用high
为什么low是high-25?
因为例如 30 29 0 0 0
id=0的时候,high=29,low=4
遍历到low=4了
这时来想一想为什么要从大到小遍历呢?——因为要减少小的给大的让位来满足p的需求
但是我让了25个位置了,就算有一个25来补足,也只是抹平抵消而已,并没有加上什么,说明方案就不可行,
所以low=high-25就return了
【但注意for循环的终止条件写 i>low 是错误的,因为low可能>high-25,只是因为不能为负,所以low=0;终止条件要写i>=low 】
代码 dfs
#include<bits/stdc++.h>
using namespace std;
int p,n[5],ans=-1;
int y[]={1,5,10,25};
bool dfs(int sum,int id,int num){//已经兑换的额度,第几个硬币,已经兑换的个数
if(sum>p)return 0;
if(id==4){
if(sum==p){
ans=max(ans,num);
return 1;
}
return 0;
}
int high=min(n[id],(p-sum)/y[id]);
int low=max(0,high-25);//这个最低也挺妙的
for(int i=high;i>=low;i--){//注意从高到低枚举,成功了就可以break了
if(dfs(sum+y[id]*i,id+1,num+i))return 1;
}
return 0;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
cin>>p>>n[0]>>n[1]>>n[2]>>n[3];
dfs(0,0,0);
if(ans==-1){cout<<"Impossible\n";return 0;}
cout<<ans<<'\n';
return 0;
}
F. 送分题
Problem #3307 - ECNU Online Judge
离线 排序,以右端点为第一排序,左端点为第二排序来对区间排序
用map来记录的话,就是种类:
个数
右指针移动,如果是增加的话,等于2的时候种类总和+1,之后移到右端点位置后,移动左指针,如果是减少的话如果减了且减到1了,种类总和-1,直到移动到左端点位置结束
有的时候,不止向右,还会向左,例如3 3和1 5,这个时候就l就像r一样就可以了
代码 TLE
虽然是TLE(4/10)代码,但是写代码时注意区间的开闭问题,就比如下面的代码中,
当 l 的起点是1,减法的时候 mp[a[l]]先减,之后再l--,而且循环条件为l<now.l,这就说明now.l这个点还没有被剪掉;r 的起点是0时,加法的时候,先r++,mp[a[r]]再加,循环条件为r<now.r,这就说明now.r在循环结束的时候已经被加上了
#include<bits/stdc++.h>
using namespace std;
struct node{
int l,r,id;
};
bool cmp(node a,node b){
if(a.r!=b.r)return a.r<b.r;
return a.l<b.l;
}
node qu[500005];//左端点 右端点 id
int a[500005]; int ans[500005];
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int N,Q;cin>>N>>Q;
for(int i=1;i<=N;i++)cin>>a[i];
for(int i=0;i<Q;i++){
cin>>qu[i].l>>qu[i].r;
qu[i].id=i;
}
sort(qu,qu+Q,cmp);
int sum=0;
map<int,int>mp;int l=1,r=0;
for(int i=0;i<Q;i++){
node now=qu[i];
while(r<now.r){
r++;mp[a[r]]++;if(mp[a[r]]==2)sum++;
if(mp[a[r]]==3)sum--;
}
while(l<now.l){
mp[a[l]]--;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==1)sum--;l++;
}
while(l>now.l){
l--;mp[a[l]]++;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==3)sum--;
}
ans[now.id]=sum;
}
for(int i=0;i<Q;i++)cout<<ans[i]<<'\n';
return 0;
}
代码 莫队 分块排序 TLE
上面的代码时间复杂度最差还是O(mn),所以用莫队
莫队的形式
分块,块的大小是根号n,对于l在同一个块内的,按照r的大小排序,否则就按照块号排序
TLE(8/10)【unordered map使测试7通过了,用node或者把三者拆开来排序的效率都是一样的】
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp;
int a[500005];int ans[500005];
int N,Q;int block;
struct node{
int l,r,id;
};
node qu[500005];//左端点 右端点 id
bool cmp(node a,node b){
if(a.l/block==b.l/block)//如果是在一个块内的
return a.r<b.r;
return a.l/block<b.l/block;//不在一个块内的按照块号分
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>N>>Q;block=sqrt(N);
for(int i=1;i<=N;i++)cin>>a[i];
for(int i=0;i<Q;i++){
cin>>qu[i].l>>qu[i].r;
qu[i].id=i;
}
sort(qu,qu+Q,cmp);
int sum=0;
int l=1,r=0;
for(int i=0;i<Q;i++){
node now=qu[i];
while(r>now.r){
mp[a[r]]--;if(mp[a[r]]==2)sum++;
if(mp[a[r]]==1)sum--;r--;
}
while(r<now.r){
r++;mp[a[r]]++;if(mp[a[r]]==2)sum++;
if(mp[a[r]]==3)sum--;
}
while(l<now.l){
mp[a[l]]--;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==1)sum--;l++;
}
while(l>now.l){
l--;mp[a[l]]++;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==3)sum--;
}
ans[now.id]=sum;
}
for(int i=0;i<Q;i++)cout<<ans[i]<<'\n';
return 0;
}
代码 莫队 分块 离散化 AC
记录思考了两天的SB时刻
注意看,虽然a[i]的范围是0~1e9,但是它只有5e6个数字,这个时候把a离散化以下,就可以把a在区间内的个数收纳在5e6的数组mp内,其中mp的序号是a[i]数值的种类,mp的值是a[i]在区间里出现的个数
所以离散化,这样就不会在while里面的map的加减花费太多时间(?),于是就AC了!
#include<bits/stdc++.h>
using namespace std;
int mp[500005];
int a[500005];int ans[500005];
int N,Q;int block;int tot=0;
struct node{
int l,r,id;
};
unordered_map<int,int>num;
node qu[500005];//左端点 右端点 id
bool cmp(node a,node b){
if(a.l/block==b.l/block)//如果是在一个块内的
return a.r<b.r;
return a.l/block<b.l/block;//不在一个块内的按照块号分
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>N>>Q;block=sqrt(N);
for(int i=1;i<=N;i++){
cin>>a[i];
if(num.count(a[i])==0)num[a[i]]=tot++;//离散化
a[i]=num[a[i]];
}
for(int i=0;i<Q;i++){
cin>>qu[i].l>>qu[i].r;
qu[i].id=i;
}
sort(qu,qu+Q,cmp);
int sum=0;
int l=1,r=0;
for(int i=0;i<Q;i++){
node now=qu[i];
while(r>now.r){
mp[a[r]]--;if(mp[a[r]]==2)sum++;
if(mp[a[r]]==1)sum--;r--;
}
while(r<now.r){
r++;mp[a[r]]++;if(mp[a[r]]==2)sum++;
if(mp[a[r]]==3)sum--;
}
while(l<now.l){
mp[a[l]]--;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==1)sum--;l++;
}
while(l>now.l){
l--;mp[a[l]]++;if(mp[a[l]]==2)sum++;
if(mp[a[l]]==3)sum--;
}
ans[now.id]=sum;
}
for(int i=0;i<Q;i++)cout<<ans[i]<<'\n';
return 0;
}
2017软件系夏令营
3296. 2333
Problem #3296 - ECNU Online Judge
首先魔法石可以变成2或者3或者23,问最多能组多少个2333
那最省的组成方法就是三个石头组成一个2333,所以整除3就好了
重点:做题时要看清题目,一开始看成多少种排列组合了,仔细看了看之后才发现是能组多少个
注意:输出一个答案后要换行
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int T;cin>>T;int a;
while(T--){
cin>>a;
cout<<a/3<<'\n';
}
return 0;
}
3297. 铺瓷砖
Problem #3297 - ECNU Online Judge
就方案数 和 1的个数
乍一看以为是状态压缩dp,吓死
代码 dfs
方案数就是最后return的时候如果是合理的,那么方案数+1;1的个数就是return的时候加上该方案1的个数
#include<bits/stdc++.h>
using namespace std;
int maxx=0,all=0,ans=0;
void dfs(int sum,int pre,int num){//sum是已经铺的,pre是前一个,num是有多少个1
if(sum>=all){
if(sum==all){maxx=maxx+num;ans++;}
return;
}
if(pre==1){
dfs(sum+2,2,num);dfs(sum+3,3,num);
}
else if(pre==2){
dfs(sum+1,1,num+1);dfs(sum+3,3,num);
}
else if(pre==3){
dfs(sum+1,1,num+1);dfs(sum+2,2,num);
}
else{
dfs(sum+1,1,num+1);dfs(sum+2,2,num);dfs(sum+3,3,num);
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int T;cin>>T;string s;
while(T--){
maxx=0;ans=0;cin>>all;
dfs(0,0,0);
cout<<ans<<'\n'<<maxx<<'\n';
}
return 0;
}
代码 线性dp
用两个dp来表示,一个是方案数,一个是1的个数
二维dp,第一位是长度,第二位是现在放入的;
初始化dp[1][1]=1,dp[2][2]=1,dp[3][3]=1,dp[3][2]=1,dp[3][1]=1;
状态转移dp[i][1]=dp[i-1][2]+dp[i-1][3],dp[i][2]=dp[i-2][1]+dp[i-2][3],dp[i][3]=dp[i-3][1]+dp[i-3][2];
方案数总数是dp[n][1]+dp[n][2]+dp[n][3]
如何得到1的个数?——想不出来怎么从上面的dp中找到1的个数的答案,所以就新开了一个one
状态转移:
one[n][1]中1的个数是从one[n-1][2]+1和one[n-1][3]+1得到的;【但是这一步是有问题的】
one[n][2]中1的个数是从one[n-2][1]和one[n-2][3]得到的;
one[n][3]中1的个数是从one[n-3][1]和one[n-3][2]得到的
初始化 one[1][1]=1,one[3][1]=1,one[3][2]=1
Q:上面划线的步骤为什么有问题呢?
因为不确定one[n-1][2]和one[n-1][3]是否合法,例如one[4][2]就不合法,如果按照上面的步骤就变成了one[5][1]=one[4][2]+one[4][3]+2,这是不合法的
并且,只要是dp[i-1][2]和dp[i-1][3]存在,所有的它们后面加上1都可以增加one[i][1]的个数
例如,下面是dp[6][1]的方案,从中可以看到dp[5][2]=2,dp[5][3]=1
2 3 1
3 2 1
2 1 2 1
从上面可以看到1放在了所有dp[i-1][2]和dp[i-1][3]方案的后面
Q:应该怎么改进呢?
状态方程应该变为one[i][1]=one[i-1][2]+one[i-1][3]+dp[i-1][2]+dp[i-1][3];
#include<bits/stdc++.h>
using namespace std;
int maxx=0,n=0;
int dp[35][4];
int one[35][4];
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int T;cin>>T;string s;
while(T--){
memset(dp,0,sizeof(dp));cin>>n;
dp[1][1]=1,dp[2][2]=1,dp[3][3]=1,dp[3][2]=1,dp[3][1]=1;
one[1][1]=1,one[3][1]=1,one[3][2]=1;
for(int i=4;i<=n;i++){
dp[i][1]=dp[i-1][2]+dp[i-1][3];
dp[i][2]=dp[i-2][1]+dp[i-2][3];
dp[i][3]=dp[i-3][1]+dp[i-3][2];
one[i][1]=one[i-1][2]+one[i-1][3]+dp[i-1][2]+dp[i-1][3];
one[i][2]=one[i-2][1]+one[i-2][3];
one[i][3]=one[i-3][1]+one[i-3][2];
}
cout<<dp[n][1]+dp[n][2]+dp[n][3]<<'\n'<<one[n][1]+one[n][2]+one[n][3]<<'\n';
}
return 0;
}
不过既然想到状压dp了,那就来一道状压铺瓷砖吧
补充:状压dp
291. 蒙德里安的梦想
参考题解:AcWing 291. 蒙德里安的梦想
题意:求把N×M的棋盘分割成若干个1×2 的的小长方形,有多少种方案。1≤N,M≤11
思路:总的方案数就等于摆完所有横向长方形的方案数。所以,我们只用考虑如何枚举横向长方形的摆放即可
3298. 排队买夜宵
Problem #3298 - ECNU Online Judge
做了那么久 终于有一道我一看就会的题了qwq
和 合法括号序列判断 一模一样的题
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int T;cin>>T;string s;
while(T--){
stack<char>st;
cin>>s;
for(int i=0;s[i];i++){
if(st.empty())st.push(s[i]);
else if(st.top()=='1'&&s[i]=='0')st.pop();
else if(st.top()=='0'&&s[i]=='1')st.pop();
else st.push(s[i]);
}
cout<<st.size()<<'\n';
}
return 0;
}
9. Alice and A simple problem
Problem #9 - ECNU Online Judge
简单模拟
代码
#include<bits/stdc++.h>
using namespace std;
int dp[101][101],cnt=0;
int main()
{
int m,n;cin>>m>>n;
for(int i=0;i<n;i++){//行
for(int j=0;j<m;j++){
dp[j][i]=++cnt;
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cout<<dp[i][j];
if(j!=n-1)cout<<' ';
}
cout<<'\n';
}
return 0;
}
11. Cacey and Calphabet
找到最大上升子序列的长度?
代码 vector+lower_bound Onlogn
其实就是贪心算法
#include<bits/stdc++.h>
using namespace std;
vector<int> v;
int main()
{
string s;cin>>s;
for(int i=0;s[i];i++){
if(v.empty())v.push_back(s[i]-'a');
else{
int tmp=lower_bound(v.begin(),v.end(),s[i]-'a')-v.begin();
if(tmp==v.size())v.push_back(s[i]-'a');
else v[tmp]=s[i]-'a';
}
}
cout<<26-v.size();
return 0;
}
代码 dp On^2
dp[i]表示以i为结尾的最
首先找到s[j]小于s[i]且dp[j]最大的,dp[i]=dp[j]+1
#include<bits/stdc++.h>
using namespace std;
vector<int> v;
int dp[55];
int main()
{
string s;cin>>s;int n=s.size();
for(int i=0;i<n;i++){
int maxx=0;
for(int j=0;j<i;j++){
if(s[j]<s[i]&&dp[j]>maxx)maxx=dp[j];
}
dp[i]=maxx+1;
}
cout<<26-dp[n-1];
}
代码 树状数组 nlogn
树状数组的目的在于找到 i 之前的s[j]<s[i]中的最大值
所以可以以j 和 i为序,换句话说,树状数组的序号只要26个就可以了
一边遍历,一边更新树状数组
#include<bits/stdc++.h>
using namespace std;
# define lowbit(x) x&(-x)
int a[30];
int query(int x){//查询是向下的,比如9的话要查a[8]和a[9]
int ma=0;
for(;x;x-=lowbit(x))
ma=max(ma,a[x]);
return ma;
}
void add(int x,int k){//更新是向上的,比如更新了a[3],a[4]也要更新
for(;x<=26;x+=lowbit(x))
a[x]=max(a[x],k);
return;
}
int main()
{
string s;cin>>s;int n=s.size();int ans=0;
for(int i=0;s[i];i++){
int ma=query(s[i]-'a'+1-1);//+1是因为树状数组是从1开始的,-1是因为s[j]<s[i]
add(s[i]-'a'+1,ma+1);
ans=max(ans,ma+1);
}
cout<<26-ans;
}
3299. 主色调
Problem #3299 - ECNU Online Judge
时限是3s,暴力做法
在遍历区间的时候,第二层循环来了一个颜色,这个颜色出现次数++,更新其中出现次数最多的颜色的最小的序号【这个用一个if条件判断就可以达到了】
其实就是很普通的模拟题
代码
#include<bits/stdc++.h>
using namespace std;
int a[5003];
int main()
{
int n;
while(cin>>n){
vector<int> ans(n+1,0);
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++){
vector<int>v(n+1,0);int maxid=0,maxxv=0;
for(int j=i;j<n;j++){
v[a[j]]++;
if(maxxv<v[a[j]]||(maxxv==v[a[j]]&&maxid>a[j])){
maxid=a[j];maxxv=v[a[j]];
}
ans[maxid]++;
}
}
for(int i=1;i<=n;i++){
cout<<ans[i];if(i!=n)cout<<' ';
}
cout<<'\n';
}
}
3300. 奇数统计
Problem #3300 - ECNU Online Judge
不会做,写一个暴力,TLE了
#include<bits/stdc++.h>
using namespace std;
int a[100005];
long long C(long long m,long long n){
long long g=1,d=1;
for(int i=1;i<=m-n;i++){
g=g*(i+n);
d=d*i;
}
return g/d;
}
int main()
{
int n,T;cin>>T;
while(T--){
cin>>n;int ans=0;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(a[i]<a[j])continue;
if(C(a[i],a[j])&1)ans++;
}
}
cout<<ans<<'\n';
}
}
参考
题解:EOJ-3300 奇数统计(高维前缀和)
卢卡斯定理:算法学习笔记(25): 卢卡斯定理
组合数的奇偶判断:组合数奇偶性的判断(附证明)
题解
前置知识:n!中含有2因子的个数等于(n-它的二进制形式中1的个数)
Q:为什么n!中含有2因子的个数等于(n-它的二进制形式中1的个数)?
前置知识:
N!质因数2的个数 = [N / 2] + [N / 4] + [N / 8] + ....
因为N/2 表示的是2 4 6 8 10 12……这些相隔为2的数的个数;N / 4表示的是4 8 12 16这些相隔为4的数……这样子加起来就是N!质因数2的个数
定理:n!中含有2因子的个数等于(n-它的二进制形式中1的个数)证明
【有点树状数组 lowbit 的感觉】
补充:n&(n-1)作用:将n的二进制表示中的最低位为1的改为0,用于将n的二进制表示中的最低位为1的改为0
重点:a&b=b组合数是奇数,所以要找a&b=b的组合
设sum[b]的值是对于b来说,和b组合的组合数会变成奇数的个数;算法是动态规划
方程为dp[i]=a[i]+a[(所有i的二进制的子集)],例如 dp[110]=a[110]+a[100]+a[010]【a数组表示这个数有几个】
状态转移方程可表现为
如何实现?枚举1的最高位,从1枚举到第17位(2^17=131072)
3301. OIOIOI
Problem #3301 - ECNU Online Judge