冬季赛题解

本场比赛题目难度分布
简单题:
硕哥的签到题(出处:原创题)
硕哥的最短路(出处:百度之星2019全国初赛第三场)
硕哥的字符串(出处:百度之星2019全国初赛第二场)
硕哥的大整数(出处:快速乘模板题)
硕哥的全排列(出处:全排列模板题)
中等题:
硕哥的表达式(出处:中缀表达式解析模板题)
硕哥的托儿所(出处:HAOI2008 糖果传递)
硕哥的布尔式(出处:Face Book 全球初赛 Mr.X)
困难题:
硕哥的数学题(出处:原创题)
硕哥的树贸易(出处:POJ3728 The merchant)
硕哥的电路图(出处:POJ3532 Resistance)
以下是逐题题解:
简单题:
硕哥的签到题
签到题,不再赘述。
代码如下

/*

*/
#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=+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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=+5;
const int INF=0x3f3f3f3f;
string s;
int main() {
    ios::sync_with_stdio(false);
    cin>>s;
    cout<

硕哥的大整数
传统的C++数据类型中,不存在某个数字的范围达到。因此我们要考虑通过一种“乘法转化为加法”的方式,来优化我们的计算过程,使得计算中不产生溢出。
我们知道乘法其实就是把很多个加法运算合到一起。现在我们的乘法会爆范围,那我们就把它转化为加法。但是我们不可能一个一个的加,这样复杂度会是 级别。所以我们模仿2进制加法操作来完成。
代码如下

/*

*/
#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=+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<

硕哥的全排列
这是一道模板题,可以通过调用next_permutation函数,或者直接手写递归实现。
代码提供了上述的两种方法。
代码如下

/*

*/
#define method_2
#ifdef method_1
/*

*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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<>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
#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])

你可能感兴趣的:(冬季赛题解)