牛客网重现赛链接:https://www.nowcoder.com/acm/contest/123#question
A | Anagram |
题意:对于26个大写字母中的每一个字母operations一次就变为下一个字母,比如A操作一次变为B,B操作一次变为C,而Z操作一次变为A,给出等长的两个字符串str1,str2,问最少需要对第一个字符串str1操作几次使得其能够变为另一个字符串tmp,且转换后的字符串tmp中每个字符出现的次数与str2中每个字符出现的次数相同,这里允许字符排列不同只要个数对应即可。
解析:签到题之一,可直接用贪心来做,对于每个str2中的字符,只需往前找str1中最先出现的字母即可,比如str2中需要一个'C',那么看str1中有没有'C',有的话只需操作0次即可得到C,否则看str1中有没有'B',否则看str1中有没有'A',否则看str1中有没有'Z'...找到第一个出现的即可转换。
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll M=27; int mp[M][M]; int num1[M],num2[M]; string str1,str2; void init() { for(int i=0;i<26;i++)//mp[i][j]为i转换到j需要的操作次数 { for(int j=0;j<26;j++) { mp[i][j]=(j-i+26)%26; } } } int main() { init(); while(cin>>str1>>str2) { memset(num1,0,sizeof(num1)); memset(num2,0,sizeof(num2)); int len1=str1.length(); int len2=str2.length(); for(int i=0;i<len1;i++)//记录字符出现情况 num1[str1[i]-'A']++; for(int i=0;i<len2;i++) num2[str2[i]-'A']++; ll ans=0; for(int i=0;i<26;i++) { while(num2[i]) { bool f=false; for(int j=i;j>=0;j--) { if(num1[j]) { f=true; ans+=mp[j][i]; num2[i]--; num1[j]--; break; } } if(f==false) { for(int j=25;j>i;j--) { if(num1[j]) { ans+=mp[j][i]; num2[i]--; num1[j]--; break; } } } } } cout<<ans<<endl; } return 0; }
C | Cities |
题意:有n个城市,每个有权值ai,现在想在城市间修一些路,使得所有城市链接起来,两个城市间修一条路的花费是两个城市权值之和,求最小花费。
解析:签到题,每个城市都连到权值最小的那一个城市即可
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll M=100005; int n,a[M]; int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); int minV=1; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } for(int i=2;i<=n;i++) { if(a[i]<a[minV]) minV=i; } ll ans=0; for(int i=1;i<=n;i++) { if(i!=minV) { ans+=(a[i]+a[minV]); } } printf("%lld\n",ans); } return 0; }
F | Four-tuples |
题意:给出四个区间,让在每个区间中取一个数组成四元组(x1,x2,x3,x4),并且要求 ,问有能有多少种组合方式
解析:题意看似简单,但是要注意x1=x3,x2=x4是允许的,这一点是很可能想不到的,那么很容易想到用容斥来做,但注意这里要基于题干中四个不等于来做。具体见代码
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; ll l1,r1,l2,r2,l3,r3,l4,r4; int main() { int T; scanf("%d",&T); while(T--) { scanf("%lld%lld%lld%lld%lld%lld%lld%lld",&l1,&r1,&l2,&r2,&l3,&r3,&l4,&r4); ll len1=(r1-l1+1); ll len2=(r2-l2+1); ll len3=(r3-l3+1); ll len4=(r4-l4+1); ll tl,tr,tl2,tr2; ll ans=len1*len2%mod; ans=ans*len3%mod; ans=ans*len4%mod; //此时ans为总可能方案数 //下面进行容斥处理不合法的 //减去满足一个不等式的 //1与2交,即减去x1=x2的方案 tl=max(l1,l2); tr=min(r1,r2); if(tl<=tr) ans=((ans-((tr-tl+1)*len3%mod*len4%mod))%mod+mod)%mod; //2与3交,即减去x2=x3的方案 tl=max(l2,l3); tr=min(r2,r3); if(tl<=tr) ans=((ans-((tr-tl+1)*len1%mod*len4%mod))%mod+mod)%mod; //3与4交,即减去x3=x4的方案 tl=max(l3,l4); tr=min(r3,r4); if(tl<=tr) ans=((ans-((tr-tl+1)*len1%mod*len2%mod))%mod+mod)%mod; //4与1交,即减去x4=x1的方案 tl=max(l1,l4); tr=min(r1,r4); if(tl<=tr) ans=((ans-((tr-tl+1)*len2%mod*len3%mod))%mod+mod)%mod; //加上同时满足两个不等式的 //1,2,3交,在计算1与2交和2与3交时,将1,2,3交的情况减去了两次,所以这里加回来 //可以理解为,减去x1=x2和x2=x3时,将x1=x2=x3减了两次 tl=max(l1,max(l2,l3)); tr=min(r1,min(r2,r3)); if(tl<=tr) ans=(ans+(tr-tl+1)*len4%mod)%mod; //1,2,4交 tl=max(l1,max(l2,l4)); tr=min(r1,min(r2,r4)); if(tl<=tr) ans=(ans+(tr-tl+1)*len3%mod)%mod; //1,3,4交 tl=max(l1,max(l3,l4)); tr=min(r1,min(r3,r4)); if(tl<=tr) ans=(ans+(tr-tl+1)*len2%mod)%mod; //2,3,4交 tl=max(l2,max(l3,l4)); tr=min(r2,min(r3,r4)); if(tl<=tr) ans=(ans+(tr-tl+1)*len1%mod)%mod; //1,2交且3,4交 tl=max(l1,l2); tr=min(r1,r2); tl2=max(l3,l4); tr2=min(r3,r4); if(tl<=tr&&tl2<=tr2) ans=(ans+(tr-tl+1)*(tr2-tl2+1)%mod)%mod; //1,4交且2,3交 tl=max(l1,l4); tr=min(r1,r4); tl2=max(l3,l2); tr2=min(r3,r2); if(tl<=tr&&tl2<=tr2) ans=(ans+(tr-tl+1)*(tr2-tl2+1)%mod)%mod; //减去同时满足三个不等式的,即x1=x2=x3=x4 //1,2,3,4交 tl=max(l4,max(l2,max(l3,l1))); tr=min(r4,min(r2,min(r3,r1))); if(tl<=tr) ans=((ans-((tr-tl+1)*3%mod))%mod+mod)%mod; printf("%lld\n",ans); } return 0; }
G | Games |
题意:Alice和Bob在玩游戏,有n堆石头,在每一回合中,玩家可以从堆中移除一些石块(数量必须是正数,并且不能大于堆中剩余石块的数量)。谁移除最后一块石头并且所有堆都空了,则他获胜,ALice是先手
但是在游戏开始之前,Bob可以选择一些石头堆并将其全部移除,可以去除的石头堆的数量是非负整数,并且不大于给定的数字d。注意d可 以大于n,在这种情况下,Bob可以去除所有的堆。
两者均选择最优策略,让求Bob有多少种取胜方式。解析:这是用到NIm博弈原理的一个背包问题,NIm博弈问题的基本模型为:有三堆各若干个物品(个数分别为a,b,c),两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。这里的三个一般可推广至n个
nim博弈解决思路:先手必败态等价于a^b^c=0(^表示异或运算)。
那么我们只要为Bob设计一种去堆方式使得,Alice先手时有a1^a2^a3^...^an=0;
首先异或有如下运算性质,a^b=c等价于a^b^b=c^b即a=c^b;
那么我们要解的方程形式为:ai1^ai2^ai3^...^aim=0,这里aik是没被去掉的堆的石头数。
令dp[i][j][k]是前i堆去掉j堆的异或值为k的方案数,那么可得转移方程:
dp[i][j][k] = dp[i-1][j][k^a[i]] + dp[i-1][j-1][k];
意思为:前i堆去掉j堆的异或值为k的方案数dp[i][j][k]状态有两种来源:
①.取第i堆石头作为第j堆被去掉的,则其方案数等于前i-1堆去掉j-1堆的异或值为k的方案数dp[i-1][j-1][k](去掉的不计异或和)
②.第i堆石头不去掉的,则其方案数等于前i-1堆去掉j堆的异或值为k^a[i]的方案数dp[i-1][j][k^a[i]](留下的计异或和)
可以实现用三维数组来存是因为d<=10,a[i]<=1e3,1e3的二进制为1111101000,他们之间异或不会超过1111111111即1023.
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; const int M=1e3+5; int n,d,a[M]; ll dp[M][15][1024];//dp[i][j][k]是前i堆去掉j堆异或值为k的方案数 int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&d); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); dp[1][0][a[1]]=1;//前1堆中一堆都不去 dp[1][1][0]=1; //前1堆中去掉一堆 for(int i=2;i<=n;i++) { for(int j=0;j<=min(i,d);j++) { for(int k=0;k<=1023;k++) { dp[i][j][k]=(dp[i-1][j-1][k]+dp[i-1][j][k^a[i]])%mod; } } } ll ans=0; for(int i=0;i<=min(n,d);i++) ans=(ans+dp[n][i][0])%mod; printf("%lld\n",ans%mod); } return 0; }
E | Sequence |
题意:有序列(p1,p2,p3...pn),对于一个元素pi如果 存在j满足(1≤j<i) 且pj<pi .那么我们称pi为"good",现要从序列中去掉一个数,问去哪个数可以使得剩下的数中"good"最多。如果有多个输出值最小的。
解析:用数组cnt[i]表示数值i删除后减少的'good'的数量。
代码:
#include<bits/stdc++.h> #define PI acos(-1.0) #define ll long long using namespace std; #define inf 0x7fffffff const ll M=1000010; ll a,n; ll cnt[M]; inline void Init() { for(int i=1;i<=n;i++) cnt[i]=0; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%lld",&n); ll now_min=inf; ll pre_min=inf; Init(); for(int i=1;i<=n;i++) { scanf("%lld",&a); if(a<=now_min) { pre_min=now_min; now_min=a; }else if(a>=now_min&&a<pre_min) { cnt[now_min]++; cnt[a]++; pre_min=a; } else if(a>=pre_min) { cnt[a]++; } } ll val=cnt[1],it=1; for(int i=2;i<=n;i++) { if(cnt[i]<val) {val=cnt[i];it=i;} } printf("%lld\n",it); } return 0; }