题目:
2017 JUST Programming Contest 4.0
A:给你一个序列长度为n(n<=1e5),定义Beauty(l, r) = al & al + 1 & al + 2 & ... & ar,求
其实自己在纸上推一下不难发现,我们只需要维护二进制中每一位连续为1的数量,然后每一次都加num[i]*2的i次方即可。
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 10010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; ll c[maxn],a[maxn]; int n,m,k; ll num[maxn]; ll ans,tmp,cnt; int main() { c[0]=1; for(int i=1;i<=28;i++) c[i]=c[i-1]*2; int T,cas; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(num,0,sizeof(num)); ans=0; for(int i=0;i<n;i++) { ll x; scanf("%lld",&x); for(ll j=0;j<=28;j++) { if(x&(1<<j)) num[j]++; else num[j]=0; ans+=num[j]*c[j]; } } printf("%lld\n",ans); } return 0; }
B:给你一个序列,其中-1处为数字丢失处。而每个数满足a[i] = ( a[i-1]+ 1) % m,(1 < i ≤ n)(n<=1000,m<=1e9)
因此只需要找一个不为-1的数字分别往前、往后填上就行了。
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 100010 #define ll long long #define inf 1e9+7 using namespace std; int n,m,k; ll a[maxn],c[maxn]; ll ans,tmp,cnt; ll num[maxn]; int main() { int T,cas; c[0]=1; for(int i=1;i<=30;i++) c[i]=c[i-1]*2; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); ll ans; int t; memset(num,0,sizeof(num)); for(int i=0;i<n;i++) { ll x; scanf("%lld",&a[i]); if(a[i]!=-1){ans=a[i];t=i;} } for(int i=t-1;i>=0;i--) { if(a[i]==-1) {a[i]=a[i+1]-1;if(a[i]<0) a[i]=(ll)(m-1);} } for(int i=t+1;i<n;i++) { if(a[i]==-1) a[i]=(a[i-1]+1)%(ll)m; } for(int i=0;i<n;i++) printf("%lld%c",a[i],i==n-1?'\n':' '); } return 0; }
C:给你一个序列a,你要对每个a[i]找到一个a[j]使得(a[i]+a[j])%1000000007最大。(2<=n<=1e5)
不难想到,从小到大排一遍序以后,j=n-1,从最小的那个数开始(i=0),必定满足若a[i]+a[j]<mo,则a[j]就是a[i]找的那个数。
若>=mo,则j--;且满足使(a[i-1]+a[k])%mo最大的k一定小于等于j。
有几种特殊情况:
1、j=-1时,若i!=n-1,则一定(a[i]+a[n-1])%mo最大
2、j=-1时,若i==n-1,则一定则一定(a[i]+a[n-2])%mo最大
3、i==j时,j--;
题目不难,细节不少。
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 100010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; ll aa[maxn],c[maxn]; ll tmp,cnt; ll ans[maxn]; int n; struct node { ll v; int id; bool operator<(node aa)const { return v<aa.v||v==aa.v&&id<aa.id; } }a[maxn]; int main() { int T,cas; c[0]=1; for(int i=1;i<=30;i++) c[i]=c[i-1]*2; scanf("%d",&T); while(T--) { scanf("%d",&n); int t; for(int i=0;i<n;i++) { scanf("%lld",&a[i].v); a[i].id=i; } sort(a,a+n); int j=n-1; for(int i=0;i<n;i++) { ll tmp=a[i].v; while(j>=0&&(tmp+a[j].v>=mo||(i==j))) j--; if(j<0) { if(i!=n-1) ans[a[i].id]=(tmp+a[n-1].v)%mo; else ans[a[i].id]=(tmp+a[n-2].v)%mo; } else ans[a[i].id]=(tmp+a[j].v); } for(int i=0;i<n;i++) printf("%lld%c",ans[i],i==n-1?'\n':' '); } return 0; }
D:给你一个只包含小写字母的无限长的串,循环节为s,长度为n(n<=1e4),求字母ch在下标为[l,r]长度的区间里出现了多少次。(l<=r<=1e9)
首先预处理一下前缀和,就是字母ch在0~i区间里出现了num[i][ch-'a']次。
然后对于区间[l,r](0<l<=r<n)中ch出现的次数即为num[r][ch-'a']-num[l-1][ch-'a'];
即对于l/len==r/len时,ans=num[r][ch-'a']-num[l-1][ch-'a'];
否则 ans=(r/len-l/len-1)*num[n-1][ch-'a']+num[r][ch-'a']+num[n-1][ch-'a']-num[l-1][ch-'a'];
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 10010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; int aa[maxn],c[maxn]; int tmp,cnt; int ans[maxn]; int num[maxn][27]; char s[maxn]; int n,m,q; int find(int l,int r,int ch) { if(l==0) return num[r][ch]; else return num[r][ch]-num[l-1][ch]; } int main() { int T,cas; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&q); memset(num[0],0,sizeof(num[0])); int t; scanf("%s",s); for(int i=0;i<n;i++) { int id=s[i]-'a'; if(i) for(int j=0;j<26;j++) num[i][j]=num[i-1][j]; num[i][id]++; } for(int i=0;i<q;i++) { int x,y,id; char ch[2]; scanf("%d %d %s",&x,&y,ch); id=ch[0]-'a'; x--;y--; if((y/n)==(x/n)) { x%=n;y%=n; int ans=find(x,y,id); printf("%d\n",ans); } else { int ans=(y/n)-(x/n)-1; ans*=num[n-1][id]; x%=n;y%=n; ans+=find(x,n-1,id); ans+=find(0,y,id); printf("%d\n",ans); } } } return 0; }
E:给你n(n<=14)个骰子,每个骰子六个面都标有数字(1~100)你要从每个骰子选一个面,把面上的数字乘起来%mod(1e9+7)为m。求有多少种不同的选法。
上来就来了一发爆搜TLE,经提示后才知道神奇的折半法。。。
即先爆搜前一半骰子的乘积%mod后用map存起来其方法总数
再对后半段爆搜,对每个结果t,m*t的逆元%mod即为乘积等于m需要的数,直接map找其方法总数即可。
具体见代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 10010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; ll d[maxn],c[maxn]; ll tmp,cnt; ll ans; ll a[20][8]; int n,f1,f2,n1; ll m,q; map<ll,ll>mp; ll power(ll a,ll n) //a的n次方mod { ll ans=1; a=a%mo; while (n) { if(n&1) ans=(ans*a)%mo; n>>=1; a=(a*a)%mo; } return ans; } void dfs(int i,ll tmp) { if(i==n1) { if(mp.count(tmp)==0) {mp[tmp]=1;} else { mp[tmp]++; } return; } for(int j=0;j<6;j++) { dfs(i+1,(tmp*a[i][j])%mo); } } void dfs2(int i,ll tmp) { if(i==n) { ans+=mp[(m*(power(tmp,mo-2)))%mo]; return; } for(int j=0;j<6;j++) { dfs2(i+1,(tmp*a[i][j])%mo); } } int main() { int T,cas; scanf("%d",&T); while(T--) { ans=0; scanf("%d %lld",&n,&m); mp.clear(); for(int i=0;i<n;i++) { for(int j=0;j<6;j++) { scanf("%lld",&a[i][j]); } sort(a[i],a[i]+6); } n1=(n+1)/2; dfs(0,1); dfs2(n1,1); printf("%lld\n",ans); } return 0; }
F:题意是求x的总数,其中
1、1 < x < n (n<1e6)
2、ay ≤ ax, for each y (1 ≤ y < x).
3、ax ≤ az, for each z (x < z ≤ n).
只需要处理一下前缀最大值和后缀最小值,扫一遍就出来了。
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 1000010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; int ma[maxn],mi[maxn]; int tmp,cnt; int ans; int a[maxn]; int n; int m,q; int main() { int T,cas; scanf("%d",&T); while(T--) { scanf("%d",&n); ma[0]=0; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); ma[i]=max(ma[i-1],a[i]); } mi[n+1]=inf; for(int i=n;i>=1;i--) { mi[i]=min(mi[i+1],a[i]); } int ans=0; for(int i=2;i<n;i++) if(a[i]>=ma[i-1]&&a[i]<=mi[i+1]) ans++; printf("%d\n",ans); } return 0; }
G:给你一个n*m(n,m<50)的01棋盘,你要使其第一行、第一列、最后一行、最后一列的格子全为1,每次可以把任意两个格子交换数值,求最少需要交换多少次。如果不能使其第一行、第一列、最后一行、最后一列的格子全为1,则输出-1。
只需要记录边界上0的数量和非边界1的数量,比较一下即可。。。
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 1010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; int tmp,cnt; int ans; int a[maxn]; int n; int m,q; char s[maxn][maxn]; int main() { int T,cas; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); // getchar(); for(int i=0;i<n;i++) scanf("%s",s[i]); int ans=0,tmp=0; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { int id=(s[i][j]&15); if(i==0||j==0||(i==n-1)||(j==m-1)) { if(!id) ans++; } else if(id) tmp++; } //cout<<tmp<<ans<<endl; if(tmp<ans) puts("-1"); else printf("%d\n",ans); } return 0; }
H:给你n(n<2e5)个数a[1]~a[n],你刚开始在1的位置,你要跳到位置n。假设你现在在位置i
1、你可以跳到a[i]右边和a[i]值相等的最近的一个位置
2、你可以直接向右跳一个格子
求最少用多少部跳到位置n。
这不就是牛客网上某场大佬赛签到题的变种么。。。
记忆化bfs,记忆每个已经跳过的点。至于每个点可以往右跳几个预处理一下就行了。。。
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 200010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; int tmp,cnt; int ans; int a[maxn],c[maxn]; int tiao[maxn]; int n; int m,q; struct node { int x; int tmp; }no; void bfs() { queue<node>q; no.x=1; no.tmp=0; q.push(no); c[1]=1; while(!q.empty()) { node k=q.front(); q.pop(); node kk=k; kk.tmp++; if(k.x==n) { printf("%d\n",k.tmp); return; } if(!c[k.x+1]){ c[k.x+1]=1; kk.x++; q.push(kk); kk.x--; } if(tiao[kk.x]!=-1) { kk.x=tiao[k.x]; if(!c[kk.x]) { c[kk.x]=1; q.push(kk); } } } } int main() { int T,cas; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(c,0,sizeof(c)); memset(tiao,-1,sizeof(tiao)); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); if(!c[a[i]]) c[a[i]]=i; else tiao[c[a[i]]]=i,c[a[i]]=i; } int ans=0; memset(c,0,sizeof(c)); bfs(); } return 0; }
I:给你n(n<1e5)个数a[i]~a[n],你要求其中所有不重复子序列的乘积的和 mod 1e9+7。
又来了这道题,之前在牛客网上也见到过类似的,不过那是每个序列挑一个数的乘积总和。公式类似,直接求即可。
sum=sum+(sum+1)*a[i].(1<=i<=n) 公式很好推
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 200010 #define ll long long #define inf 1e9+7 using namespace std; const ll mo=1e9+7; int tmp,cnt; int ans; ll a[maxn],c[maxn]; int n; int m,q; int main() { int T,cas; scanf("%d",&T); while(T--) { scanf("%d",&n); ll ans=0; ll sum=0; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); ans=(ans+((sum+1)*a[i])%mo)%mo; sum=ans; } printf("%lld\n",ans); } return 0; }
J:给你一个串,长度为n(n<=20)求把其中的字母重新排列组合能形成的不重复的回文串的数量。
记录每个字母出现的次数,然后
1、n为偶数,有字母出现奇数次 答案为0
2、n为奇数,有多余一个字母出现奇数次 答案为0
3、否则 求n/2,即每个字母挑选a[i]个位置 即组合数C(n/2,a[i]) 注意求完后n/2要减去a[i]
代码:
#pragma comment(linker,"/STACK:102400000,102400000") #include<bits/stdc++.h> #define maxn 210 #define ll long long #define inf 1e9+7 using namespace std; const ll mod=1e9+7; int tmp,cnt; int ans; int c[maxn]; int n; int m,q; char s[maxn]; int k,flag,x,f,y,p; long long ni[maxn]; long long a[maxn]; long long zuhe(int x,int y){ //组合数 return a[x]*ni[y]%mod*ni[x-y]%mod; } long long calc(long long x,long long y){ long long z=1; while (y){ if (y&1)(z*=x)%=mod; (x*=x)%=mod,y/=2; } return z; } int main() { a[0]=1; for (int i=1;i<maxn;i++)a[i]=a[i-1]*i%mod; ni[maxn-1]=calc(a[maxn-1],mod-2); for (int i=maxn-2;i>=0;i--) ni[i]=ni[i+1]*(i+1)%mod; int T,cas; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(c,0,sizeof(c)); scanf("%s",s); for(int i=0;i<n;i++) { int id=s[i]-'a'; c[id]++; } int tmp=0; for(int i=0;i<26;i++) { if(c[i]&1) tmp++; } if((!(n&1))&&(tmp)) puts("0"); else if(tmp>1) puts("0"); else { ll ans=1; n/=2; sort(c,c+26); for(int i=0;i<26;i++) { c[i]/=2; if(!n) break; if(c[i]){ ans=ans*zuhe(n,c[i]); n-=c[i]; } } printf("%lld\n",ans); } } return 0; }
总体难度中等偏下,可惜由于今天考试,昨天的比赛没打,今天的估计也打不了了。。。