2020.07.24 福州大学ACM/ICPC集训队选拔赛 部分题解

  1. 对于任何一个三角形 \(ABC\) ,令 \(f(A,B,C)\) 表示其三条边的三个中点构成的点集。
    现在给定 \(f(A,B,C)\) 且满足所有坐标均为整数,试构造三角形 \(ABC\) 三个顶点坐标。
    保证答案存在且唯一。

中位线定理可得,设三角形 \(DEF\) 对边的中点分别位 \(ABC\) ,则 \(\vec{BC}={1\over 2}\vec{FE}=\vec{FA}=\vec{AE}\)

因此,考虑求解 \(E\) 点坐标等价于求解 \(\vec{OE}\) 。则 \(\vec{OE}=\vec{OA}+\vec{AE}=\vec{OA}+\vec{OC}-\vec{OB}\)

不难知道,三角形两中点和减去第三个中点即可得到第三个中点对角的坐标。按这个方法写完排个序即可。

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pii;
pii operator + (const pii &a,const pii &b) { return pii(a.first+b.first,a.second+b.second); }
pii operator - (const pii &a,const pii &b) { return pii(a.first-b.first,a.second-b.second); }
int main(){
    pii Pnt[5];
    for(int i=1;i<=3;i++) cin>>Pnt[i].first>>Pnt[i].second;
    pii Ans[5];
    Ans[1]=Pnt[1]+Pnt[2]-Pnt[3];
    Ans[2]=Pnt[1]+Pnt[3]-Pnt[2];
    Ans[3]=Pnt[2]+Pnt[3]-Pnt[1];
    sort(Ans+1,Ans+4);
    for(int i=1;i<=3;i++)
        cout<<Ans[i].first<<" "<<Ans[i].second<<endl;
    return 0;
}

  1. 给你两个长度为 \(len\leq 10^​6\)​​ 的由 01 组成的二进制字符串 \(n\)\(m\) 定义 JC 的可爱值为:从 \(1\)\(n\) 中选一个数 \(x\),从 \(1\)\(m\) 中选一个数 \(y\),使得 \((x,y)\) 奇偶性不同的对数.
    下面三个操作一直进行:
    累加 JC 的可爱值.
    字符串 \(n\) , \(m\) 失去他们最后一位,即 \(n=\lfloor{n\over 2}\rfloor,m=\lfloor{m\over 2}\rfloor\) ( \(\lfloor\rfloor\) 表示向下取整).
    如果 \(n=0\),或 \(m=0\) 退出.

不难想出,要使得选出的 \((x,y)\) 奇偶性不同,则必然是 \(x\)\(y\) 一个为奇数,一个为偶数

由于 \(n\) 中的奇数个数为 \(\lfloor{n+1\over 2}\rfloor\) ,偶数个数为 \(\lfloor{n\over 2}\rfloor\)

通过暴力找规律(写一个代码,算不同的 \(n,m\) 时,\((\lfloor{n+1\over 2}\rfloor\cdot \lfloor{m\over 2}\rfloor+\lfloor{n\over 2}\rfloor\cdot \lfloor{m+1\over 2}\rfloor)\) )亦或者稍微证明一下,不难发现,每个 \(n,m\) 对答案的贡献为 \(\lfloor{nm\over 2}\rfloor={nm-(n\&1)\cdot(m\&1)\over 2}\)

现只需考虑如何实现即可:由于 \(nm\) 的乘积在取模意义下等于两者各自的取模,再乘积的取模。\((n\&1)\cdot (m\&1)\) 仅与最后一位有关

故将串 \(n,m\) 前补足 \(0\) ,使得两者长度相同。然后从前往后,一边记录到当前位置 \(n,m\) 在取模意义下的值,一边计算对答案的贡献:若当前二进制位 \(n,m\) 均为 \(1\) ,则对答案的贡献为 \((nm-1)\mod p\) ,否则为 \(nm\mod p\) 。求和即可算出答案。

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7,inv2=MOD+1>>1,MAXN=1e6+10;;
stack<int> num[3];
string s;
inline ll ans(){
    for(int i=1;i<=2;i++){
        getline(cin,s);
        for(int j=s.size()-1;j>=0;j--)
            num[i].push(s[j]=='1');
    }
    while(num[1].size()<num[2].size()) num[1].push(0);
    while(num[1].size()>num[2].size()) num[2].push(0);
    ll A=0,B=0,Ans=0;
    for(int i=num[1].size();i>0;i--){
        int a=num[1].top(); num[1].pop();
        int b=num[2].top(); num[2].pop();
        A=((A<<1)|a)%MOD;
        B=((B<<1)|b)%MOD;
        ll Tmp=A*B-(a&b);
        Tmp=Tmp%MOD*inv2%MOD;
        Ans=(Ans+Tmp)%MOD;
    }
    return Ans;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout<<ans()<<endl;
    return 0;
}

  1. 胖达发现有 \(n\) 只熊猫在甜筒摊前排了一个队伍,从前往后依次编号为 \(1,2,\cdots n\)
    胖达还发现,熊猫们不喜欢老实排队。每过一会儿,会有编号为 \(i\) 的熊猫从队伍里走出来并插到队伍的最前面去,或者从队伍里走出来并排到队伍的末尾
    胖达很无聊,所以他时不时会问当前队伍从前往后第 \(i\) 个位置的熊猫的编号是多少

这题本人做法略微有些复杂,使用了分块+双端队列

将每 \(\sqrt n\) 只熊猫装在一个块内,每只熊猫维护一下当前它所在的块。当熊猫 \(i\) 走到队首时,我们可以对于熊猫 \(i\) 所在的块内暴力删除这个熊猫,再把它加到开头那个分块内。接着,从它原先在的块开始,把前一个块的最后一个弹出,扔进当前块内。记得在处理时更新一下被改动的熊猫所在的块。走到队尾也是类似的处理。

由于原先所在块内有 \(\sqrt n\) 只熊猫,暴力删除的复杂度是 \(O(\sqrt n)\) 的;对外每块末尾扔进后一个块,单次处理是 \(O(1)\) 的,总的次数是 \(O(\sqrt n)\) 次。因此修改的复杂度是 \(O(\sqrt n)\) 的。

查询当前第 \(i\) 只熊猫,可先从分块外扫过去,直到扫到查询熊猫所在分块;在进入分块暴力查询;也可直接用公式算出在第几个分块,然后进入暴力查询。

外面扫分块的复杂度为 \(O(\sqrt n)\) ,用公式直接算出在哪个分块是 \(O(1)\) 的;分块内暴力查询的复杂度是 \(O(\sqrt n)\) 的。因此查询的复杂度是 \(O(\sqrt n)\) 的。

因此,总复杂度为 \(O(q\sqrt n)\) 。过不了就卡卡常数。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
typedef deque<int> dq;
int N,Q;
struct SQRT{
    dq DQ;
    inline void insertBack(int val) { DQ.push_back(val); }
    inline void insertFront(int val) { DQ.push_front(val); }
    inline int popBack(){
        int res=DQ.back();
        DQ.pop_back();
        return res;
    }
    inline int popFront(){
        int res=DQ.front();
        DQ.pop_front();
        return res;
    }
    inline void popElem(int val){
        dq::iterator p=DQ.begin();
        while(*p!=val) p++;
        DQ.erase(p);
    }
    inline int ans(int pos){
        for(auto e : DQ)
            if(pos) pos--;
            else return e;
    }
};
struct MAIN{
    int Siz;
    int Cnt;
    int Set[MAXN];
    SQRT S[500];
    inline void Front(int val){
        int pos=Set[val],res;
        S[pos].popElem(val);
        for(int i=pos;i>0;i--){
            res=S[i-1].popBack();
            Set[res]=i;
            S[i].insertFront(res);
        }
        S[0].insertFront(val);
        Set[val]=0;
    }
    inline void Back(int val){
        int pos=Set[val],res;
        S[pos].popElem(val);
        for(int i=pos;i<Cnt;i++){
            int res=S[i+1].popFront();
            Set[res]=i;
            S[i].insertBack(res);
        }
        S[Cnt].insertBack(val);
        Set[val]=Cnt;
    }
    inline int ans(int pos){
        int Tot=0;
        for(int i=0;i<=Cnt;i++)
            if(pos>=Tot&&pos<Tot+Siz) return S[i].ans(pos-Tot)+1;
            else Tot+=Siz;
    }
}M;
inline void pre(){
    cin>>N>>Q;
    M.Siz=floor(sqrt(N));
    M.Cnt=(N-1)/M.Siz;
    for(int i=0;i<N;i++)
        M.S[i/M.Siz].insertBack(i),M.Set[i]=i/M.Siz;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    pre();
    int T,I;
    while(Q--&&cin>>T>>I)
        if(0);
        else if(T==1) M.Front(I-1);
        else if(T==2) M.Back(I-1);
        else if(T==3) cout<<M.ans(I-1)<<endl;
    return 0;
}

  1. 有一棵 \(n\) 个点,编号分别为 \(1\)\(n\) 的树,其中 \(s\) 号点为根。
    在树上养了很多松鼠,在第 \(i\) 个点上住了 \(A\) 个雄松鼠,\(B\) 个雌松鼠。
    因为某些缘故,它们开始同时向根节点移动,但它们相当不安分,如果在同一个节点上,雄松鼠会和雌松鼠繁殖,简单地来说以下事件会依序发生:
    这个点的 \(A\) 数量变成了 \(A+c\times B\),这个点的 \(B\) 数量变成了 \(B+d\times A\)
    根节点的所有松鼠移动到地面,位于地面上的松鼠不会再繁殖;
    所有松鼠同时朝它们的父节点移动。
    所有事件各自都在一瞬间完成,直至树上没有松鼠。
    现在想知道最终有多少只松鼠到达了地面。

题目有一些问题,理论上当 \(A,B\) 一位为 \(0\) 时,是不会繁殖的。但这题视为会繁殖,那就这样处理吧。

遍历一遍整棵树,不难发现,每个节点的松鼠只会遇到同一层的松鼠。而且根据分配律可证明(先挖坑,自己还没想明白),不论同一层的松鼠怎么分配,它们最后对答案的贡献;即为它们的和,乘上层数次(根节点为 \(1\) 层)的矩阵 \(\left( \begin{matrix} 1&C \\\ \\ D&1 \end{matrix} \right)\)

即第 \(n\) 层对答案的贡献为 \(\left( \begin{matrix} 1&C \\\ \\ D&1 \end{matrix} \right)^n\cdot \left( \begin{matrix} \sum_{dep_i=n}A_i \\\ \\ \sum_{dep_i=n}B_i \end{matrix} \right)\)

预处理出每一层对总和 \(A,B\) 的贡献,矩阵的乘方可以递推,累加起来即是答案。

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7,MAXN=1e5+10;
struct Matrix{
    ll Num[2][2];

    Matrix() { Num[0][0]=Num[0][1]=Num[1][0]=Num[1][1]=0; }
    Matrix operator * (const Matrix &x) const {
        Matrix y;
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                for(int k=0;k<2;k++)
                    y.Num[i][j]=(y.Num[i][j]+Num[i][k]*x.Num[k][j])%MOD;
        return y;
    }
    Matrix operator + (const Matrix &x) const {
        Matrix y;
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                y.Num[i][j]=(Num[i][j]+x.Num[i][j])%MOD;
        return y;
    }
}Bas,Dep[MAXN],Mul,Ans;
ll N,S,C,D,A[MAXN],B[MAXN];
vector<int> Edg[MAXN];
void dfs(int pos,int pa,int fl){
    Dep[fl].Num[0][0]+=A[pos];
    Dep[fl].Num[1][0]+=B[pos];

    for(auto to : Edg[pos])
        if(to!=pa)
            dfs(to,pos,fl+1);
}
inline void pre(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>N>>S>>C>>D;
    for(int i=1;i<N;i++){
        int u,v;
        cin>>u>>v;
        Edg[u].push_back(v);
        Edg[v].push_back(u);
    }
    for(int i=1;i<=N;i++) cin>>A[i]>>B[i];
    Bas.Num[0][0]=1;
    Bas.Num[0][1]=C;
    Bas.Num[1][0]=D;
    Bas.Num[1][1]=1;
    Mul.Num[0][0]=1;
    Mul.Num[1][1]=1;
    dfs(S,0,1);
}
int main(){
    pre();
    for(int i=1;i<=N;i++){
        Mul=Mul*Bas;
        Ans=Ans+Mul*Dep[i];
    }
    cout<<(Ans.Num[0][0]+Ans.Num[1][0])%MOD<<endl;
    return 0;
}

  1. 有一个碗状容器高度为 \(H\) ,容器厚度不计,用任一平行于底的水平平面截取碗得到的都是圆,且半径 \(r(h)\) 满足: \(r(h)=r_​0+e^h,h\in [0,H]\)
    其中 \(h\) 为水平截面到底部的距离,\(e\) 为自然对数。
    给定 \(k\) ,求使得容器恰好储水总容量的 \(​{1\over k}\)​​​ 时的水面高度 \(h_k\)

不难列出方程 \(\displaystyle {1\over k}\int_0^H\pi r^2(h)\text dh=\int_0^{h_k}\pi r^2(h)\text dh\)

令变上限函数 \(\displaystyle f(x)=\int_0^x r^2(h)\text dh\) 则不难整理出 \({1\over k}f(H)=f(h_k)\)

由于 \(f'(x)=r^2(x)\geq 0\)\(f(x)\) 单调递增,可以二分

又因为 \(\displaystyle f(x)=\int_0^x (r_0+e^h)^2\text dh=({1\over 2}e^{2h}+2r_0e^h+r_0^2h)|^x_0={1\over 2}(e^{2x}-1)+2r_0(e^x-1)+r_0^2x\) 其他的就二分可出答案

当然,由于精度要求较低,且 \([0,H]\) 枚举范围较小,可以直接 for 循环处理

【代码】

#include<bits/stdc++.h>
using namespace std;
const double esp=1e-6;
double r0,H,K;
inline double f(double x){
    return 0.5*(exp(2*x)-1)+(exp(x)-1)*2*r0+r0*r0*x;
}
int main(){
    cin>>r0>>H>>K;
    double Y=f(H)/K;
    double Head=0,Tail=H,Mid,Ans=0;
    while(Tail-Head>esp){
        Ans=Mid=(Head+Tail)/2;
        double y=f(Mid);
        if(y>Y) Tail=Mid;
        else Head=Mid;
    }
    printf("%0.2f",Ans);
    return 0;
}

  1. \(n\) 个人(编号 \([1,n]\) ),每个人说了一句话。第 \(i\) 个人说话内容为 \(j,v\) ,当 \(v=1\) 表示第 \(i\) 个人说第 \(j\) 个人说的是真话,若 \(v=0\) 表示说的是假话,若 \(v=−1\) 表示第 \(i\) 个人说不知道第 \(j\) 个人的话是真还是假。
    \(n\) 个人说的句话,每个人说的要么是真的,要么是假的,问共有多少种不同的可能?由于答案很大,请将答案对 \(k\) 取模

类似于食物链。当 \(v_i=1\) 时,分别连接 \(i,j_i\)\(\neg i,\neg j_i\) ;表示若第 \(i\) 个人说的是真话,则第 \(j_i\) 个人说的是真话;若第 \(i\) 个人说的是假话,则第 \(j_i\) 个人说的是假话。

同理,当 \(v_i=0\) 是,分别连接 \(i,\neg j_i\)\(\neg i,j_i\)\(v_i=-1\) 的不处理。

则若存在 \(i\) 使得 \(i,\neg i\) 处于同一并查集内,一定无解。一个人不能既说真话,又说假话。此时返回 \(0\)

否则,若第 \(i\) 个人说了真话,则所有与它处于同一并查集的情况都应视为成立。故统计总并查集的数量 \(Cnt\) ,答案即为 \(2^{Cnt\over 2}\) (每个人说真话和说假话都会统计到一次)

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=2e6+10;
ll N,K;
inline ll fpow(ll a,ll x){
    ll ans=1;
    for(;x;x>>=1,a=a*a%K) if(x&1) ans=ans*a%K;
    return ans;
}
ll Pa[MAXN],Vis[MAXN];
int pa(int n) { return (n==Pa[n])?n:(Pa[n]=pa(Pa[n])); }
inline void merge(int u,int v) { Pa[pa(u)]=pa(v); }
inline bool isunion(int u,int v) { return pa(u)==pa(v); }
inline void pre(){
    cin>>N>>K;
    ll J,V;
    for(int i=1;i<=N+N;i++) Pa[i]=i,Vis[i]=0;
    for(int i=1;i<=N;i++){
        cin>>J>>V;
        if(V==-1) continue;
        else if(V==1) merge(i,J),merge(i+N,J+N);
        else if(V==0) merge(i+N,J),merge(i,J+N);
    }
}
inline ll ans(){
    ll Cnt=0;
    for(int i=1;i<=N;i++)
        if(isunion(i,i+N)) return 0;
        else if(!Vis[pa(i)]){
            Vis[pa(i)]=1;
            Vis[pa(i+N)]=1;
            Cnt++;
        }
    return fpow(2,Cnt);
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    pre();
    cout<<ans()<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/JustinRochester/p/13373163.html
今日推荐