CSUST-4.26集训队选拔(全解)
比赛链接:http://acm.csust.edu.cn/contest/91
Update:2020.4.28-更新了I题摸鱼的tomjobs的某些证明和E题恶心心的题的优化及坑点说明
emmm,这个榜有点惨,不过比我们去年到是好多了。。。有一题数据出了点水,导致写了应该就能过,要不是我在各位打完比赛的那晚上闲着感觉有点慌去验了波题。。。然后发现了某些验题人的迷惑AC行为。。。。。。
怎么说呢。。。感觉全程思维题,没脑子的我可能只能等死了,我觉得最简单的两题应该是C题矿工那题和H题大乱斗那题。。。都是一眼题10来分钟,一发AC。。。接下来的出题人所谓的签到题我倒是不太敢苟同了。。。。可能是我比较菜QAQ,A题1v1男人大战和E题恶心心的题其实也不是很难,至于J题lonely_wind的传说easy版。。。。由于数据非常水,直接输出所有的数量就过了,当天晚上tomjobs就加强了一下数据,不过我还是想锤tomjobs。。。居然诋毁我的形象QAQ,什么拿着2升的可乐和大鸡腿,我聚餐的时候还没吃到过鸡腿呢。。。hard版的也不是很难,优化一下ma的求法就完事了,这个也很好优化
题目说明:
A.1v1男人大战(思维) B.送温暖(思维) C.黄金矿工(二分+最短路)
D.探险雷达(思维) E.恶心心的题(线段树+数论) F.打扑克牌(状压DP)
G.随机数(数论) H.动漫明星大乱斗2(缩点) I.摸鱼的tomjobs2(二进制拆位|STL)
J.lonely_wind的传说easy(思维) K.lonely_wind的传说hard(思维|Hash)
比赛已结束,请到problem中提交代码
A.1v1男人大战
题目大意:PC打怪兽,PC攻击分n段有x点血,怪兽攻击分m段有y点血,问PC能否打死怪兽,不能输出-1,否则输出PC在第几轮打死怪兽(n,m<=2e5,x,y<=1e12)
Sample Input 1
6 1 1000 1000 -100 -200 -300 125 77 -4 -1
Sample Output 1
9
emmm,题目看起来很简单。。。。实际上也是比较简单的,我们分两种情况讨论,一是在第一组的攻击内就有双方死亡,二是没有死亡。那么第一种情况就很好办了,我们直接做一次循环就好了,至于第二种情况,由于攻击有加血的存在,所以我们可以用前缀和先求出一组攻击的最大叠加伤害,并用x和y扣去这个数,接下来我们用剩余血量直接除以每组的最终前缀和,计算一下需要多少组,然后直接扣去,最后我们再将刚刚扣去的最大叠加伤害加回来,再跑一轮循环就结束了。具体情况代码中有详细说明
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define ll long long const int mac=2e5+10; const ll inf=1e18+10; int a[mac],b[mac]; ll suma[mac],sumb[mac]; int main() { int n,m; ll x,y; scanf ("%d%d%lld%lld",&n,&m,&x,&y); ll hsta=inf,hstb=inf; for (int i=0; i<n; i++){ scanf ("%d",&a[i]); suma[i]=(i==0?0:suma[i-1])+a[i]; hsta=min(hsta,suma[i]);//计算最高叠加伤害 } for (int i=0; i<m; i++){ scanf ("%d",&b[i]); sumb[i]=(i==0?0:sumb[i-1])+b[i]; hstb=min(hstb,sumb[i]); } if (abs(hsta)>=y || abs(hstb)>=x){//可以直接一组结束 ll round=0; while (x>=0 && y>=0){ y+=a[round%n]; if (y<=0) {printf ("%lld\n",round+1); return 0;} x+=b[round%m]; if (x<=0) {printf ("-1\n"); return 0;} round++; } } else {//没办法一轮结束,那么我们先扣去可以达到的最大值再求解 if (suma[n-1]>=0) {printf("-1\n"); return 0;}//如果总的段数回血,且一轮最高叠加伤害不够 ll round1=0,round2=0; y+=hsta,x+=hstb; ll nb=y/abs(suma[n-1]); if (y%abs(suma[n-1])) nb++; y+=nb*suma[n-1]; y-=hsta;//将血量加回去 while (y>0){//剩下的血量一定能在一组内打掉 y+=a[round1]; if (y<=0){ round1++; round1+=nb*n; break; } round1++; }//得出打怪的回合数round1 if (sumb[m-1]>=0) round2=inf; else { nb=x/abs(sumb[m-1]); if (x%abs(sumb[m-1])) nb++; x+=nb*sumb[m-1]; x-=hstb; while (x>0){ x+=b[round2]; if (x<=0){ round2++; round2+=nb*m; break; } round2++; } } if (round2>=round1) printf("%lld\n",round1); else printf("-1\n"); } return 0; }
B.送温暖
题目大意:给你一个01数列,让你通过执行一次op操作后求他的X-子序列最长有多长,最长方案数有几个,op操作:将[L,R]区间的01翻转,0变1,1变0。X-子序列:任意相邻元素互不相同
Sample Input 1
7 0 1 0 0 0 1 1
Sample Output 1
6 3
emmm,不会,等死吧。。
诶,想想还是友好点,首先我们求出原串的最长X-子序列,设其长度为len,那么我们执行翻转操作的时候最多能够增加2个,当然也有0个和1个情况。接下来我们求相邻元素相同的个数有多少个,比如说样例有3个,分别是(3,4)(4,5)(6,7),那么要使得翻转后效果最佳,我们只需要(4,4)(4,6)(5,6)。。。看出了什么吗,就是将相同元素切一半配对,那么n个的话方案数就有n*(n-1)/2,这里也很容易看出只要有2个及以上的相同相邻元素,答案就是+2,如果是相邻元素只有1个记为$(i,j)$,我们可以有两种翻转方式,即要么翻转第一个位置$i$,要么翻转第二个位置$j$,此时的答案很容易知道是+1,而+0就不用说了,直接就是整个序列的X-子序列就是本身,我们将整个序列翻转,就是一个方案了,然后就没了。。。。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; typedef long long ll; int a[mac],b[mac]; int main() { int n,cnt=0; scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf ("%d",&a[i]); } b[++cnt]=a[1]; int num=0; for (int i=2; i<=n; i++) if (a[i]!=b[cnt]) b[++cnt]=a[i]; for (int i=1; i<n; i++){ if (a[i]==a[i+1]) num++; } int ans1=cnt+min(num,2); ll ans2; if (min(num,2)>=2) ans2=1LL*num*(num-1)/2; else ans2=num+1; printf ("%d %lld\n",ans1,ans2); return 0; }
C.黄金矿工
题目大意:给你无向带权图,边有两个属性,一个是最大承载量,一个是通过时间,问你在时间T内最多能一次运送多少货物
Sample Input 1
4 4 20 1 2 5 15 2 4 4 6 1 3 3 15 3 4 1 4
Sample Output 1
1
emmm,乍一看还以为是最大流。。。结果就是个二分加最短路。。。我们二分一次运送的货物量就行了,然后跑最短路,唯一有点区别的就是在进行选择道路的时候比较一下该边的最大承载量是否大于等于二分的值就好了,其他的就是正常的最短路了
#include <bits/stdc++.h> using namespace std; const int mac=3e4+10; #define ll long long const ll inf=1e18+10; struct edge { int to,next,w,c; }eg[mac<<1]; int head[mac],num=0; ll dis[mac]; bool vis[mac]; struct node { int id; ll s; bool operator<(const node&a)const{ return s>a.s; } }; void add(int u,int v,int c,int t) { eg[++num]=edge{v,head[u],t,c}; head[u]=num; } int dij(int tval,int n,int T) { priority_queue<node>q; for (int i=1; i<=n; i++) dis[i]=inf; dis[1]=0; q.push(node{1,0}); memset(vis,false,sizeof vis); while (!q.empty()){ node now=q.top(); q.pop(); int u=now.id; if (vis[u]) continue; vis[u]=1; for (int i=head[u]; i!=-1; i=eg[i].next){ int v=eg[i].to; if (eg[i].c<tval) continue; if (dis[u]+eg[i].w<dis[v]){ dis[v]=dis[u]+eg[i].w; q.push(node{v,dis[v]}); } } } if (dis[n]>T) return 0; return 1; } int main() { int n,m,T; scanf ("%d%d%d",&n,&m,&T); int l=0,r=0,mid; memset(head,-1,sizeof head); for (int i=1; i<=m; i++){ int u,v,c,t; scanf ("%d%d%d%d",&u,&v,&c,&t); r=max(r,c); add(u,v,c,t);add(v,u,c,t); } int ans=0; while (l<=r){ mid=(l+r)>>1; if (dij(mid,n,T)){ ans=mid; l=mid+1; } else r=mid-1; } printf ("%d\n",ans); return 0; }
D.探险雷达
题目大意:给你一个数列,其中表示的意义如下:
问魔物的排列有多少种情况
Sample Input 1
2 1 1
Sample Output 1
2
。。。。我很慌,非常慌,大概是被样例误导了。。。实际上可能的情况只有3种,即0,1,2,我们需要知道的是当头被固定的时候,整个魔物数列也会被固定,那么我们枚举头的情况就可以了。。此题就结束了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; int a[mac],b[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%d",&b[i]); int ans=0; for (a[1]=0; a[1]<=1; a[1]++){ int mark=0; for (int i=2; i<=n; i++){ a[i]=b[i-1]-a[i-1]-a[i-2]; if (a[i]!=0 && a[i]!=1){ mark=1;break; } if (i==n && b[n]!=a[n]+a[n-1]){ mark=1;break; } } if (!mark) ans++; } printf("%d\n",ans); return 0; }
E.恶心心的题
题目大意:给你n个数$a_i$和q次询问,每次询问L,R,x,表示$[L,R]$同$x$的最小公倍数LCM,答案对p取模(n,q<=1e5,$a_i$<=300,x<=1e9)
Sample Input 1
3 100 2 4 5 8 1 2 3 3 3 4
Sample Output 1
60 8
emmm,这个题本来是4s时限的。。。后来我写了个1.3s的代码感觉时间给多了,tomjobs又写了个600ms的代码。。。。就改成2s时限了
我们可以先考虑能否用线段树预处理出每一段的LCM,答案是肯定的,不过有一点需要注意的是这个答案非常大,需要取模,而取完模之后再取LCM是不正确的,所以我们要换种方法,我们注意到$a_i$非常小,这就暗示了我们去分解这个数,实际上LCM的求解实质就是取质因子分解后的每个质因子的最大指数。我们可以开一个二维线段树tree[maxn][65],300以内有62个素数。我们这样就完成了预处理,之后x有点麻烦,他比较大,我们要采用一般的质因数分解的方式都会T掉,所以我们考虑一个比较特别的方法:
for (int i=2; i*i<=x; i++){ while (x%i==0) x/=i;//此时i为x的质因子 }
然后此题就宣告结束了。。。
Update:实际上我们只需要分解x在300以内的质因子就好了,分解之后剩下的因子和其他的数没有任何关联,我们可以直接乘进去了,所以对于x根本不用任何特殊处理。。。果然我太菜了QWQ,详情请看第二份AC代码。。。我也跑到了600ms+
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define lc rt<<1 #define rc rt<<1|1 #define ll long long const int mac=1e5+10; int mod,a[mac],vis[400]; int tree[mac<<2][65];//300以内有62个素数 int prim[65],cnt=0,use[65]; void push_up(int rt) { for (int i=1; i<=cnt; i++) tree[rt][i]=max(tree[lc][i],tree[rc][i]); } void build(int l,int r,int rt) { if (l==r){ int x; scanf ("%d",&x); for (int i=1; i<=cnt; i++){ if (x==1) break; while (x%prim[i]==0) { x/=prim[i]; tree[rt][i]++; } } return; } int mid=(l+r)>>1; build(lson);build(rson); push_up(rt); } void query(int l,int r,int rt,int L,int R) { if (l>=L && r<=R){ for (int i=1; i<=cnt; i++) use[i]=max(use[i],tree[rt][i]); return; } int mid=(l+r)>>1; if (mid>=L) query(lson,L,R); if (mid<R) query(rson,L,R); } int qick(int a,int b) { int ans=1; while (b){ if (b&1) ans=1LL*ans*a%mod; a=1LL*a*a%mod; b>>=1; } return ans; } int main() { int n,p,q; int m=301; for (int i=2; i<=sqrt(m); i++) if (!vis[i]) for (int j=i*i; j<=m; j+=i) vis[j]=1; for (int i=2; i<m; i++) if (!vis[i]) prim[++cnt]=i; scanf ("%d%d%d",&n,&p,&q); mod=p; build(1,n,1); while (q--){ int l,r,x; scanf ("%d%d%d",&l,&r,&x); memset(use,0,sizeof use); query(1,n,1,l,r); int ans=1; for (int i=1; i<=cnt; i++){ int px=0; while (x%prim[i]==0) px++,x/=prim[i]; use[i]=max(use[i],px); ans=1LL*ans*qick(prim[i],use[i])%mod; } for (int i=2; i*i<=x; i++){ while (x%i==0) x/=i,ans=1LL*ans*i%mod; } if (x>1) ans=1LL*ans*x%mod; printf ("%d\n",ans); } return 0; }
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define lc rt<<1 #define rc rt<<1|1 #define ll long long const int mac=1e5+10; int mod,a[mac],vis[400]; int tree[mac<<2][65];//300以内有62个素数 int prim[65],cnt=0,use[65]; void push_up(int rt) { for (int i=1; i<=cnt; i++) tree[rt][i]=max(tree[lc][i],tree[rc][i]); } void build(int l,int r,int rt) { if (l==r){ int x; scanf ("%d",&x); for (int i=1; i<=cnt; i++){ if (x==1) break; while (x%prim[i]==0) { x/=prim[i]; tree[rt][i]++; } } return; } int mid=(l+r)>>1; build(lson);build(rson); push_up(rt); } void query(int l,int r,int rt,int L,int R) { if (l>=L && r<=R){ for (int i=1; i<=cnt; i++) use[i]=max(use[i],tree[rt][i]); return; } int mid=(l+r)>>1; if (mid>=L) query(lson,L,R); if (mid<R) query(rson,L,R); } int qick(int a,int b) { int ans=1; while (b){ if (b&1) ans=1LL*ans*a%mod; a=1LL*a*a%mod; b>>=1; } return ans; } int main() { int n,p,q; int m=301; for (int i=2; i<=sqrt(m); i++) if (!vis[i]) for (int j=i*i; j<=m; j+=i) vis[j]=1; for (int i=2; i<m; i++) if (!vis[i]) prim[++cnt]=i; scanf ("%d%d%d",&n,&p,&q); mod=p; build(1,n,1); while (q--){ int l,r,x; scanf ("%d%d%d",&l,&r,&x); memset(use,0,sizeof use); query(1,n,1,l,r); int ans=1; for (int i=1; i<=cnt; i++){ int px=0; while (x%prim[i]==0) px++,x/=prim[i]; use[i]=max(use[i],px); ans=1LL*ans*qick(prim[i],use[i])%mod; } ans=1LL*x*ans%mod; printf ("%d\n",ans); } return 0; }
F.打扑克牌
题目大意:给你一个长度为n的数字串,你可以任意打乱顺序,求有多少个不同的数字串可以被m整除(n<=15,m<=50)
Sample Input 1
123 2
Sample Output 1
2
emmm,n给的非常小,我们大概可以想到状压DP。。。那么我们要怎么压呢,1和0能代表什么呢,想想好像也知道这里的1和0只能是代表放好了没有,那么刚开始的时候是状态0,即一个都没有放好,我们需要转移到有一个1的状态,以下是详细说明:
for (int i=0; i<(1<<n); i++){//枚举放好的状态 for (int j=0; j<n; j++){//尝试放入第j个元素 if (i&(1<<j)) continue;//该位置已经放好了 for (int k=0; k<m; k++){//枚举转移的的模数 dp[i|(1<<j)][(k*10+a[j])%m]+=dp[i][k]; } } } ll ans=dp[(1<<n)-1][0];
那么ans就是最终答案了吗,想想也知道不太对,这里没有考虑到重复数字的影响,比如说如果样例给的是133,那么总的排列就会由6个降为3个,那么1234->2233有什么改变呢?我们会发现每次降低的都是相同数字的阶乘,也就是说我们的ans还要除以每个数字数目的阶乘
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; int a[20],num[15]; ll dp[1<<16][52];//1表示已经放好了,0表示还没放好 ll use[20]; char s[20]; int main() { int m; scanf ("%s%d",s,&m); int n=strlen(s); for (int i=0; i<n; i++){ a[i]=s[i]-'0'; num[a[i]]++; } use[0]=1; for (int i=1; i<=15; i++) use[i]=use[i-1]*i;//重复i次的影响 dp[0][0]=1; for (int i=0; i<(1<<n); i++){//枚举放好的状态 for (int j=0; j<n; j++){//尝试放入第j个元素 if (i&(1<<j)) continue;//该位置已经放好了 for (int k=0; k<m; k++){//枚举转移的的模数 dp[i|(1<<j)][(k*10+a[j])%m]+=dp[i][k]; } } } ll ans=dp[(1<<n)-1][0]; for (int i=1; i<=9; i++) ans/=use[num[i]]; printf ("%lld\n",ans); return 0; }
G.随机数
题目大意:给你n个区间,从每个区间中随机抽取一个数,问最小值的期望是多少,答案对1e9+7取模
Sample Input 1
2 2 3 1 3
Sample Output 1
833333341
emmmm,题目看得有点迷最小值的期望怎么求,先分析一下样例,最小值为1的情况下有两种可能(2,1)(3,1)总得取法6种,概率就是$\frac{2}{6}$,$E(1)=\frac{1}{3}$
最小值为2的情况下有(2,2)(2,3)(3,2)概率为$\frac{3}{6}$,$E(2)=2\times \frac{3}{6}=1$
最小值为3的情况下有(3,3)概率为$\frac{1}{6}$,$E(3)=3\times \frac{1}{6}=\frac{1}{2}$
那么答案就是$\frac{1}{3}+1+\frac{1}{2}=\frac{11}{6}$
我们枚举最小值的情况,然后对每个最小值得出他的个数乘以其值,累加起来最后除以每一段的区间长度就ok了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; const int mac=2e3+10; ll qick(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } struct node { int l,r; }a[mac]; int main() { int n,ma=0; scanf ("%d",&n); for (int i=1; i<=n; i++){ int l,r; scanf ("%d%d",&l,&r); a[i]=node{l,r}; ma=max(ma,r); } ll ans=0; for (int i=1; i<=ma; i++){ ll p1=1,p2=1; for (int j=1; j<=n; j++){ if (a[j].r<i) p1=0;//无论怎么选,i都不可能是最小值 if (a[j].l>i) {//无论怎么选,i都是最小值 p1=p1*(a[j].r-a[j].l+1)%mod; p2=p2*(a[j].r-a[j].l+1)%mod; } else { p1=p1*(a[j].r-i+1)%mod;//大于等于i的数量 p2=p2*(a[j].r-i)%mod;//大于i的数量 } } if (p1==0) continue; else ans+=1LL*i*(p1-p2+mod)%mod,ans%=mod; } for (int i=1; i<=n; i++){ ans=ans*qick(a[i].r-a[i].l+1,mod-2)%mod; } printf ("%lld\n",ans); return 0; }
H.动漫明星大乱斗2
题目大意:n,m,q,m对关系,问你当选中x时,有多少个人是x所达不到的
Sample Input 1
2 1 2 1 2 1 2
Sample Output 1
0 0
题面有点繁琐,但所幸基本都过了这题,那就没什么好讲的了,我们直接并查集缩点就完事了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; int id[mac],father[mac]; int num[mac]; int find(int x){return x==father[x]?x:father[x]=find(father[x]);} int main() { int n,m,q; scanf ("%d%d%d",&n,&m,&q); for (int i=1; i<=n; i++) father[i]=i; for (int i=1; i<=m; i++){ int u,v; scanf ("%d%d",&u,&v); int fu=find(u),fv=find(v); if (fu!=fv) father[fu]=fv; } int block=0; for (int i=1; i<=n; i++) if (father[i]==i) id[i]=++block; for (int i=1; i<=n; i++) id[i]=id[find(i)]; for (int i=1; i<=n; i++) num[id[i]]++;//每个点中有多少个元素 while (q--){ int st; scanf("%d",&st); int ans=n-num[id[st]]; printf ("%d\n",ans); } return 0; }
I.摸鱼的tomjobs2
题目大意:给你n个数,定义连续几个数$a_i$(可以是1个)的与(&)运算为这些数的交叉值,问有多少个不同的交叉值(n<=1e5,$a_i$<=1e18)
Sample Input 1
4 1 2 3 4
Sample Output 1
5
哎呀呀呀呀,数据加强了QAQ,本来用set加个小优化就可以直接过了,具体如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=1e5+10; ll a[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf ("%lld",&a[i]); } set<ll>s; for (int i=1; i<=n; i++){ ll p=a[i]; ll pp=0; s.insert(p); for (int j=i+1; j<=n; j++){ p&=a[j]; pp&=a[j]; s.insert(p); if (p==pp) break;//这个时候也没必要进行下去了,因为从i+1开始还会得到p这个值 } } int ans=s.size(); printf ("%d\n",ans); return 0; }
现在嘛。。。还是可以用set过的,只不过多加了个map,但感觉有点不太稳。。。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=1e5+10; ll a[mac]; unordered_map<ll,int>vis; set<ll>s[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf ("%lld",&a[i]); } int ans=0; for (int i=1; i<=n; i++){ s[i].insert(a[i]); if (!vis[a[i]]) ans++,vis[a[i]]=1; for (auto x:s[i-1]){ if (vis[a[i]&x]==0)vis[a[i]&x]=1,ans++; s[i].insert(a[i]&x); } } printf ("%d\n",ans); return 0; }
Update:。。。。。有人说这个set里面最多只有60来个元素,很稳的,我尝试证明一波。。。果然很稳QAQ
xjb证明:之前看过下面做法的各位应该清楚只有0&1对于1来说才有改变,那么对于a[i]&x有变化,也就是说x的某些位是1,a[i]中的某些位是0,而x中最多有60来个1,所以它最多有60来次改变的机会。。。QAQ这个证明有点水了,我还是太菜了QWQ
接下来就是好像是出题人的官方做法了。。。想不出来不要太难为自己了QAQ
我们将每个数字拆成二进制位,我们知道与运算只有0&1的情况下对于1来说才有改变,那么我们可以预处理出二进制状态下每个数的0的位置,之后枚举的时候判断每个数的二进制第j位是否为1,如果是的话,我们直接找到距离该数最近的那个数(二进制状态第j位为0的)就好了,每次枚举二进制位的时候都取最近的点,那么就可以做到连续与运算了,举个例子,3 3 5,第一个数和第二个数是一样的,那么我们得到的最近的点就是3,接下来直接3&5保存答案就好了,然后继续异或下去。代码中有详细的注释
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; typedef long long ll; const int inf=1e9+7; ll a[mac]; int pos[65][mac],cnt[65]; unordered_map<ll,int>vis; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%lld",&a[i]); for (int i=2; i<=n; i++){//第一个就没必要分解了,反正也用不上... ll x=a[i]; for (int j=0; j<=63; j++){ if ((x&1)==0) pos[j][++cnt[j]]=i;//第cnt[j]个二进制j位为0的是第i个数 x>>=1; } } for (int i=0; i<=63; i++){ pos[i][++cnt[i]]=inf;//加个边界 cnt[i]=1; } int ans=0; for (int i=1; i<=n; i++){ for (int j=0; j<=63; j++){ if (pos[j][cnt[j]]==i) cnt[j]++;//如果本身该位置含0,那么下一次找该位置0要往后找 } ll x=a[i]; if (!vis[x]) ans++,vis[x]=1; while (x){ int mi=inf; for (int j=0; j<=63; j++){ if (x&(1LL<<j)) mi=min(mi,pos[j][cnt[j]]);//找到能改变x的最近的点 //如果x的第j位为1,那么我们只需要找到第j位为0的,就可以使得它改变了 } if (mi==inf) break; x&=a[mi]; if (!vis[x]) ans++,vis[x]=1; } } printf ("%d\n",ans); return 0; }
J.lonely_wind的传说easy
题目大意:给你一串编码,让你求打乱顺序后相同位置不同数的个数最多有多少个,这个表达规则如下:
n段,num次重复,m个,表示第i段里有m个元素并且重复了num次(n<=2e4,m<=100,num<=1000)
Sample Input
2 1 2 1 2 2 2 2 3
Sample Output
6
。。。。。这题过分水了,不知道为什么没几个人写。找找规律就出现了嘛,将这一长串编码的长度记为sum,如果有某个数出现的次数mx超过$\frac{1}{2}sum$那么答案就是$(sum-mx)\times 2$,给个2 2 3 3 3就知道了,最多的形态:3 3 3 2 2,就是4个。而如果没有超过的话,就直接是sum个了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; unordered_map<int,int>q; int main() { int n; scanf ("%d",&n); int ma=0,sum=0; for (int i=1; i<=n; i++){ int num,m; scanf ("%d%d",&num,&m); for (int i=1; i<=m; i++){ int x; scanf ("%d",&x); q[x]+=num; ma=max(ma,q[x]); } sum+=num*m; } if (ma*2LL<=sum) printf ("%d\n",sum); else printf ("%d\n",(sum-ma)*2); return 0; }
K.lonely_wind的传说hard
题目大意:给你一串编码,让你求打乱顺序后相同位置不同数的个数最多有多少个,这个表达规则如下:
n段,num次重复,m个,表示第i段里有m个元素并且重复了num次(n<=4e4,m<=100,num<=1000)
Sample Input
2 1 2 1 2 2 2 2 3
Sample Output
6
如果用上面的方法的话会T掉,因为map修改的复杂度会比较大,所以我们要优化ma的求法,第一种方法就是用结构体存下每个数的数值,及其重复的次数,然后我们排个序,将相同数值的重复次数全部加起来取最大值就完事了,第二种方法就是Hash。。。这个就比较骚了,这样的话我们用普通的数组就可以直接完成map的long long映射,大大优化了时间
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=4e6+10; void in(int &x) { int f=0; char ch=getchar(); while (ch>'9' || ch<'0') ch=getchar(); while (ch>='0' && ch<='9') f=(f<<3)+(f<<1)+ch-'0',ch=getchar(); x=f; } struct node { int id; ll val; bool operator<(const node&a) const{ return id<a.id; } }a[mac]; int main() { int n; in(n); ll sum=0; int cnt=0; for (int i=1; i<=n; i++){ int num,m; in(num);in(m); for (int i=1; i<=m; i++){ int x; in(x); a[++cnt]=node{x,num}; } sum+=num*m; } sort(a+1,a+1+cnt); int p=a[1].id; ll ma1=0,ma2=0; for (int i=1; i<=cnt; i++){ if (a[i].id==p) ma1+=a[i].val; else { p=a[i].id; ma2=max(ma2,ma1); ma1=a[i].val; } } ma2=max(ma2,ma1); if (ma2*2<=sum) printf ("%d\n",sum); else printf ("%d\n",(sum-ma2)*2); return 0; }