牛客网重现赛链接: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;
}