本场比赛题目难度分布
简单题:
硕哥的签到题(出处:原创题)
硕哥的最短路(出处:百度之星2019全国初赛第三场)
硕哥的字符串(出处:百度之星2019全国初赛第二场)
硕哥的大整数(出处:快速乘模板题)
硕哥的全排列(出处:全排列模板题)
中等题:
硕哥的表达式(出处:中缀表达式解析模板题)
硕哥的托儿所(出处:HAOI2008 糖果传递)
硕哥的布尔式(出处:Face Book 全球初赛 Mr.X)
困难题:
硕哥的数学题(出处:原创题)
硕哥的树贸易(出处:POJ3728 The merchant)
硕哥的电路图(出处:POJ3532 Resistance)
以下是逐题题解:
简单题:
硕哥的签到题
签到题,不再赘述。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=+5;
const int INF=0x3f3f3f3f;
int main() {
ios::sync_with_stdio(false);
cout<<"shuo ge is handsome!!!";
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的最短路
手算一下n为3,4,5时候的结果,就会发现规律。
答案就是
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=+5;
const int INF=0x3f3f3f3f;
int n;
int main() {
ios::sync_with_stdio(false);
cin>>n;
cout<<(1^n);
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的字符串
我们注意到,异或的本质是不进位加法。因此,每一次异或后,结果最多增大一。而如果每一次加法后,结果必然增大一。于是,只要贪心的将所有问号全部换成加号即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=+5;
const int INF=0x3f3f3f3f;
string s;
int main() {
ios::sync_with_stdio(false);
cin>>s;
cout<<s.length()/2+1;
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的大整数
传统的C++数据类型中,不存在某个数字的范围达到。因此我们要考虑通过一种“乘法转化为加法”的方式,来优化我们的计算过程,使得计算中不产生溢出。
我们知道乘法其实就是把很多个加法运算合到一起。现在我们的乘法会爆范围,那我们就把它转化为加法。但是我们不可能一个一个的加,这样复杂度会是 级别。所以我们模仿2进制加法操作来完成。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int maxn=+5;
const ll INF=0x3f3f3f3f3f3f3f3fll;
ll a,b,p;
ll work(ll a,ll b,ll p){
ll ans=0;
while(b){
if(b&1) ans=(ans+a)%p;
a=(a*2)%p;
b>>=1;
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin>>a>>b>>p;
cout<<work(a,b,p);
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的全排列
这是一道模板题,可以通过调用next_permutation函数,或者直接手写递归实现。
代码提供了上述的两种方法。
代码如下
/*
*/
#define method_2
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int maxn=10+5;
const int INF=0x3f3f3f3f;
int n,a[maxn];
int main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
a[i]=i;
}
do{
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
}while(next_permutation(a+1,a+n+1));
return 0;
}
#endif
#ifdef method_2
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int maxn=10+5;
const int INF=0x3f3f3f3f;
int n,order[maxn],vis[maxn];
void dfs(int x){
if(x==n+1){
for(int i=1;i<=n;i++) cout<<order[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(vis[i]) continue;
vis[i]=1;
order[x]=i;
dfs(x+1);
vis[i]=0;
order[x]=0;//由于有vis数组的限制 所以这一行可以省略
}
}
int main() {
ios::sync_with_stdio(false);
cin>>n;
dfs(1);
return 0;
}
#endif
#ifdef method_3
/*
*/
#endif
中等题:
硕哥的布尔式
我们考虑最后一步运算,会出现如下三种情况
0 运算符 0
0 运算符 x
x 运算符 x
其中只有第二种情况的结果是有可能不确定的,而这种情况下,最多只要调整一下这个最后一次进行的运算符即可。
因此我们首先将字符串中的所有x换成0后,计算一次答案。然后将字符串中的所有x换成1后,再计算一次答案。
如果两次答案相同,则意味着x的取值不会影响结果。
否则,只要一次操作即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=300+5;
const int INF=0x3f3f3f3f;
int T,n,kase=0;
stack<char>st;
int cal(string s){
for(int i=0;i<n;i++){
if(s[i]!=')') st.push(s[i]);
else{
char a=st.top();st.pop();
char op=st.top();st.pop();
char b=st.top();st.pop();
st.pop();
int ans;
if(op=='&')ans=(a-'0')&(b-'0');
if(op=='|')ans=(a-'0')|(b-'0');
if(op=='^')ans=(a-'0')^(b-'0');
st.push((char)(ans+'0'));
}
}
return (int)(st.top()-'0');
}
int main() {
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cout<<"Case #"<<++kase<<": ";
string s;
cin>>s;n=s.length();
if(n==1){
if(s[0]=='0'||s[0]=='1') cout<<"0"<<endl;
else cout<<"1"<<endl;
continue;
}
string s1=s,s2=s;
for(int i=0;i<n;i++){
if(s1[i]=='x')s1[i]='0';
if(s1[i]=='X')s1[i]='1';
}
for(int i=0;i<n;i++){
if(s2[i]=='x')s2[i]='1';
if(s2[i]=='X')s2[i]='0';
}
if(cal(s1)==cal(s2)) cout<<"0"<<endl;
else cout<<"1"<<endl;
}
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的托儿所
环形传递有个结论,必然存在两个点之间没有传递,这个可以通过反证法证明。
然后写出前缀和的答案,枚举断开处,发现当断开处是前缀和的中位数时,就有了最优情况。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int maxn=1000000+5;
const int INF=0x3f3f3f3f;
ll n,a[maxn],s[maxn];
ll sum=0,ans=0;
int main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
ll mid=sum/n;
for(int i=1;i<=n;i++){
a[i]-=mid;
s[i]=s[i-1]+a[i];
// cout<<s[i]<<" ";
}
sort(s+1,s+n+1);
for(int i=1;i<=n;i++){
ans+=abs(s[i]-s[(n+1)/2]);
}
cout<<ans;
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的表达式
中规中矩的表达式解析问题,由于代码量较长,因此放在了中等题的位置。
代码如下
/*
*/
#include<cstdio>
#include<cstring>
#include<stack>
#include<iostream>
using namespace std;
stack<int> num;
stack<char> ch;
string s;
int l;
int getnum(char a) {
if (a=='+'||a=='-') return 0;
else if (a=='*'||a=='/') return 1;
else if (a=='^') return 2;
else return -1;
}
void count() {
int a=num.top();
num.pop();
if (ch.top()=='+') a=num.top()+a;
else if (ch.top()=='-') a=num.top()-a;
else if (ch.top()=='*') a=num.top()*a;
else if (ch.top()=='/'&&a!=0) a=num.top()/a;
else if (ch.top()=='^') {
int s=1;
for (int i=0; i<a; i++) s*=num.top();
a=s;
}
num.pop();
num.push(a);
ch.pop();
}
void output(stack<int>x){
cout<<"数值栈 ";
int cnt=0;
while(!x.empty()){
int temp=x.top();
cout<<temp<<" ";
x.pop();
}
cout<<endl;
}
void output1(stack<char>x){
cout<<"操作符栈 ";
int cnt=0;
while(!x.empty()){
char temp=x.top();
x.pop();
cout<<temp<<" ";
}
cout<<endl;
}
int main() {
cin>>s;
if (s[0]=='-') s="0"+s;
for (int i=2; i<s.size(); i++)
if (s[i]=='-'&&s[i-1]=='(') s.insert(i,"0"),i++; //将负号转化为减号
l=s.size();
for (int i=0; i<l;) {
if (s[i]>='0'&&s[i]<='9') {
int x=0;
for (; s[i]>='0'&&s[i]<='9'; i++) x=x*10+s[i]-'0';
num.push(x);
} else if (s[i]==')') {
while (num.size()>1&&ch.top()!='(') count();
i++;
//printf("%d\n",ch.size());
if (!ch.empty()) ch.pop(); //删除对应左括号
} else {
if (s[i]=='(') {
ch.push(s[i]);
i++;
continue;
}
while (num.size()>1&&getnum(ch.top())>=getnum(s[i])) count();
ch.push(s[i]);
i++;
}
}
while (!ch.empty()&&ch.top()!='(') count();
printf("%d\n",num.top());
}
困难题:
硕哥的数学题
前置知识:
莫比乌斯函数:
μ(d)的定义是:
1 当时,。
2 当,且为互异素数时,。(说直白点,就是d分解质因数后,没有幂次大于平方的质因子,此时函数值根据分解的个数决定)。
3 只要当d含有任何质因子的幂次大于等于2,则函数值为0。
莫比乌斯反演:
定理:F(n)和f(n)是定义在非负整数集合上的两个函数,并且满足条件:
那么存在一个结论:
我们设:
为的个数,为和的倍数的个数
则可以由莫比乌斯反演可以推出:
设完这两个函数之后,我们便发现,
于是就直接开始推答案:
枚举设为t
这时候,这个式子已经可以做到的时间复杂度了,但是因为有多组数据,所以我们再用一下整除分块,这题就可以做到了。
代码如下
#include<iostream>
#include<cstdio>
#define ll long long
#define re register
using namespace std;
const int N=100009;
int T,pri[N],mu[N],cnt;
ll sum[N];
bool isp[N];
int Cirno() {
mu[1]=1;
for(re int i=2; i<N; ++i) {
if(!isp[i])pri[++cnt]=i,mu[i]=-1;
for(re int j=1; j<=cnt; ++j) {
if(i*pri[j]>=N)break;
isp[i*pri[j]]=1;
if(!(i%pri[j])) {
mu[i*pri[j]]=0;
break;
} else mu[i*pri[j]]=-mu[i];
}
}
for(re int i=1; i<N; ++i)sum[i]=sum[i-1]+mu[i];
}
void solve(int n,int m,int k) {
ll ans=0;
n/=k,m/=k;
int lim=min(n,m);
for(int i=1; i<=lim;) {
ll j=min(n/(n/i),m/(m/i));
ans+=1ll*(sum[j]-sum[i-1])*(n/i)*(m/i);
i=j+1;
}
printf("%lld\n",n*m-ans);
}
int main() {
Cirno();
scanf("%d",&T);
while(T--) {
int n,m;
scanf("%d%d",&n,&m);
solve(n,m,1);
}
return 0;
}
硕哥的树贸易
因为存在买卖顺序的问题,问题不能单纯的理解为在树链上求解最值。
考虑答案的产生过程,有三种情况。
1 在from到lca的过程中完成买卖。
2 在lca到to的过程中完成买卖。
3 在from到lca的过程中以这一段的最低价格买入,在lca到to的过程中以这一段的最高价格卖出。
因此将深度进行二进制划分,完成几个数组的dp。
up数组,up[i][j]维护i到i节点往上2^j的节点的最大差价
down数组,down[i][j]维护i节点往下2^j到i的节点的最大差价
Max数组, Max[i][j]维护i到i节点往上2^j的节点之间价格的最大值
Min数组,Min[i][j]维护i到i节点往上2^j的节点之间价格的最小值
最后答案通过组合这些二进制数组得到。具体内容详见注释。本题在转移时细节较多,需要细心。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=50000+5;
const int INF=0x3f3f3f3f;
int n,q;
struct node {
int from,to;
} edge[maxn<<1];
int t,head[maxn],tot=0,fa[maxn][20],d[maxn],w[maxn],up[maxn][20],down[maxn][20],Max[maxn][20],Min[maxn][20];
/*
up数组,up[i][j]维护i到i节点往上2^j的节点的最大差价
down数组,down[i][j]维护i节点往下2^j到i的节点的最大差价
Max数组, Max[i][j]维护i到i节点往上2^j的节点之间价格的最大值
Min数组,Min[i][j]维护i到i节点往上2^j的节点之间价格的最小值
*/
void add(int u,int v) {
edge[++tot].to=v;edge[tot].from=head[u];head[u]=tot;
}
void bfs() {
queue<int>q;
q.push(1);
d[1]=1;
while(!q.empty()) {
int x=q.front();
q.pop();
for(int i=head[x]; i; i=edge[i].from) {
int y=edge[i].to;
if(!d[y]) {
q.push(y);
d[y]=d[x]+1;
Max[y][0]=max(w[x],w[y]);
Min[y][0]=min(w[x],w[y]);
up[y][0]=max(0,w[x]-w[y]);
// D(x);D(y);D(w[x]);D(w[y]);D(up[y][0]);E;
down[y][0]=max(0,w[y]-w[x]);
fa[y][0]=x;
int res1,res2;
for(int j=1; j<=t; j++) {
fa[y][j]=fa[fa[y][j-1]][j-1];
Max[y][j]=max(Max[y][j-1],Max[fa[y][j-1]][j-1]);
Min[y][j]=min(Min[y][j-1],Min[fa[y][j-1]][j-1]);
res1=max(0,Max[fa[y][j-1]][j-1]-Min[y][j-1]);
res2=max(up[fa[y][j-1]][j-1],up[y][j-1]);
// D(y);D(j);D(res1);D(res2);D(Max[fa[y][j-1]][j-1]);D(Min[y][j-1]);E;
up[y][j]=max(res1,res2);
res1=max(0,Max[y][j-1]-Min[fa[y][j-1]][j-1]);
res2=max(down[fa[y][j-1]][j-1],down[y][j-1]);
down[y][j]=max(res1,res2);
}
}
}
}
}
int lca(int x,int y) {
if(d[x]>d[y]) swap(x,y);
for(int i=t; i>=0; i--) {
if(d[fa[y][i]]>=d[x]) {
y=fa[y][i];
}
}
if(x==y) return x;
for(int i=t; i>=0; i--) {
if(fa[x][i]!=fa[y][i]) {
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int query_up(int x,int dep,int &val){ //val:x到lca的最大差值
val=0;
int res=INF; //res:x到lca的最小值
for(int i=t;i>=0;i--){
if(dep&(1<<i)){
val=max(val,up[x][i]);
val=max(val,Max[x][i]-res); //先算val保证此时的res表示的最小值一定在x以下(即对应点深度大于x)
res=min(res,Min[x][i]);
x=fa[x][i];
}
}
return res;
}
int query_down(int x,int dep,int &val){ //val:lca到x的最大差值
val=0;
int res=0; //res:lca到x的最大值
for(int i=t;i>=0;i--){
if(dep&(1<<i)){
val=max(val,down[x][i]);
val=max(val,res-Min[x][i]); //先算val保证此时的res表示的最小值一定在x以上(即对应点深度小于x)
res=max(res,Max[x][i]);
x=fa[x][i];
}
}
return res;
}
int main() {
// ios::sync_with_stdio(false);
scanf("%d",&n);
t=int((log(double(n))/log(2.0)))+1;
// D(t);E;
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
int from,to;
for(int i=1;i<=n-1;i++) scanf("%d%d",&from,&to),add(from,to),add(to,from);
bfs();
/*
for(int i=1;i<=n;i++){
for(int j=0;j<=t;j++){
D(i);D(j);D(Min[i][j]);D(Max[i][j]);D(up[i][j]);D(down[i][j]);E;
}
E;
}
*/
scanf("%d",&q);
while(q--){
scanf("%d%d",&from,&to);
int temp=lca(from,to);
int a,b,val1,val2;
//a:from到lca的最小值
//b:lca到to的最大值
//val1:from到lca的最大差价
//val2:lca到to的最大差价
a=query_up(from,d[from]-d[temp],val1);
b=query_down(to,d[to]-d[temp],val2);
int ans=max(0,max(max(val1,val2),b-a));
printf("%d\n",ans);
}
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
硕哥的电路图
在电路中,很难直接根据输入判断每条导线中电流的流向。
因此需要基尔霍夫定律来列出每个点电位的方程。
剩下来的,就是高斯消元了。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int INF=0x3f3f3f3f;
const double eps=1e-4;
int n,m;
double a[maxn][maxn],x[maxn];// a和x分别为方程的左边的矩阵和等式右边的值,求解之后x存的就是结果
int equ,var;// 方程数和未知数个数
int Gauss() {// 返回0表示无解,1表示有解
int i,j,k,col,max_r;
for(k=1,col=1; k<=equ&&col<=var; k++,col++) {
max_r=k;
for(i=k+1; i<=equ; i++)if(fabs(a[i][col])>fabs(a[max_r][col]))max_r=i;
if(fabs(a[max_r][col])<eps)return 0;
if(k!=max_r) {
for(j=col; j<=var; j++)swap(a[k][j],a[max_r][j]);
swap(x[k],x[max_r]);
}
x[k]/=a[k][col];
for(j=col+1; j<=var; j++)a[k][j]/=a[k][col];
a[k][col]=1;
for(i=0; i<=equ; i++)if(i!=k) {
x[i]-=x[k]*a[i][k];
for(j=col+1; j<=var; j++)a[i][j]-=a[k][j]*a[i][col];
a[i][col]=0;
}
}
return 1;
}
int main() {
// ios::sync_with_stdio(false);
scanf("%d%d",&n,&m);
int from,to;
double r;
for(int i=1; i<=m; i++) {
scanf("%d%d%lf",&from,&to,&r);
//对于电路中的一条from到to,阻值为r的边。方程组中会有四个项的系数受到影响。
a[from][from]+=1.0/r,a[to][to]+=1.0/r,a[from][to]-=1.0/r,a[to][from]-=1.0/r;
}
// a[n][n]+=1.0;
x[1]=1.0,x[n]=-1.0;
/*
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
printf("%.2f ",a[i][j]);
}
E;
}
*/
equ=var=n;
Gauss();
printf("%.2lf ",x[1]-x[n]); //事实上 x[n]等于0恒成立
// for(int i=1; i<=n; i++) printf("%.2lf ",x[i]);
return 0;
}
#endif
网友评论