本场比赛题目难度分布
简单题:
硕哥的签到题(出处:原创题)
硕哥的最短路(出处:百度之星2019全国初赛第三场)
硕哥的字符串(出处:百度之星2019全国初赛第二场)
硕哥的大整数(出处:快速乘模板题)
硕哥的全排列(出处:全排列模板题)
中等题:
硕哥的表达式(出处:中缀表达式解析模板题)
硕哥的托儿所(出处:HAOI2008 糖果传递)
硕哥的布尔式(出处:Face Book 全球初赛 Mr.X)
困难题:
硕哥的数学题(出处:原创题)
硕哥的树贸易(出处:POJ3728 The merchant)
硕哥的电路图(出处:POJ3532 Resistance)
以下是逐题题解:
简单题:
硕哥的签到题
签到题,不再赘述。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
硕哥的最短路
手算一下n为3,4,5时候的结果,就会发现规律。
答案就是
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
硕哥的字符串
我们注意到,异或的本质是不进位加法。因此,每一次异或后,结果最多增大一。而如果每一次加法后,结果必然增大一。于是,只要贪心的将所有问号全部换成加号即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
硕哥的大整数
传统的C++数据类型中,不存在某个数字的范围达到。因此我们要考虑通过一种“乘法转化为加法”的方式,来优化我们的计算过程,使得计算中不产生溢出。
我们知道乘法其实就是把很多个加法运算合到一起。现在我们的乘法会爆范围,那我们就把它转化为加法。但是我们不可能一个一个的加,这样复杂度会是 级别。所以我们模仿2进制加法操作来完成。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
硕哥的全排列
这是一道模板题,可以通过调用next_permutation函数,或者直接手写递归实现。
代码提供了上述的两种方法。
代码如下
/*
*/
#define method_2
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
中等题:
硕哥的布尔式
我们考虑最后一步运算,会出现如下三种情况
0 运算符 0
0 运算符 x
x 运算符 x
其中只有第二种情况的结果是有可能不确定的,而这种情况下,最多只要调整一下这个最后一次进行的运算符即可。
因此我们首先将字符串中的所有x换成0后,计算一次答案。然后将字符串中的所有x换成1后,再计算一次答案。
如果两次答案相同,则意味着x的取值不会影响结果。
否则,只要一次操作即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=300+5;
const int INF=0x3f3f3f3f;
int T,n,kase=0;
stackst;
int cal(string s){
for(int i=0;i>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"<
硕哥的托儿所
环形传递有个结论,必然存在两个点之间没有传递,这个可以通过反证法证明。
然后写出前缀和的答案,枚举断开处,发现当断开处是前缀和的中位数时,就有了最优情况。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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<
硕哥的表达式
中规中矩的表达式解析问题,由于代码量较长,因此放在了中等题的位置。
代码如下
/*
*/
#include
#include
#include
#include
using namespace std;
stack num;
stack 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; ix){
cout<<"数值栈 ";
int cnt=0;
while(!x.empty()){
int temp=x.top();
cout<x){
cout<<"操作符栈 ";
int cnt=0;
while(!x.empty()){
char temp=x.top();
x.pop();
cout<>s;
if (s[0]=='-') s="0"+s;
for (int i=2; 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
#include
#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)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
硕哥的树贸易
因为存在买卖顺序的问题,问题不能单纯的理解为在树链上求解最值。
考虑答案的产生过程,有三种情况。
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<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() {
queueq;
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<=0;i--){
if(dep&(1<
硕哥的电路图
在电路中,很难直接根据输入判断每条导线中电流的流向。
因此需要基尔霍夫定律来列出每个点电位的方程。
剩下来的,就是高斯消元了。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<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])