华东师范研究生机试(2017 cs se)

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组合的组合数会变成奇数的个数;算法是动态规划

这里用高维前缀和:

应用:当序号j是序号i的子集的时候,f[i]+=f[j]; 最后f[i]是所有序号是i的子集的数组的

其思路为:第一个回合,现在看最低位的二进制是不是1,如果是的话最低位是0的那个数就会加给你;第x个回合,看看现在第x位的二进制是不是1,如果是的话,把第x位是0的那个数加给你

for(int j = 0; j < n; j++) 
    for(int i = 0; i < 1 << n; i++)
        if(i >> j & 1) f[i] += f[i ^ (1 << j)];

注意:每一个T记得要初始化前缀和

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int f[400005];
int a[100005];
signed main()
{
     int T;cin>>T;
     while(T--){
          memset(f,0,sizeof(f));
          int n;cin>>n;int x;
          for(int i=0;i<n;i++){
               cin>>a[i];f[a[i]]++;
          }
          for(int i=0;i<18;i++){
               for(int j=0;j<(1<<18);j++){
                    if(j>>i&1)f[j]+=f[j^(1<<i)];
               }
          }int ans=0;
          for(int i=0;i<n;i++){
               ans+=f[a[i]];
          }
          cout<<ans<<'\n';
     }
     return 0;
}

3301. OIOIOI 

Problem #3301 - ECNU Online Judge

没看到题解,是构造题,算了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值